Crossfire Client, Trunk
cfmaplog.py
Go to the documentation of this file.
1 #!/bin/python
2 #
3 license = '''
4 cfmaplog.py - Crossfire GTK Client plug-in to track per-character map visits.
5 Copyright (C) 2025, "Kevin R. Bulgrien" <kbulgrien@att.net>
6 
7 This program is free software: you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free Software
9 Foundation, either version 3 of the License, or (at your option) any later
10 version.
11 
12 This program is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 details.
16 
17 You should have received a copy of the GNU General Public License along with
18 this program. If not, see <https://www.gnu.org/licenses/>.
19 '''
20 
21 # os.path.expanduser()
22 import io
23 
24 # os.path.expanduser()
25 import os
26 
27 # regex
28 import re
29 
30 # database
31 import sqlite3
32 
33 # stderr,stdin,stdout
34 import sys
35 
36 # time.sleep(), time.strftime()
37 import time
38 
39 debug = False
40 
41 # Functions ##################################################################
42 
43 # Send something to the "server". If a newline should go, and it usually
44 # should, pass one at the end of the text. This script may build output
45 # incrementally, so do not add one here.
46 #
47 def client_send(text):
48  sys.stdout.write(text)
49  sys.stdout.flush()
50  return
51 
52 # Send something to the "user" via stderr. For these to be seen, the client
53 # is started in a console. If a newline should go, and it usually should,
54 # pass "\n" at the end of the text. This script may build output
55 # incrementally, so do not add one here.
56 #
57 def console_send(text):
58  sys.stderr.write(text)
59  sys.stderr.flush()
60  return
61 
62 # Send something to facilitate debugging via stderr. For these to be seen,
63 # the client is started in a console. If a newline should go, and it usually
64 # should, pass "\n" at the end of the string. This script may build output
65 # incrementally, so do not add one here.
66 #
67 def debug_send(text):
68  if debug:
69  console_send(text)
70  return
71 
72 # Send something to the "player" by way of the Messages pane. If a newline
73 # should go, and it usually should, pass one at the end of the text. This
74 # script may build output incrementally, so do not add one here.
75 #
76 # IMPORTANT: It appears that the integer-based color is currently broken
77 # in the client; text markup is required to set text attributes.
78 #
79 # See Also: client/common/shared/newclient.h
80 #
81 NDI_BLACK = 0
82 NDI_WHITE = 1
83 NDI_NAVY = 2
84 NDI_RED = 3
85 NDI_ORANGE = 4
86 NDI_BLUE = 5 # Actually, it is Dodger Blue
87 NDI_DK_ORANGE = 6 # DarkOrange2
88 NDI_GREEN = 7 # SeaGreen
89 NDI_LT_GREEN = 8 # DarkSeaGreen, which is actually paler
90  # than seagreen - also background color.
91 NDI_GREY = 9
92 NDI_BROWN = 10 # Sienna.
93 NDI_GOLD = 11
94 NDI_TAN = 12 # Khaki.
95 NDI_MAX_COLOR = 12 # Last value in.
96 
97 NDI_COLOR_MASK = 0xff # Gives lots of room for expansion - we are
98  # using an int anyways, so we have the
99  # space to still do all the flags.
100 
101 NDI_UNIQUE = 0x100 # Print immediately, don't buffer.
102 NDI_ALL = 0x200 # Inform all players of this message.
103 NDI_ALL_DMS = 0x400 # Inform all logged in DMs. Used in case of
104 
105 # See Also: client/gtk-v2/src/info.c
106 #
107 # The following markup strings are supported:
108 #
109 # [b] ... [/b] bold
110 # [i] ... [/i] italic
111 # [ul] ... [/ul] underline
112 # [print] font_style_names[0] ???
113 # [arcane] font_style_names[1]
114 # [strange] font_style_names[2]
115 # [fixed] font_style_names[3]
116 # [hand] font_style_names[4]
117 # [color=x] ... [/color] where x are the quoted color names below.
118 #
119 # font_style_names[] are determined by the selected theme. The default
120 # installed theme files are in client/gtk-v2/themes and are "Standard"
121 # and Black. Both themes use the same set of fonts by default.
122 #
123 # font_style_names[0] = <system/theme default> ???
124 # font_style_names[1] = "URW Chancery Lk"
125 # font_style_names[2] = "Sans Italic"
126 # font_style_names[3] = "Luxi Mono"
127 # font_style_names[4] = "Century Schoolbook L Italic"
128 #
129 # Color names set by the user in the gtkrc file. */
130 # static const char *const usercolorname[NUM_COLORS] = {
131 # "black", /* 0 */
132 # "white", /* 1 */
133 # "darkblue", /* 2 */
134 # "red", /* 3 */
135 # "orange", /* 4 */
136 # "lightblue", /* 5 */
137 # "darkorange", /* 6 */
138 # "green", /* 7 */
139 # "darkgreen", /* 8 *//* Used for window background color */
140 # "grey", /* 9 */
141 # "brown", /* 10 */
142 # "yellow", /* 11 */
143 # "tan" /* 12 */
144 #
145 def player_send(text):
146  client_send(f"draw 0 [color=darkorange]{codefile}: {text}")
147  debug_send(f"{codefile}: {text}")
148  return
149 
151  #
152  # Does a visit log exist for this map and player? This may fail if a player
153  # hasn't visited the map before when the logger was active. If not found,
154  # a map cannot have been marked completed.
155  #
156  if map_id and player_id:
157  cursor.execute('''
158  SELECT COMPLETED, COMPLETED_DATE FROM visit
159  WHERE MAP_ID = ? AND PLAYER_ID = ?
160  ''', ( map_id, player_id ))
161  query = cursor.fetchone()
162  if query != None:
163  completed = query[0]
164  completed_date = query[1]
165  if completed:
166  player_send(f"You marked this area completed {completed} times.\n")
167  player_send(f"The most recent completion was: {completed_date}.\n")
168 
169 # Begin ######################################################################
170 
171 try:
172  server_name = os.environ['CF_SERVER_NAME']
173  player_name = os.environ['CF_PLAYER_NAME']
174 except:
175  console_send(f"{license}\n")
176  console_send(f"This plug-in is not meant to run as a standalone program.\n")
177  sys.exit(0)
178 
179 # Sometimes its hard to pick out where the beginning of the run is located
180 # when stopping and starting the script during development so output this.
181 #
182 debug_send("" + \
183  "-----------------------------------------------------------------------\n")
184 
185 # sqlite3 initialization #####################################################
186 #
187 codefile = re.compile("[.][^.]+\Z").sub("", __file__)
188 datafile = codefile + ".db"
189 codefile = re.compile("^.+\/").sub("", codefile)
190 debug_send(f"{datafile}\n")
191 
192 try:
193  dbConn = sqlite3.connect(f"{datafile}")
194 except:
195  console_send(f"{e}\n")
196  player_send(f"{datafile}\n")
197  player_send(f"[color=red]sqlite3.connect error[/color]\n")
198  player_send(f"exiting...\n")
199  sys.exit(0)
200 
201 cursor = dbConn.cursor()
202 
203 # server initialization ######################################################
204 #
205 servers = 0
206 server_id = 0
207 
208 debug_send(f"CF_SERVER_NAME: {server_name}\n")
209 
210 try:
211  cursor.execute('''
212  CREATE TABLE IF NOT EXISTS server (
213  SERVER_ID INTEGER PRIMARY KEY NOT NULL,
214  SERVER_NAME TEXT UNIQUE NOT NULL
215  )
216  ''')
217 except:
218  console_send(f"{e}\n")
219  player_send(f"{datafile}\n")
220  player_send(f"[color=red]CREATE TABLE server error[/color]\n")
221  player_send(f"exiting...\n")
222  sys.exit(0)
223 
224 try:
225  cursor.execute('''
226  SELECT COUNT(*) FROM server
227  ''')
228 except:
229  console_send(f"{e}\n")
230  player_send(f"{datafile}\n")
231  player_send(f"[color=red]SELECT FROM server error[/color]\n")
232  player_send(f"exiting...\n")
233  sys.exit(0)
234 
235 query = cursor.fetchone()
236 servers = query[0]
237 
238 # Get or assign a server_id for the current server.
239 #
240 try:
241  cursor.execute('''
242  SELECT SERVER_ID FROM server
243  WHERE SERVER_NAME = ?
244  ''', ( server_name, ))
245 except:
246  console_send(f"{e}\n")
247  player_send(f"{datafile}\n")
248  player_send(f"[color=red]SELECT FROM server error[/color]\n")
249  player_send(f"exiting...\n")
250 
251 query = cursor.fetchone()
252 if query == None:
253  servers = servers + 1
254  server_id = servers
255  try:
256  cursor.execute('''
257  INSERT INTO server
258  ( SERVER_ID, SERVER_NAME )
259  VALUES ( ?, ? )
260  ''', (servers, server_name))
261  except:
262  console_send(f"{e}\n")
263  player_send(f"{datafile}\n")
264  player_send(f"[color=red]INSERT INTO server error[/color]\n")
265  player_send(f"exiting...\n")
266  sys.exit(0)
267 
268  dbConn.commit()
269 else:
270  server_id = query[0]
271 
272 debug_send(f"server_name (server_id): {server_name} ({server_id})\n")
273 
274 # player initialization ######################################################
275 #
276 # request player
277 # request player 1481 Player: Brayagorn the human
278 
279 players = 0
280 player_code = 0
281 player_title = ''
282 player_seen = time.strftime("%Y/%m/%d %H:%M")
283 
284 debug_send(f"CF_PLAYER_NAME: {player_name}\n")
285 
286 regx_rqst_player = "^request\splayer\s"
287 regc_rqst_player = re.compile(regx_rqst_player)
288 regx_rqst_player_id = "(\d+)\s+"
289 regc_rqst_player_id = re.compile(regx_rqst_player_id)
290 regx_rqst_player_strt = regx_rqst_player + regx_rqst_player_id
291 regc_rqst_player_strt = re.compile(regx_rqst_player_strt)
292 regx_rqst_player_name = "Player:\s+(\w+)\s+the\s+(.+)"
293 regc_rqst_player_name = re.compile(regx_rqst_player_name)
294 regx_rqst_player_data = regx_rqst_player_id + regx_rqst_player_name
295 regc_rqst_player_data = re.compile(regx_rqst_player_data)
296 
297 try:
298  cursor.execute('''
299  CREATE TABLE IF NOT EXISTS player (
300  PLAYER_ID INTEGER PRIMARY KEY NOT NULL,
301  SERVER_ID INTEGER NOT NULL,
302  PLAYER_NAME TEXT NOT NULL,
303  PLAYER_TITLE TEXT NOT NULL,
304  PLAYER_SEEN TEXT NOT NULL
305  )
306  ''')
307 except:
308  console_send(f"{e}\n")
309  player_send(f"{datafile}\n")
310  player_send(f"[color=red]CREATE TABLE player error[/color]\n")
311  player_send(f"exiting...\n")
312  sys.exit(0)
313 
314 try:
315  cursor.execute('''
316  SELECT COUNT(*) from player
317  ''')
318 except:
319  console_send(f"{e}\n")
320  player_send(f"{datafile}\n")
321  player_send(f"[color=red]SELECT FROM player error[/color]\n")
322  player_send(f"exiting...\n")
323 
324 query = cursor.fetchone()
325 players = query[0]
326 try:
327  cursor.execute('''
328  SELECT PLAYER_ID, PLAYER_TITLE FROM player
329  WHERE PLAYER_NAME = ? AND SERVER_ID = ?
330  ''', ( player_name, server_id ))
331 except:
332  console_send(f"{e}\n")
333  player_send(f"{datafile}\n")
334  player_send(f"[color=red]SELECT FROM player error[/color]\n")
335  player_send(f"exiting...\n")
336 
337 query = cursor.fetchone()
338 if query == None:
339  client_send("request player\n")
340 
341  for buffer in sys.stdin:
342  buffer = buffer.rstrip()
343  if regc_rqst_player_strt.match(buffer):
344  buffer = regc_rqst_player.sub('', buffer)
345  matches = regc_rqst_player_data.match(buffer)
346  player_nmbr = matches.group(1)
347  # player_name = matches.group(2)
348  player_title = matches.group(3)
349  players = players + 1
350  try:
351  cursor.execute('''
352  INSERT INTO player
353  ( PLAYER_ID, SERVER_ID, PLAYER_NAME, PLAYER_TITLE, PLAYER_SEEN )
354  VALUES ( ?, ?, ?, ?, ? )
355  ''', (players, server_id, player_name, player_title, player_seen))
356  except:
357  player_send(f"{datafile}\n")
358  player_send(f"[color=red]INSERT INTO player error[/color]\n")
359  player_send(f"exiting...\n")
360  sys.exit(0)
361 
362  dbConn.commit()
363  player_id = players
364  else:
365  continue
366 else:
367  player_id = query[0]
368  player_title = query[1]
369 
370 debug_send(f"player_id: {player_id}\n")
371 
372 try:
373  cursor.execute('''
374  UPDATE player
375  SET PLAYER_SEEN = ?
376  WHERE PLAYER_ID = ?
377  ''', (player_seen, player_id))
378 except:
379  player_send(f"{datafile}\n")
380  player_send(f"[color=red]UPDATE player error[/color]\n")
381  player_send(f"exiting...\n")
382  sys.exit(0)
383 
384 dbConn.commit()
385 
386 player_send(f"Hello, {player_name}\n")
387 
388 # map initialization #########################################################
389 #
390 # Scorn Alchemy Shop (/scorn/shops/potionshop) in The Kingdom of Scorn
391 # Created: 1996-05-02 bt (thomas@astro.psu.edu)
392 # Modified: 2023-11-27 Rick Tanner
393 #
394 # watch drawextinfo 0 10 0 Undead Church (/scorn/misc/church) in The Kingdom of Scorn
395 # watch drawextinfo 0 10 0 Created: 1993-10-15
396 # Modified: 2021-09-21 Nicolas Weeger
397 #
398 # (null) (/random/undead_quest0000) in The Kingdom of Scorn
399 # xsize -1
400 # ysize -1
401 # wallstyle dungeon2
402 # floorstyle lightdirt
403 # monsterstyle undead
404 # layoutstyle onion
405 # decorstyle creepy
406 # exitstyle sstair
407 # final_map /scorn/peterm/undead_quest
408 # symmetry 4
409 # difficulty_increase 0.000000
410 # dungeon_level 1
411 # dungeon_depth 5
412 # orientation 1
413 # origin_x 4
414 # origin_y 14
415 # random_seed 1746522242
416 #
417 regx_wtch_draw = "^watch drawextinfo (\d+\s){3}"
418 regc_wtch_draw = re.compile(regx_wtch_draw, 0)
419 regx_wtch_draw_strt = regx_wtch_draw + "([^\(]+|\(null\)\s*)\("
420 regc_wtch_draw_strt = re.compile(regx_wtch_draw_strt, 0)
421 regx_wtch_draw_name = regx_wtch_draw_strt + "[~/]"
422 regc_wtch_draw_name = re.compile(regx_wtch_draw_name, 0)
423 regx_wtch_draw_path = "^([^\(\)]+|\(null\)\s)\(([^\)]+)\)\s*(.*)"
424 regc_wtch_draw_path = re.compile(regx_wtch_draw_path, 0)
425 regx_wtch_draw_made = "Created:\s+(.*)"
426 regc_wtch_draw_made = re.compile(regx_wtch_draw_made, 0)
427 regx_wtch_draw_date = "Modified:\s+(.+)"
428 regc_wtch_draw_date = re.compile(regx_wtch_draw_date, 0)
429 regx_wtch_draw_xsiz = regx_wtch_draw + "xsize\s[-]?\d+"
430 regc_wtch_draw_xsiz = re.compile(regx_wtch_draw_xsiz, 0)
431 regx_wtch_draw_rnds = "random_seed\s\d+"
432 regc_wtch_draw_rnds = re.compile(regx_wtch_draw_rnds, 0)
433 
434 cursor.execute('''
435  CREATE TABLE IF NOT EXISTS map (
436  MAP_ID INTEGER PRIMARY KEY NOT NULL,
437  MAP_PATH TEXT UNIQUE NOT NULL,
438  MAP_NAME TEXT NOT NULL,
439  MAP_MADE TEXT NOT NULL,
440  MAP_DATE TEXT NOT NULL
441  )
442 ''')
443 
444 cursor.execute('''
445  SELECT COUNT(*) from map
446 ''')
447 query = cursor.fetchone()
448 maps = query[0]
449 
450 map_id = 0;
451 map_line = -1;
452 map_data = '';
453 map_name = '';
454 map_path = '';
455 map_made = '';
456 map_date = '';
457 
458 # quiet list initialization ##################################################
459 #
460 cursor.execute('''
461  CREATE TABLE IF NOT EXISTS quiet (
462  PLAYER_ID INTEGER,
463  SERVER_ID INTEGER,
464  MAP_PATTERN TEXT NOT NULL
465  )
466 ''')
467 
468 quiets = 0
469 
470 for loop in ( 'world_%_%', '%Apartment%', '%Inn %' ):
471  quiets = quiets + 1
472  cursor.execute('''
473  SELECT
474  MAP_PATTERN
475  FROM
476  quiet
477  WHERE
478  PLAYER_ID IS NULL AND SERVER_ID IS NULL AND MAP_PATTERN = ?
479  ''', (loop, ))
480  if cursor.fetchone() is None:
481  cursor.execute('''
482  INSERT INTO quiet
483  ( PLAYER_ID, SERVER_ID, MAP_PATTERN )
484  VALUES
485  ( NULL, NULL, ? )
486  ''', ( loop, ))
487 
488 cursor.execute('''
489  SELECT COUNT(*) from quiet
490 ''')
491 query = cursor.fetchone()
492 quiets = query[0]
493 
494 # visit log initialization ###################################################
495 #
496 cursor.execute('''
497  CREATE TABLE IF NOT EXISTS visit (
498  MAP_ID INTEGER NOT NULL,
499  PLAYER_ID INTEGER NOT NULL,
500  VISIT_TOTAL INTEGER NOT NULL,
501  VISIT_DATE TEXT NOT NULL,
502  COMPLETED INTEGER DEFAULT 0,
503  VISIT_DATE TEXT NOT NULL DEFAULT ""
504  )
505 ''')
506 
507 # Schema update if COMPLETED is missing.
508 #
509 cursor.execute('''
510  PRAGMA table_info(visit)
511 ''')
512 success = False
513 query = cursor.fetchall()
514 for row in query:
515  # row field numbers are zero-based
516  if row[1] == "COMPLETED":
517  success = True
518 if not success:
519  debug_send(f"ALTER TABLE visit ADD COLUMN COMPLETED INTEGER DEFAULT 0\n")
520  cursor.execute('''
521  ALTER TABLE visit ADD COLUMN COMPLETED INTEGER DEFAULT 0
522  ''')
523 
524 # Schema update if COMPLETED_DATE is missing.
525 #
526 cursor.execute('''
527  PRAGMA table_info(visit)
528 ''')
529 success = False
530 query = cursor.fetchall()
531 for row in query:
532  # row field numbers are zero-based
533  if row[1] == "COMPLETED_DATE":
534  success = True
535 if not success:
536  debug_send(f"ALTER TABLE visit ADD COLUMN COMPLETED_DATE STRING NOT NULL" \
537  + ": DEFAULT ''\n")
538  cursor.execute('''
539  ALTER TABLE visit ADD COLUMN COMPLETED_DATE STRING NOT NULL DEFAULT ""
540  ''')
541 
542 visits = 0
543 visit_date = ''
544 
545 cursor.execute('''
546  SELECT COUNT(*) from visit
547 ''')
548 query = cursor.fetchone()
549 visits = query[0]
550 
551 # visit cache initialization #################################################
552 #
553 vcConn = sqlite3.connect(":memory:")
554 vcursor = vcConn.cursor()
555 
556 vcursor.execute('''
557  CREATE TABLE IF NOT EXISTS vcache (
558  MAP_SEQ INTEGER NOT NULL,
559  MAP_ID INTEGER NOT NULL
560  )
561 ''')
562 
563 v_head = 1
564 v_tail = 0
565 
566 # cfmaplog service ###########################################################
567 
568 regc_scripttell = re.compile('^scripttell\s+', 0)
569 
570 # Notify when entering a map.
571 #
572 client_send("watch newmap\n")
573 
574 # Interact with the client.
575 #
576 for buffer in sys.stdin:
577  buffer = buffer.rstrip()
578 
579  debug_send(f"> '{buffer}'\n")
580 
581  if not len(buffer):
582  time.sleep(0.25)
583  continue
584 
585  if regc_scripttell.match(buffer):
586  match regc_scripttell.sub('', buffer):
587 
588  # Player may halt the script with either 'scripttell' or 'scriptkill'
589  #
590  case 'quit':
591  break
592 
593  # player may toggle debug with 'scripttell'
594  #
595  case 'debug':
596  debug = not debug
597 
598  case 'complete' | 'incomplete':
599  completed = 0
600  completed_date = ""
601  try:
602  cursor.execute('''
603  SELECT COMPLETED, COMPLETED_DATE
604  FROM visit WHERE MAP_ID = ? and PLAYER_ID = ?
605  ''', ( map_id, player_id))
606  except:
607  console_send(f"{e}\n")
608  debug_send(f"map_id {map_id} player_id {player_id}")
609 
610  query = cursor.fetchone()
611  if query == None:
612  if buffer == 'scripttell complete':
613  completed = 1
614  completed_date = time.strftime("%Y/%m/%d %H:%M")
615  else:
616  if buffer == 'scripttell complete':
617  completed = query[0] + 1
618  player_send(f"You've marked this map complete {completed} times.\n")
619  if completed > 1 and len(query[1]):
620  player_send(f"The most recent previous time was: {query[1]}.\n")
621  completed_date = time.strftime("%Y/%m/%d %H:%M")
622  else:
623  if query[0] == 0:
624  player_send(f"This map is already marked as not completed.\n")
625  else:
626  player_send(f"This map is now marked as not completed.\n")
627 
628  debug_send(f"map_id {map_id} player_id {player_id}\n")
629  debug_send(f"completed {completed} completed_date {completed_date}\n")
630  try:
631  cursor.execute('''
632  UPDATE visit
633  SET COMPLETED = ?, COMPLETED_DATE = ?
634  WHERE MAP_ID = ? AND PLAYER_ID = ?
635  ''', (completed, completed_date, map_id, player_id))
636  except:
637  console_send(f"{e}\n")
638 
639  dbConn.commit()
640 
641  case _:
642  player_send(f"'{buffer}'\n")
643  player_send(f"[color=red]Not a recognized command.[\color]\n")
644 
645  buffer = ''
646  continue
647 
648  # When a map is entered, ask the client to start forwarding drawextinfo
649  # the server sends. Instruct the client to issue a mapinfo command on
650  # our behalf. Then commence listening for mapinfo output the client
651  # forwards to us.
652  #
653  if buffer == "watch newmap":
654  buffer = ''
655  map_id = 0;
656  map_line = 0;
657  map_data = '';
658  map_name = '';
659  map_path = '';
660  map_made = '';
661  map_date = '';
662  client_send("watch drawextinfo\n")
663  client_send("issue 1 1 mapinfo\n")
664  continue
665 
666  if map_line >= 0:
667  map_line = map_line + 1
668 
669  debug_send(f"{map_line}> '{buffer}'\n")
670 
671  if regc_wtch_draw_name.match(buffer):
672  buffer = regc_wtch_draw.sub('', buffer)
673  map_data = "map_name"
674  elif regc_wtch_draw_made.search(buffer):
675  buffer = regc_wtch_draw.sub('', buffer)
676  map_data = "map_made"
677  elif regc_wtch_draw_date.search(buffer):
678  buffer = regc_wtch_draw.sub('', buffer)
679  map_data = "map_date"
680  elif regc_wtch_draw_xsiz.search(buffer):
681  buffer = regc_wtch_draw.sub('', buffer)
682  map_date = time.strftime("%Y-%m-%d")
683  map_data = "map_xsize"
684  map_made = 'random'
685  elif regc_wtch_draw_rnds.match(buffer):
686  map_data = "map_write"
687  else:
688  buffer = ''
689  continue
690 
691  debug_send(f"map_data {map_data}\n")
692 
693  match map_data:
694  case 'map_name':
695  matches = regc_wtch_draw_path.match(buffer)
696  debug_send(f"{map_line}_1: '" + matches.group(1) + "'\n")
697  debug_send(f"{map_line}_2: '" + matches.group(2) + "'\n")
698  debug_send(f"{map_line}_3: '" + matches.group(3) + "'\n")
699 
700  map_name = matches.group(1) + matches.group(3)
701  map_path = matches.group(2)
702  continue
703  case 'map_made':
704  matches = regc_wtch_draw_made.search(buffer)
705  map_made = matches.group(1)
706  buffer = ''
707  continue
708  case 'map_date':
709  matches = regc_wtch_draw_date.search(buffer)
710  map_date = matches.group(1)
711  map_data = 'map_write'
712 
713  if map_data == 'map_write':
714 
715  debug_send(f"map_name (map_path) {map_name} ({map_path})\n")
716  debug_send(f"map_made {map_made}\n")
717  debug_send(f"map_date {map_date}\n")
718  debug_send(f"unwatch: drawextinfo\n")
719 
720  client_send("unwatch drawextinfo\n")
721 
722  # Insert a "new" map into the map database. Assume if the insertion
723  # fails that it was already present. TODO: Do not insert if unneeded.
724  #
725  try:
726  maps = maps + 1
727  cursor.execute('''
728  INSERT INTO map
729  ( MAP_ID, MAP_PATH, MAP_NAME, MAP_MADE, MAP_DATE )
730  VALUES ( ?, ?, ?, ?, ? )
731  ''', (maps, map_path, map_name, map_made, map_date))
732  dbConn.commit()
733  except sqlite3.IntegrityError:
734  maps = maps - 1
735 
736  # Get the map_id of the entered map. This SHOULD NEVER fail because the
737  # map should always have been added by this point.
738  #
739  cursor.execute('''
740  SELECT MAP_ID FROM map
741  WHERE MAP_PATH = ?
742  ''', ( map_path, ))
743  query = cursor.fetchone()
744  if query == None:
745  map_id = 0
746  pass
747  else:
748  map_id = query[0]
749 
750  debug_send(f"map_id: {map_id}\n")
751 
752  # Is the map_id in the (recently) visit(ed) cache? If so, do not make
753  # an attempt to log the visit. This should reduce spammy visits to
754  # maps that may occur when passing through doors inside a store, or
755  # perhaps even for events like dimention door.
756  #
757  vcursor.execute ('''
758  SELECT MAP_SEQ FROM vcache
759  WHERE MAP_ID = ?
760  ''', ( map_id, ) )
761  query = vcursor.fetchone()
762  if query != None:
763  debug_send(f"SQUELCH visit message!\n")
764  #
765  # But completion notices are never squelched if for recent visits as
766  # this may affect a player's choice as to whether or not to proceed.
767  #
769  else:
770  vcursor.execute('''
771  INSERT INTO vcache
772  ( MAP_SEQ, MAP_ID )
773  VALUES ( ?, ? )
774  ''', (v_head, map_id) )
775  dbConn.commit()
776  v_head = v_head + 1
777  debug_send(f"vcache -> v_head: {v_head}\n")
778  if v_head - v_tail >= 10:
779  vcursor.execute('''
780  DELETE FROM vcache
781  WHERE MAP_SEQ = ?
782  ''', (v_tail, ))
783  dbConn.commit()
784  v_tail = v_tail + 1
785  debug_send(f"vcache -> v_tail: {v_tail}\n")
786 
787  # Is there already a visit log for this map and player? This may fail
788  # because a player hasn't visited the map before when the logger was
789  # active. If not found, add a new visit log, otherwise update the
790  # existing log.
791  #
792  visit_date = time.strftime("%Y/%m/%d %H:%M")
793  cursor.execute('''
794  SELECT VISIT_TOTAL, COMPLETED, COMPLETED_DATE FROM visit
795  WHERE MAP_ID = ? AND PLAYER_ID = ?
796  ''', ( map_id, player_id ))
797  query = cursor.fetchone()
798  if query == None:
799  visit_total = 0
800  cursor.execute('''
801  INSERT INTO visit
802  ( MAP_ID, PLAYER_ID, VISIT_TOTAL, VISIT_DATE )
803  VALUES ( ?, ?, ?, ? )
804  ''', (map_id, player_id, 1, visit_date))
805  dbConn.commit()
806  player_send(f"I don't think I remember this place!\n")
807  else:
808  completed_date = query[2]
809  visit_total = query[0]
810  completed = query[1]
811  cursor.execute('''
812  SELECT
813  MAP_PATTERN
814  FROM
815  quiet
816  WHERE PLAYER_ID IS NULL AND SERVER_ID IS NULL AND ? LIKE MAP_PATTERN
817  ''', ( map_name, ))
818  query = cursor.fetchone()
819  if query:
820  debug_send(f"QUIET!SHH!\n")
821  else:
822  player_send(f"You were here at least {visit_total} times prior.\n")
823  #
824  # Update the visit count
825  #
826  visit_total = visit_total + 1
827  debug_send(f"visit_total {visit_total}\n")
828 
829  try:
830  cursor.execute('''
831  UPDATE visit
832  SET VISIT_TOTAL = ?, VISIT_DATE = ?
833  WHERE MAP_ID = ? AND PLAYER_ID = ?
834  ''', (visit_total, visit_date, map_id, player_id))
835  except:
836  console_send(f"{e}\n")
837  dbConn.commit()
838  #
839  # Always check map completion status without regard to quiet status.
840  #
842 
843  map_line = -1;
844 
845  buffer = ''
846 
847 player_send(f"Farewell, {player_name}\n")
848 
849 vcConn.close()
850 dbConn.close()
851 
852 # End ########################################################################
853 
854 sys.exit(0)
855 
by
cfmaplog py A Crossfire Map Log Plug In designed for the Crossfire GTK2 Client Conceptualized by
Definition: cfmaplog.txt:4
installer
To create a Windows client installer
Definition: nsis.txt:1
cfmaplog.check_completed
def check_completed()
Definition: cfmaplog.py:150
in
static GInputStream * in
Definition: client.c:71
client
To create a Windows client you need NSIS First build the client
Definition: nsis.txt:3
cfmaplog.debug_send
def debug_send(text)
Definition: cfmaplog.py:67
cfmaplog.client_send
def client_send(text)
Definition: cfmaplog.py:47
cfmaplog.player_send
def player_send(text)
Definition: cfmaplog.py:145
needed
To create a Windows client you need NSIS First build the then put all its required files into a files subdirectory of the directory containing this readme if needed
Definition: nsis.txt:5
cfmaplog.console_send
def console_send(text)
Definition: cfmaplog.py:57
script
Definition: script.c:120