1 /*************************************************************************** 2 * (C) Copyright 2003-2013 - Stendhal * 3 *************************************************************************** 4 *************************************************************************** 5 * * 6 * This program is free software; you can redistribute it and/or modify * 7 * it under the terms of the GNU General Public License as published by * 8 * the Free Software Foundation; either version 2 of the License, or * 9 * (at your option) any later version. * 10 * * 11 ***************************************************************************/ 12 package games.stendhal.server.script; 13 14 import java.util.Arrays; 15 import java.util.LinkedList; 16 import java.util.List; 17 18 import org.apache.log4j.Logger; 19 20 import games.stendhal.common.Direction; 21 import games.stendhal.common.grammar.Grammar; 22 import games.stendhal.common.parser.Sentence; 23 import games.stendhal.server.core.engine.SingletonRepository; 24 import games.stendhal.server.core.engine.StendhalRPZone; 25 import games.stendhal.server.core.events.TurnListener; 26 import games.stendhal.server.core.events.TurnNotifier; 27 import games.stendhal.server.core.scripting.ScriptImpl; 28 import games.stendhal.server.core.scripting.ScriptingSandbox; 29 import games.stendhal.server.entity.npc.ChatAction; 30 import games.stendhal.server.entity.npc.ConversationStates; 31 import games.stendhal.server.entity.npc.EventRaiser; 32 import games.stendhal.server.entity.npc.SpeakerNPC; 33 import games.stendhal.server.entity.npc.condition.AdminCondition; 34 import games.stendhal.server.entity.npc.condition.NotCondition; 35 import games.stendhal.server.entity.player.Player; 36 37 38 /** 39 * A herald which will tell news to citizens. 40 * 41 * @author yoriy 42 */ 43 public class Herald extends ScriptImpl { 44 45 // TODO: there is ability of using list of herald names, 46 // it will add to game more fun. 47 public final String HeraldName = "Patrick"; 48 49 // after some thinking, i decided to not implement here 50 // news records to file. 51 private final Logger logger = Logger.getLogger(Herald.class); 52 private final int REQUIRED_ADMINLEVEL_INFO = 100; 53 private final int REQUIRED_ADMINLEVEL_SET = 1000; 54 private final TurnNotifier turnNotifier = TurnNotifier.get(); 55 56 //private final String HaveNoTime = "Hi, I have to do my job, so I have no time to speak with you, sorry."; 57 private final String HiOldFriend = "Oh, you're here! Hi, my old friend, glad to see you."; 58 private final String TooScared = "Oh, you are crazy, sure. I can't help you, the Emperor will kill us both for that."; 59 private final String BadJoke = "Joke, yes? I like jokes, but not too much."; 60 private final String FeelBad = "Oh, I don't know what is wrong with me, I'm not feeling very well... sorry, I can't help you..."; 61 private final String DontUnderstand = "Sorry, I don't understand you"; 62 private final String InfoOnly = "Oh, I think I can trust you enough to tell you my current announcements list. "; 63 private final String WillHelp = "Sure, I will do for you all that you want."+ 64 " Tell me '#speech <time interval (seconds)> <time limit (seconds)> <text to speech>'. " + 65 "If you want to remove one of my current announcements, "+ 66 "tell me '#remove <number of speech>'. "+ 67 "You can also ask me about current announcements, say '#info' for that."; 68 69 private final LinkedList<HeraldNews> heraldNews = new LinkedList<HeraldNews>(); 70 71 /** 72 * class for herald announcements. 73 */ 74 private final static class HeraldNews { 75 76 private final String news; 77 private final int interval; 78 private final int limit; 79 private int counter; 80 private final int id; 81 private final HeraldListener tnl; getNews()82 public String getNews(){ 83 return(news); 84 } getInterval()85 public int getInterval(){ 86 return(interval); 87 } getLimit()88 public int getLimit(){ 89 return(limit); 90 } getCounter()91 public int getCounter(){ 92 return(counter); 93 } getid()94 public int getid(){ 95 return(id); 96 } getTNL()97 public HeraldListener getTNL(){ 98 return(tnl); 99 } setCounter(int count)100 public void setCounter(int count){ 101 this.counter=count; 102 } 103 104 /** 105 * constructor for news 106 * @param news - text to speech 107 * @param interval - interval between speeches in seconds. 108 * @param limit - time limit in seconds. 109 * @param counter - counter of speeches 110 * @param tnl - listener object 111 * @param id - unique number to internal works with news. 112 */ HeraldNews(String news, int interval, int limit, int counter, HeraldListener tnl, int id)113 public HeraldNews(String news, int interval, int limit, int counter, 114 HeraldListener tnl, int id){ 115 this.news=news; 116 this.interval=interval; 117 this.limit=limit; 118 this.counter=counter; 119 this.tnl=tnl; 120 this.id=id; 121 } 122 } 123 124 /** 125 * herald turn listener object. 126 */ 127 class HeraldListener implements TurnListener{ 128 private final int id; 129 /** 130 * function invokes by TurnNotifier each time when herald have to speech. 131 */ 132 @Override onTurnReached(int currentTurn)133 public void onTurnReached(int currentTurn) { 134 workWithCounters(id); 135 } 136 /** 137 * tnl constructor. 138 * @param i - id of news 139 */ HeraldListener(int i)140 public HeraldListener(int i) { 141 id=i; 142 } 143 } 144 145 /** 146 * invokes by /script -load Herald.class, 147 * placing herald near calling admin. 148 */ 149 @Override load(final Player admin, final List<String> args, final ScriptingSandbox sandbox)150 public void load(final Player admin, final List<String> args, final ScriptingSandbox sandbox) { 151 if (admin==null) { 152 logger.error("herald called by null admin", new Throwable()); 153 } else { 154 if (sandbox.getZone(admin).collides(admin.getX()+1, admin.getY())) { 155 logger.info("Spot for placing herald is occupied."); 156 admin.sendPrivateText("Spot (right) near you is occupied, can't place herald here."); 157 return; 158 } 159 sandbox.setZone(admin.getZone()); 160 sandbox.add(getHerald(sandbox.getZone(admin), admin.getX() + 1, admin.getY())); 161 } 162 } 163 164 /** 165 * function invokes by HeraldListener.onTurnReached each time when herald have to speech. 166 * @param id - ID for news in news list 167 */ workWithCounters(int id)168 public void workWithCounters(int id) { 169 int index=-1; 170 for (int i=0; i<heraldNews.size(); i++){ 171 if(heraldNews.get(i).getid()==id){ 172 index=i; 173 } 174 } 175 if (index==-1) { 176 logger.info("workWithCounters: id not found. "); 177 } 178 try { 179 final int interval = heraldNews.get(index).getInterval(); 180 final int limit = heraldNews.get(index).getLimit(); 181 final String text = heraldNews.get(index).getNews(); 182 int counter = heraldNews.get(index).getCounter(); 183 HeraldListener tnl = heraldNews.get(index).getTNL(); 184 final SpeakerNPC npc = SingletonRepository.getNPCList().get(HeraldName); 185 npc.say(text); 186 counter++; 187 turnNotifier.dontNotify(tnl); 188 if(interval*counter<limit){ 189 heraldNews.get(index).setCounter(counter); 190 turnNotifier.notifyInSeconds(interval, tnl); 191 } else { 192 // it was last announce. 193 heraldNews.remove(index); 194 } 195 } catch (IndexOutOfBoundsException ioobe) { 196 logger.error("workWithCounters: index is out of bounds: "+Integer.toString(index)+ 197 ", size "+Integer.toString(heraldNews.size())+ 198 ", id "+Integer.toString(id),ioobe); 199 } 200 } 201 202 /** 203 * kind of herald constructor 204 * @param zone - zone to place herald 205 * @param x - x coord in zone 206 * @param y - y coord in zone 207 * @return herald NPC :-) 208 */ getHerald(StendhalRPZone zone, int x, int y)209 private SpeakerNPC getHerald(StendhalRPZone zone, int x, int y) { 210 final SpeakerNPC npc = new SpeakerNPC(HeraldName) { 211 212 /** 213 * npc says his job list 214 */ 215 class ReadJobsAction implements ChatAction { 216 @Override 217 public void fire(final Player player, final Sentence sentence, final EventRaiser npc){ 218 int newssize = heraldNews.size(); 219 if(newssize==0){ 220 npc.say("My announcements list is empty."); 221 return; 222 } 223 StringBuilder sb=new StringBuilder(); 224 sb.append("Here " + Grammar.isare(newssize) + " my current " + Grammar.plnoun(newssize,"announcement") + ": "); 225 226 227 for(int i=0; i<newssize;i++){ 228 // will add 1 to position numbers to show position 0 as 1. 229 logger.info("info: index "+Integer.toString(i)); 230 try { 231 final int left = heraldNews.get(i).getLimit()/heraldNews.get(i).getInterval()- 232 heraldNews.get(i).getCounter(); 233 sb.append(" #"+Integer.toString(i+1)+". (left "+ 234 Integer.toString(left)+" times): "+ 235 "#Every #"+Integer.toString(heraldNews.get(i).getInterval())+ 236 " #seconds #to #"+Integer.toString(heraldNews.get(i).getLimit())+ 237 " #seconds: \""+heraldNews.get(i).getNews()+"\""); 238 } catch (IndexOutOfBoundsException ioobe) { 239 logger.error("ReadNewsAction: size of heraldNews = "+ 240 Integer.toString(newssize), ioobe); 241 } 242 if(i!=(newssize-1)){ 243 sb.append("; "); 244 } 245 } 246 npc.say(sb.toString()); 247 } 248 } 249 250 251 /** 252 * npc says his job list 253 */ 254 class ReadNewsAction implements ChatAction { 255 @Override 256 public void fire(final Player player, final Sentence sentence, final EventRaiser npc){ 257 int newssize = heraldNews.size(); 258 if(newssize==0){ 259 npc.say("My announcements list is empty."); 260 return; 261 } 262 263 StringBuilder sb=new StringBuilder(); 264 sb.append("Here " + Grammar.isare(newssize) + " my current " + Grammar.plnoun(newssize,"announcement") + ": "); 265 266 for(int i=0; i<newssize;i++){ 267 // will add 1 to position numbers to show position 0 as 1. 268 logger.info("info: index "+Integer.toString(i)); 269 try { 270 sb.append("\""+heraldNews.get(i).getNews()+"\""); 271 } catch (IndexOutOfBoundsException ioobe) { 272 logger.error("ReadNewsAction: size of heraldNews = "+ 273 Integer.toString(newssize), ioobe); 274 } 275 if(i!=(newssize-1)){ 276 sb.append("; "); 277 } 278 } 279 npc.say(sb.toString()); 280 } 281 } 282 /** 283 * NPC adds new job to his job list. 284 */ 285 class WriteNewsAction implements ChatAction { 286 @Override 287 public void fire(final Player player, final Sentence sentence, final EventRaiser npc){ 288 String text = sentence.getOriginalText(); 289 logger.info("Original sentence: " + text); 290 final String[] starr = text.split(" "); 291 if(starr.length < 2){ 292 npc.say("You forget time limit. I am mortal too and somewhat senile, you know."); 293 return; 294 } 295 try { 296 final int interval = Integer.parseInt(starr[1].trim()); 297 final int limit = Integer.parseInt(starr[2].trim()); 298 if(limit < interval){ 299 npc.say("I can count to "+Integer.toString(interval)+ 300 ", and "+Integer.toString(limit)+" is less then " + Integer.toString(interval)+ 301 ". Repeat please."); 302 return; 303 } 304 try { 305 text = text.substring(starr[0].length()).trim(). 306 substring(starr[1].length()).trim(). 307 substring(starr[2].length()).trim(); 308 final String out="Interval: "+Integer.toString(interval)+", limit: "+ 309 Integer.toString(limit)+", text: \""+text+"\""; 310 npc.say("Ok, I have recorded it. "+out); 311 logger.info("Admin "+player.getName()+ 312 " added announcement: " +out); 313 final HeraldListener tnl = new HeraldListener(heraldNews.size()); 314 heraldNews.add(new HeraldNews(text, interval, limit, 0, tnl, heraldNews.size())); 315 turnNotifier.notifyInSeconds(interval,tnl); 316 } catch (IndexOutOfBoundsException ioobe) { 317 npc.say(FeelBad); 318 logger.error("WriteNewsAction: Error while parsing sentence "+sentence.toString(), ioobe); 319 } 320 } catch (NumberFormatException nfe) { 321 npc.say(DontUnderstand); 322 logger.info("Error while parsing numbers. Interval and limit is: "+"("+starr[0]+"), ("+starr[1]+")"); 323 } 324 } 325 } 326 327 /** 328 * NPC removes one job from his job list. 329 */ 330 class RemoveNewsAction implements ChatAction { 331 @Override 332 public void fire(final Player player, final Sentence sentence, final EventRaiser npc){ 333 String text = sentence.getOriginalText(); 334 final String[] starr = text.split(" "); 335 if(starr.length < 2){ 336 npc.say("Tell me the number of sentence to remove."); 337 return; 338 } 339 final String number = starr[1]; 340 try { 341 final int i = Integer.parseInt(number)-1; 342 if (i < 0){ 343 npc.say(BadJoke); 344 return; 345 } 346 if (heraldNews.size()==0) { 347 npc.say("I dont have announcements now."); 348 return; 349 } 350 if(i>=(heraldNews.size())){ 351 npc.say("I have only "+ Integer.toString(heraldNews.size())+ 352 " announcements at the moment."); 353 return; 354 } 355 logger.warn("Admin "+player.getName()+" removing announcement #"+ 356 Integer.toString(i)+": interval "+ 357 Integer.toString(heraldNews.get(i).getInterval())+", limit "+ 358 Integer.toString(heraldNews.get(i).getLimit())+", text \""+ 359 heraldNews.get(i).getNews()+"\""); 360 turnNotifier.dontNotify(heraldNews.get(i).getTNL()); 361 heraldNews.remove(i); 362 npc.say("Ok, already forget it."); 363 } catch (NumberFormatException nfe) { 364 logger.error("RemoveNewsAction: cant remove "+number+" speech.", nfe); 365 npc.say(DontUnderstand); 366 } 367 } 368 } 369 370 /** 371 * npc removes all jobs from his job list 372 */ 373 class ClearNewsAction implements ChatAction { 374 @Override 375 public void fire(final Player player, final Sentence sentence, final EventRaiser npc){ 376 logger.info("ClearAllAction: Admin "+player.getName()+ 377 " cleared announcement list."); 378 for (int i=0; i<heraldNews.size(); i++) { 379 turnNotifier.dontNotify(heraldNews.get(i).getTNL()); 380 } 381 if(heraldNews.size()!=0){ 382 npc.say("Ufff, I have now some time for rest. I heard, there is a gambling game in Semos city?"); 383 heraldNews.clear(); 384 } else { 385 npc.say("Oh, thank you for trying to help me, but I'm ok."); 386 } 387 } 388 } 389 390 /** 391 * Finite states machine logic for herald. 392 */ 393 @Override 394 public void createDialog() { 395 add(ConversationStates.IDLE, 396 Arrays.asList("hi", "hola", "hello", "heya"), 397 new NotCondition(new AdminCondition(REQUIRED_ADMINLEVEL_INFO)), 398 ConversationStates.IDLE, 399 null, new ReadNewsAction()); 400 add(ConversationStates.IDLE, 401 Arrays.asList("hi", "hola", "hello", "heya"), 402 new AdminCondition(REQUIRED_ADMINLEVEL_INFO), 403 ConversationStates.ATTENDING, 404 HiOldFriend, null); 405 add(ConversationStates.ATTENDING, 406 Arrays.asList("help"), 407 new AdminCondition(REQUIRED_ADMINLEVEL_SET), 408 ConversationStates.ATTENDING, 409 WillHelp, null); 410 add(ConversationStates.ATTENDING, 411 Arrays.asList("speech", "remove"), 412 new NotCondition(new AdminCondition(REQUIRED_ADMINLEVEL_SET)), 413 ConversationStates.ATTENDING, 414 TooScared, null); 415 add(ConversationStates.ATTENDING, 416 Arrays.asList("help"), 417 new NotCondition(new AdminCondition(REQUIRED_ADMINLEVEL_SET)), 418 ConversationStates.ATTENDING, 419 InfoOnly, new ReadJobsAction()); 420 add(ConversationStates.ATTENDING, 421 Arrays.asList("info", "list", "tasks", "news"), 422 new AdminCondition(REQUIRED_ADMINLEVEL_INFO), 423 ConversationStates.ATTENDING, 424 null, new ReadJobsAction()); 425 add(ConversationStates.ATTENDING, 426 Arrays.asList("speech"), 427 new AdminCondition(REQUIRED_ADMINLEVEL_SET), 428 ConversationStates.ATTENDING, 429 null, new WriteNewsAction()); 430 add(ConversationStates.ATTENDING, 431 Arrays.asList("remove"), 432 new AdminCondition(REQUIRED_ADMINLEVEL_SET), 433 ConversationStates.ATTENDING, 434 null, new RemoveNewsAction()); 435 add(ConversationStates.ATTENDING, 436 Arrays.asList("clear"), 437 new AdminCondition(REQUIRED_ADMINLEVEL_SET), 438 ConversationStates.ATTENDING, 439 null, new ClearNewsAction()); 440 addGoodbye(); 441 } 442 }; 443 zone.assignRPObjectID(npc); 444 npc.setEntityClass("heraldnpc"); 445 npc.setPosition(x, y); 446 npc.initHP(100); 447 npc.setDirection(Direction.LEFT); 448 return(npc); 449 } 450 } 451