1
0

q3a.lisp 6.9 KB

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