q3a.lisp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. (in-package :cl-user)
  2. (defpackage chatikbot.plugins.q3a
  3. (:use :cl :chatikbot.common :alexandria))
  4. (in-package :chatikbot.plugins.q3a)
  5. (defsetting *server-log* "/home/gamer/.q3a/baseq3/server.log" "Path to Q3Arena server log file to watch")
  6. (defvar *watcher* nil "Log watcher thread if set")
  7. (defvar *lock-game* (bordeaux-threads:make-recursive-lock "Q3A lock"))
  8. (defun watch-file (filespec thunk &optional (start-at :end))
  9. "This will block forever, waiting for new data"
  10. (with-open-file (s filespec)
  11. (file-position s start-at)
  12. (loop
  13. with last-pos = (file-position s)
  14. for line = (read-line s nil nil)
  15. when line do (unwind-protect
  16. (handler-case (funcall thunk line)
  17. (error (e) (log:error e)))
  18. (setf last-pos (file-position s)))
  19. else do (progn
  20. (sleep 0.1)
  21. (when (> last-pos (file-length s))
  22. (file-position s :start))))))
  23. (defun parse-bslash-list (str)
  24. (loop for (key val) on
  25. (loop for pos = 0 then (1+ next-pos)
  26. for next-pos = (position #\\ str :start pos)
  27. collect (subseq str pos next-pos)
  28. while next-pos) by 'cddr
  29. collect (cons key val)))
  30. (defstruct game info players last-time is-active)
  31. (defstruct player name info kills began)
  32. (defstruct kill whom how)
  33. (defvar *game* (make-game) "Currently active game")
  34. (defun max-players (&optional (game *game*))
  35. (length (game-players game)))
  36. (defun map-name (&optional (game *game*))
  37. (agets (game-info game) "mapname"))
  38. (defun suicidep (player kill)
  39. (or (null (kill-whom kill))
  40. (equal (kill-whom kill) player)))
  41. (defun player-score (player)
  42. (loop for kill in (player-kills player)
  43. when (suicidep player kill) sum -1 else sum 1))
  44. (defun get-rankings (&optional (game *game*))
  45. (when-let (players (game-players game))
  46. (sort (loop for player across players
  47. when (player-began player)
  48. collect (cons (player-score player) player))
  49. #'> :key #'car)))
  50. (defun game-status (&optional (game *game*))
  51. (let ((ranks (get-rankings)))
  52. (if ranks
  53. (format nil (if (game-is-active game)
  54. "[Q3] Match *~A* is on for ~A~%~{~A~^~%~}"
  55. "[Q3] Match *~A* has ended after ~A~%~{~A~^~%~}")
  56. (map-name) (game-last-time *game*)
  57. (loop for (score . player) in ranks
  58. collect (format nil "*~A* ~A" score (player-name player))))
  59. "[Q3] War awaits")))
  60. (defun say (fmt &rest args)
  61. (let ((words (apply #'format nil fmt args)))
  62. (write words) (terpri)
  63. (dolist (*chat-id* (lists-get :q3a))
  64. (bot-send-message words))))
  65. (defun weapon (kill)
  66. (kill-how kill))
  67. (defun on-init ())
  68. (defun on-begin (player)
  69. (unless (player-began player)
  70. (say "[Q3] ~A joined the server on ~A" (player-name player) (map-name))))
  71. ;; (defun on-kill (player kill)
  72. ;; (let* ((name (player-name player))
  73. ;; (who (kill-whom kill)))
  74. ;; (if (suicidep player kill)
  75. ;; (say "[Q3] ~A suicides with score ~A" name (player-score player))
  76. ;; (say "[Q3] ~A frags ~A with score ~A" name (player-name who) (player-score player)))))
  77. (defun on-kill (player kill)
  78. (declare (ignorable player kill)))
  79. (defun on-disconnect (player)
  80. (when (player-began player)
  81. (say "[Q3] ~A has left the match with ~A frags" (player-name player) (player-score player))))
  82. (defun on-exit ()
  83. (say (game-status)))
  84. (defun process-log (line)
  85. (bordeaux-threads:with-recursive-lock-held (*lock-game*)
  86. (let* ((time (subseq line 0 7))
  87. (sep (position #\: line :start 7))
  88. (action (keyify (subseq line 7 sep)))
  89. (args (when (and sep (> (length line) (+ 2 sep))) (subseq line (+ 2 sep))))
  90. (client (when args (parse-integer args :junk-allowed t)))
  91. (player (when (and client (<= 0 client (1- (max-players))))
  92. (elt (game-players *game*) client))))
  93. (setf (game-last-time *game*) (trim-nil time))
  94. (case action
  95. (:initgame (with-slots (info players is-active) *game*
  96. (setf info (parse-bslash-list (subseq args 1))
  97. is-active t
  98. players (or players
  99. (let ((count (parse-integer (agets info "sv_maxclients"))))
  100. (make-array count :element-type 'player
  101. :initial-contents (loop for i below count collect (make-player))))))
  102. (on-init)))
  103. (:clientuserinfochanged (with-slots (info name) player
  104. (setf info (parse-bslash-list
  105. (subseq args (1+ (position #\Space args))))
  106. name (agets info "n"))))
  107. (:clientbegin (progn
  108. (on-begin player)
  109. (setf (player-began player) t)))
  110. (:clientdisconnect (progn
  111. (on-disconnect player)
  112. (setf (player-began player) nil)))
  113. (:kill (destructuring-bind (a1 a2 how)
  114. (mapcar 'parse-integer (split-sequence:split-sequence
  115. #\Space (subseq args 0 (position #\: args))))
  116. (let* ((killee (when (<= a1 (max-players))
  117. (elt (game-players *game*) a2)))
  118. (killer (elt (game-players *game*) (if killee a1 a2)))
  119. (kill (make-kill :whom killee :how how)))
  120. (push kill (player-kills killer))
  121. (on-kill killer kill))))
  122. (:exit (progn
  123. (setf (game-is-active *game*) nil)
  124. (on-exit)))
  125. (:shutdowngame (loop for player across (game-players *game*)
  126. do (setf (player-kills player) nil))))))
  127. (values))
  128. (defun ensure-watcher ()
  129. (bt:with-recursive-lock-held (*lock-game*)
  130. (if (lists-get :q3a)
  131. (unless *watcher*
  132. (setf *watcher* (bt:make-thread (lambda () (watch-file *server-log* #'process-log)) :name "q3a watch")))
  133. (when *watcher*
  134. (bt:destroy-thread *watcher*)
  135. (setf *watcher* nil))))
  136. (values))
  137. (defun handle-set-watch (enable)
  138. (lists-set-entry :q3a *chat-id* enable)
  139. (ensure-watcher)
  140. (bot-send-message (if enable "[Q3] Шлём войну!" "[Q3] Миру-мир!")))
  141. (defun handle-active ()
  142. (bot-send-message (game-status)))
  143. (def-message-cmd-handler handle-cmd-q3a (:q3a)
  144. (cond
  145. ((= 1 (length *args*))
  146. (handle-set-watch (equal "on" (car *args*))))
  147. (:otherwise (handle-active))))
  148. (add-hook :starting #'ensure-watcher)