1
0

q3a.lisp 6.5 KB

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