1. /*
  2. * Poem Reader AMX Mod Plugin
  3. * [inc] Joe Dalton <joe-dalton@fahr-zur-hoelle.org>
  4. * Created 21.09.2003
  5. *
  6. * Reads lines from a file and partitions them into poems.
  7. * One or more empty lines denote a new poem.
  8. * Lines starting with a semicolon are comments.
  9. * After waiting INITIAL_INTERVAL seconds, begins to print the poems using server say.
  10. * It then prints a line, waits for 'poem_line_interval' seconds, prints the next line
  11. * and so on, until the poem ends, then waits for 'poem_interval' seconds and prints
  12. * the next poem. After printing the last poem it starts over with the first one.
  13. *
  14. * Use the console command 'poem' to control the Poem Reader. 'poem help' displays
  15. * a list of available commands.
  16. */
  17.  
  18. #include <amxmodx>
  19. #include <amxmisc>
  20.  
  21. /* adjust these to your needs ************************************************/
  22.  
  23. // maximum number of poems
  24. #define MAX_POEMS 128
  25. // maximum number of total poem lines
  26. #define MAX_LINES 256
  27. // maximum no. of characters per line
  28. #define MAX_LINE_LEN 128
  29. // task id
  30. #define TASK_ID 1234032
  31. // seconds after map start in which the first task begins
  32. #define INITIAL_INTERVAL 30
  33. // default interval between poems
  34. stock const DEFAULT_INTERVAL[] = "900"
  35. // default interval between lines of one poem
  36. stock const DEFAULT_LINE_INTERVAL[] = "1"
  37. // poems file name, $basedir is a placeholder for amx dir (default addons/amx)
  38. stock const POEMS_FILE[] = "poems.ini"
  39. // poem console command permission
  40. stock const ADMIN_POEM = ADMIN_RCON
  41.  
  42. /* never change these! *******************************************************/
  43. stock const POEM_NAME[] = "Poem Reader"
  44. stock const POEM_VERSION[] = "1.2b"
  45. stock const POEM_AUTHOR[] = "[inc] Joe Dalton"
  46. stock const CVAR_VERSION[] = "poem_version"
  47. stock const CVAR_INTERVAL[] = "poem_interval"
  48. stock const CVAR_LINE_INTERVAL[] = "poem_line_interval"
  49. stock const LOCALINFO_CURRENT_POEM[] = "poem_current"
  50. stock const LOCALINFO_TASK_TIME[] = "poem_task_time"
  51. stock const CONCMD_POEM[] = "poem"
  52. stock const POEMCMD_HELP[] = "help"
  53. stock const POEMCMD_STATUS[] = "status"
  54. stock const POEMCMD_VERSION[] = "version"
  55. stock const POEMCMD_RESET[] = "reset"
  56. stock const POEMCMD_RELOAD[] = "reload"
  57. stock const POEMCMD_PAUSE[] = "pause"
  58. stock const POEMCMD_RESUME[] = "resume"
  59.  
  60. // file containing the actual poems
  61. static poems_file[64]
  62. // poem lines
  63. static poems[MAX_LINES][MAX_LINE_LEN]
  64. // no. of lines per poem
  65. static poem_lengths[MAX_POEMS]
  66. // no. of poems
  67. static num_poems
  68. // no. of lines total
  69. static num_lines
  70. // current poem
  71. static current_poem
  72. // first line of the current poem
  73. static current_poem_line
  74. // next line read
  75. static current_line
  76. // time of the next line in seconds since 1970/01/01 00:00:00
  77. static task_time
  78. // paused or not?
  79. static bool:paused
  80.  
  81. public plugin_init() {
  82. register_plugin(POEM_NAME, POEM_VERSION, POEM_AUTHOR)
  83. register_cvar(CVAR_INTERVAL, DEFAULT_INTERVAL)
  84. register_cvar(CVAR_LINE_INTERVAL, DEFAULT_LINE_INTERVAL)
  85. register_cvar(CVAR_VERSION, POEM_VERSION, FCVAR_SERVER)
  86. register_concmd(CONCMD_POEM,"cmd_poem",ADMIN_POEM, "<command> - 'poem help' displays a list of available commands");
  87. //build_path( poems_file , 63 , POEMS_FILE )
  88. get_configsdir( poems_file, 63 )
  89. format( poems_file, 63, "%s/%s", poems_file, POEMS_FILE )
  90. load_poems()
  91. restore_status()
  92. start_reading()
  93. return PLUGIN_CONTINUE
  94. }
  95.  
  96. public plugin_end() {
  97. save_status()
  98. return PLUGIN_CONTINUE
  99. }
  100.  
  101. /*
  102. * Reads a poem line, calculates the next line and sets a task to read it.
  103. * Called by the registered tasks, see set_poem_task().
  104. */
  105. public read_poem() {
  106. new interval = 1
  107.  
  108. if(!paused) {
  109. if(is_developer()) { // debug
  110. server_print("Poem %d, line %d = %s", current_poem, current_line, poems[current_line])
  111. server_print("Poem starts on line %d", current_poem_line)
  112. }
  113.  
  114. print_poem_line()
  115.  
  116. new next_poem_line = current_poem_line + poem_lengths[current_poem]
  117.  
  118. if(is_developer()) // debug
  119. server_print("Next poem starts on line %d", next_poem_line)
  120.  
  121. if(current_line >= next_poem_line) { // it's the beginning of the next poem
  122. current_poem++
  123. current_poem_line = current_line
  124. interval = get_cvar_num(CVAR_INTERVAL)
  125.  
  126. if(current_poem >= num_poems) { // start over
  127. reset()
  128. next_poem_line = poem_lengths[0]
  129. }
  130. }
  131. else
  132. interval = get_cvar_num(CVAR_LINE_INTERVAL)
  133.  
  134. if(is_developer()) // debug
  135. server_print("Next poem line %d in %d seconds^n", current_line, interval)
  136. }
  137.  
  138. //set task for next line
  139. set_poem_task(interval)
  140.  
  141. return PLUGIN_CONTINUE
  142. }
  143.  
  144. /*
  145. * Handler for poem console command. Called by AMXMod.
  146. * id player id
  147. * level player acces level
  148. * cid command id
  149. */
  150. public cmd_poem(const id, const level, const cid) {
  151. if (cmd_access(id, level, cid, 0)) {
  152. new cmd[32]
  153. read_argv(1, cmd, 31)
  154.  
  155. if(is_developer())
  156. server_print("%s %s", CONCMD_POEM, cmd)
  157.  
  158. if(strlen(cmd) == 0 || equali(POEMCMD_HELP, cmd)) {
  159. console_print(id, "%s commands:", POEM_NAME)
  160. print_cmd_help(id, POEMCMD_HELP, "this message")
  161. print_cmd_help(id, POEMCMD_STATUS, "display status information")
  162. print_cmd_help(id, POEMCMD_RESET, "continue with the first line of the first poem")
  163. print_cmd_help(id, POEMCMD_RELOAD, "reload the poems file (also resets)")
  164. print_cmd_help(id, POEMCMD_PAUSE, "pause reading")
  165. print_cmd_help(id, POEMCMD_RESUME, "resume reading")
  166. print_cmd_help(id, POEMCMD_VERSION, "display Poem Reader version")
  167. }
  168. else if (equali(POEMCMD_STATUS, cmd)) {
  169. if(paused)
  170. console_print(id, "%s is paused", POEM_NAME)
  171. else {
  172. new time_formatted[20]
  173. format_time(time_formatted, 19, "%m/%d/%Y %H:%M:%S", task_time)
  174. new interval = task_time - get_systime()
  175. console_print(id, "Reading %d poems with %d total lines", num_poems, num_lines)
  176. console_print(id, "Poem %d, next line %d in %ds (%s) = %s",
  177. current_poem, current_line, interval, time_formatted, poems[current_line])
  178. /*new interval = task_time - get_systime()
  179. console_print(id, "Poem %d, next line %d in %ds = %s",
  180. current_poem, current_line, interval, poems[current_line])*/
  181. }
  182. }
  183. else if (equali(POEMCMD_RESET, cmd)) {
  184. remove_task(TASK_ID);
  185. task_time = 0
  186. reset()
  187. start_reading()
  188. console_print(id, "%s reset", POEM_NAME)
  189. }
  190. else if (equali(POEMCMD_RELOAD, cmd)) {
  191. load_poems()
  192. console_print(id, "%s reloaded", POEM_NAME)
  193. }
  194. else if (equali(POEMCMD_PAUSE, cmd)) {
  195. paused = true
  196. console_print(id, "%s paused", POEM_NAME)
  197. }
  198. else if (equali(POEMCMD_RESUME, cmd)) {
  199. paused = false
  200. console_print(id, "%s resumed", POEM_NAME)
  201. }
  202. else if (equali(POEMCMD_VERSION, cmd)) {
  203. console_print(id, "%s Version %s (%s)", POEM_NAME, POEM_VERSION, POEM_AUTHOR)
  204. }
  205. else {
  206. console_print(id, "%s: Invalid command %s %s", POEM_NAME, CONCMD_POEM, cmd)
  207. }
  208. } // if
  209. return PLUGIN_HANDLED
  210. }
  211.  
  212. /* private functions *********************************************************/
  213.  
  214. /*
  215. * Log and start reading, see set_poem_task().
  216. */
  217. start_reading() {
  218. if(num_poems > 0) {
  219. log_message("[AMX] %s: Reading %d poems with %d total lines", POEM_NAME, num_poems, num_lines)
  220. if(!is_dedicated_server())
  221. server_print("%s: Reading %d poems with %d total lines", POEM_NAME, num_poems, num_lines)
  222.  
  223. new systime = get_systime()
  224. new interval = INITIAL_INTERVAL
  225. if(task_time > systime) // restore scheduled task
  226. interval = task_time - systime
  227.  
  228. set_poem_task(interval)
  229. }
  230. }
  231.  
  232. /*
  233. * Print the current poem line.
  234. */
  235. print_poem_line() {
  236. if(is_dedicated_server())
  237. server_cmd("say %s", poems[current_line])
  238. else { // simulate server say, because on listenserver server say == loopback client say
  239. new hostname[64]
  240. get_cvar_string("hostname", hostname, 63)
  241. client_print(0, print_chat, "<%s> %s", hostname, poems[current_line])
  242. }
  243. current_line++
  244. }
  245.  
  246. /*
  247. * Load the poems from the poems file.
  248. * Initializes num_poems, num_lines, poems[] and poem_lengths[]
  249. */
  250. load_poems() {
  251. if (!file_exists(poems_file))
  252. {
  253. log_message("[AMX] %s error: Poems file %s not found", POEM_NAME, poems_file)
  254. if(!is_dedicated_server())
  255. server_print("[AMX] %s error: Poems file %s not found", POEM_NAME, poems_file)
  256. return false
  257. }
  258. else {
  259. reset()
  260. new i=0, line[MAX_LINE_LEN], len, bool:newpoem = true
  261. num_lines = num_poems = 0
  262. while(read_file(poems_file, i, line, MAX_LINE_LEN-1, len)) {
  263. if(num_lines > MAX_LINES) {
  264. new msg[] = "[AMX] %s error: Too many poem lines, max. %d"
  265. log_message(msg, POEM_NAME, MAX_LINES)
  266. if(!is_dedicated_server())
  267. server_print(msg, POEM_NAME, MAX_LINES)
  268. break;
  269. }
  270. if(num_poems > MAX_POEMS) {
  271. new msg[] = "[AMX] %s error: Too many poems, max. %d"
  272. log_message(msg, POEM_NAME, MAX_POEMS)
  273. if(!is_dedicated_server())
  274. server_print(msg, POEM_NAME, MAX_POEMS)
  275. break;
  276. }
  277.  
  278. if (strlen(line) > 0) {
  279. if(contain(line, ";") != 0) { // ignore comments
  280. if (newpoem)
  281. num_poems++;
  282. newpoem = false
  283. poems[num_lines++] = line
  284. poem_lengths[num_poems]++
  285. }
  286. }
  287. else if (!newpoem) { // one or more empty lines start a new poem
  288. newpoem = true
  289. }
  290. i++
  291. }
  292. }
  293.  
  294. if(is_developer())
  295. dump_poems()
  296.  
  297. return true
  298. }
  299.  
  300. /*
  301. * Set the task to read the current poem line and save the time it will trigger.
  302. */
  303. set_poem_task(const interval) {
  304. set_task(float(interval),"read_poem", TASK_ID)
  305. task_time = get_systime() + interval
  306. }
  307.  
  308. /*
  309. * Reset, so reading will continue on first line of first poem.
  310. */
  311. reset()
  312. current_poem = current_line = current_poem_line = 0
  313.  
  314. /*
  315. * Store the current status as localinfo, so it can be restored after a mapchange.
  316. * Called by plugin_end().
  317. */
  318. save_status() {
  319. set_localinfo_num(LOCALINFO_CURRENT_POEM, current_poem)
  320. set_localinfo_num(LOCALINFO_TASK_TIME, task_time)
  321.  
  322. if (is_developer())
  323. server_print("%s status saved: current_poem %d, task_time %d^n",
  324. POEM_NAME, current_poem, task_time)
  325. }
  326.  
  327. /*
  328. * Restore task_time, current_poem, current_line and current_poem_line.
  329. * Called by plugin_init().
  330. */
  331. restore_status() {
  332. task_time = get_localinfo_num(LOCALINFO_TASK_TIME)
  333. /*if(get_systime() >= task_time) {
  334. if(task_time != 0)
  335. remove_task(TASK_ID)
  336. task_time = 0
  337. }*/
  338. current_poem = get_localinfo_num(LOCALINFO_CURRENT_POEM)
  339. current_line = 0
  340. for(new i = 0; i < current_poem; i++)
  341. current_line += poem_lengths[i]
  342. current_poem_line = current_line
  343.  
  344. if(is_developer())
  345. server_print("%s status restored: current_poem %d, current_poem_line %d, current_line %d, task_time %d^n",
  346. POEM_NAME, current_poem, current_poem_line, current_line, task_time)
  347. }
  348.  
  349. /*
  350. * Prints usage notes for a poem command to the client console.
  351. * Called by cmd_poem() on console command 'poem help'.
  352. */
  353. print_cmd_help(const id, const command[], const description[])
  354. console_print(id, "%11s: %s", command, description)
  355.  
  356. /* utility functions *********************************************************/
  357.  
  358. /* Convenience function. */
  359. stock get_localinfo_num(const name[]) {
  360. new temp[32]
  361. get_localinfo(name, temp, 31)
  362. if(strlen(temp) > 0)
  363. return str_to_num(temp)
  364. return 0
  365. }
  366.  
  367. /* Convenience function. */
  368. stock set_localinfo_num(const name[], const value) {
  369. new temp[32]
  370. num_to_str(value, temp, 31)
  371. set_localinfo(name, temp)
  372. }
  373.  
  374. /* Convenience function. */
  375. stock is_developer()
  376. return get_cvar_num("developer") != 0
  377.  
  378. /*
  379. * For debugging.
  380. * Output the currently loaded poems and their lengths to the server console.
  381. */
  382. stock dump_poems() {
  383. for(new poem = 0; poem < num_poems; poem++) {
  384. server_print("Poem %d has %d lines", poem, poem_lengths[poem])
  385. }
  386. server_print("")
  387. for(new line = 0; line < num_lines; line++) {
  388. server_print("Poem line %d: %s", line, poems[line])
  389. }
  390. server_print("")
  391. }