4 cfmaplog.py - Crossfire GTK Client plug-in to track per-character map visits.
5 Copyright (C) 2025, "Kevin R. Bulgrien" <kbulgrien@att.net>
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
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
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/>.
48 sys.stdout.write(text)
58 sys.stderr.write(text)
146 client_send(f
"draw 0 [color=darkorange]{codefile}: {text}")
156 if map_id
and player_id:
158 SELECT COMPLETED, COMPLETED_DATE FROM visit
159 WHERE MAP_ID = ? AND PLAYER_ID = ?
160 ''', ( map_id, player_id ))
161 query = cursor.fetchone()
164 completed_date = query[1]
166 player_send(f
"You marked this area completed {completed} times.\n")
167 player_send(f
"The most recent completion was: {completed_date}.\n")
172 server_name = os.environ[
'CF_SERVER_NAME']
173 player_name = os.environ[
'CF_PLAYER_NAME']
176 console_send(f
"This plug-in is not meant to run as a standalone program.\n")
183 "-----------------------------------------------------------------------\n")
187 codefile = re.compile(
"[.][^.]+\Z").sub(
"", __file__)
188 datafile = codefile +
".db"
189 codefile = re.compile(
"^.+\/").sub(
"", codefile)
193 dbConn = sqlite3.connect(f
"{datafile}")
197 player_send(f
"[color=red]sqlite3.connect error[/color]\n")
201 cursor = dbConn.cursor()
208 debug_send(f
"CF_SERVER_NAME: {server_name}\n")
212 CREATE TABLE IF NOT EXISTS server (
213 SERVER_ID INTEGER PRIMARY KEY NOT NULL,
214 SERVER_NAME TEXT UNIQUE NOT NULL
220 player_send(f
"[color=red]CREATE TABLE server error[/color]\n")
226 SELECT COUNT(*) FROM server
231 player_send(f
"[color=red]SELECT FROM server error[/color]\n")
235 query = cursor.fetchone()
242 SELECT SERVER_ID FROM server
243 WHERE SERVER_NAME = ?
244 ''', ( server_name, ))
248 player_send(f
"[color=red]SELECT FROM server error[/color]\n")
251 query = cursor.fetchone()
253 servers = servers + 1
258 ( SERVER_ID, SERVER_NAME )
260 ''', (servers, server_name))
264 player_send(f
"[color=red]INSERT INTO server error[/color]\n")
272 debug_send(f
"server_name (server_id): {server_name} ({server_id})\n")
282 player_seen = time.strftime(
"%Y/%m/%d %H:%M")
284 debug_send(f
"CF_PLAYER_NAME: {player_name}\n")
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)
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
310 player_send(f
"[color=red]CREATE TABLE player error[/color]\n")
316 SELECT COUNT(*) from player
321 player_send(f
"[color=red]SELECT FROM player error[/color]\n")
324 query = cursor.fetchone()
328 SELECT PLAYER_ID, PLAYER_TITLE FROM player
329 WHERE PLAYER_NAME = ? AND SERVER_ID = ?
330 ''', ( player_name, server_id ))
334 player_send(f
"[color=red]SELECT FROM player error[/color]\n")
337 query = cursor.fetchone()
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)
348 player_title = matches.group(3)
349 players = players + 1
353 ( PLAYER_ID, SERVER_ID, PLAYER_NAME, PLAYER_TITLE, PLAYER_SEEN )
354 VALUES ( ?, ?, ?, ?, ? )
355 ''', (players, server_id, player_name, player_title, player_seen))
358 player_send(f
"[color=red]INSERT INTO player error[/color]\n")
368 player_title = query[1]
377 ''', (player_seen, player_id))
380 player_send(f
"[color=red]UPDATE player error[/color]\n")
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)
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
445 SELECT COUNT(*) from map
447 query = cursor.fetchone()
461 CREATE TABLE IF NOT EXISTS quiet (
464 MAP_PATTERN TEXT NOT NULL
470 for loop
in (
'world_%_%',
'%Apartment%',
'%Inn %' ):
478 PLAYER_ID IS NULL AND SERVER_ID IS NULL AND MAP_PATTERN = ?
480 if cursor.fetchone()
is None:
483 ( PLAYER_ID, SERVER_ID, MAP_PATTERN )
489 SELECT COUNT(*) from quiet
491 query = cursor.fetchone()
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 ""
510 PRAGMA table_info(visit)
513 query = cursor.fetchall()
516 if row[1] ==
"COMPLETED":
519 debug_send(f
"ALTER TABLE visit ADD COLUMN COMPLETED INTEGER DEFAULT 0\n")
521 ALTER TABLE visit ADD COLUMN COMPLETED INTEGER DEFAULT 0
527 PRAGMA table_info(visit)
530 query = cursor.fetchall()
533 if row[1] ==
"COMPLETED_DATE":
536 debug_send(f
"ALTER TABLE visit ADD COLUMN COMPLETED_DATE STRING NOT NULL" \
539 ALTER TABLE visit ADD COLUMN COMPLETED_DATE STRING NOT NULL DEFAULT ""
546 SELECT COUNT(*) from visit
548 query = cursor.fetchone()
553 vcConn = sqlite3.connect(
":memory:")
554 vcursor = vcConn.cursor()
557 CREATE TABLE IF NOT EXISTS vcache (
558 MAP_SEQ INTEGER NOT NULL,
559 MAP_ID INTEGER NOT NULL
568 regc_scripttell = re.compile(
'^scripttell\s+', 0)
576 for buffer
in sys.stdin:
577 buffer = buffer.rstrip()
585 if regc_scripttell.match(buffer):
586 match regc_scripttell.sub(
'', buffer):
598 case
'complete' |
'incomplete':
603 SELECT COMPLETED, COMPLETED_DATE
604 FROM visit WHERE MAP_ID = ? and PLAYER_ID = ?
605 ''', ( map_id, player_id))
608 debug_send(f
"map_id {map_id} player_id {player_id}")
610 query = cursor.fetchone()
612 if buffer ==
'scripttell complete':
614 completed_date = time.strftime(
"%Y/%m/%d %H:%M")
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")
624 player_send(f
"This map is already marked as not completed.\n")
626 player_send(f
"This map is now marked as not completed.\n")
628 debug_send(f
"map_id {map_id} player_id {player_id}\n")
629 debug_send(f
"completed {completed} completed_date {completed_date}\n")
633 SET COMPLETED = ?, COMPLETED_DATE = ?
634 WHERE MAP_ID = ? AND PLAYER_ID = ?
635 ''', (completed, completed_date, map_id, player_id))
643 player_send(f
"[color=red]Not a recognized command.[\color]\n")
653 if buffer ==
"watch newmap":
667 map_line = map_line + 1
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"
685 elif regc_wtch_draw_rnds.match(buffer):
686 map_data =
"map_write"
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")
700 map_name = matches.group(1) + matches.group(3)
701 map_path = matches.group(2)
704 matches = regc_wtch_draw_made.search(buffer)
705 map_made = matches.group(1)
709 matches = regc_wtch_draw_date.search(buffer)
710 map_date = matches.group(1)
711 map_data =
'map_write'
713 if map_data ==
'map_write':
715 debug_send(f
"map_name (map_path) {map_name} ({map_path})\n")
729 ( MAP_ID, MAP_PATH, MAP_NAME, MAP_MADE, MAP_DATE )
730 VALUES ( ?, ?, ?, ?, ? )
731 ''', (maps, map_path, map_name, map_made, map_date))
733 except sqlite3.IntegrityError:
740 SELECT MAP_ID FROM map
743 query = cursor.fetchone()
758 SELECT MAP_SEQ FROM vcache
761 query = vcursor.fetchone()
774 ''', (v_head, map_id) )
778 if v_head - v_tail >= 10:
792 visit_date = time.strftime(
"%Y/%m/%d %H:%M")
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()
802 ( MAP_ID, PLAYER_ID, VISIT_TOTAL, VISIT_DATE )
803 VALUES ( ?, ?, ?, ? )
804 ''', (map_id, player_id, 1, visit_date))
806 player_send(f
"I don't think I remember this place!\n")
808 completed_date = query[2]
809 visit_total = query[0]
816 WHERE PLAYER_ID IS NULL AND SERVER_ID IS NULL AND ? LIKE MAP_PATTERN
818 query = cursor.fetchone()
822 player_send(f
"You were here at least {visit_total} times prior.\n")
826 visit_total = visit_total + 1
832 SET VISIT_TOTAL = ?, VISIT_DATE = ?
833 WHERE MAP_ID = ? AND PLAYER_ID = ?
834 ''', (visit_total, visit_date, map_id, player_id))