1 /* $Id$ */
2 /***************************************************************************
3  *                      (C) Copyright 2003 - Marauroa                      *
4  ***************************************************************************
5  ***************************************************************************
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  ***************************************************************************/
13 package games.stendhal.server.entity;
14 
15 import static games.stendhal.common.Constants.KARMA_SETTINGS;
16 import static games.stendhal.common.constants.General.COMBAT_KARMA;
17 
18 import java.awt.geom.Rectangle2D;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Objects;
26 import java.util.WeakHashMap;
27 import java.util.function.Predicate;
28 import java.util.stream.Collectors;
29 import java.util.stream.Stream;
30 import java.util.stream.StreamSupport;
31 
32 import org.apache.log4j.Logger;
33 
34 import com.google.common.collect.ImmutableList;
35 import com.google.common.collect.ImmutableList.Builder;
36 
37 import games.stendhal.common.EquipActionConsts;
38 import games.stendhal.common.Level;
39 import games.stendhal.common.NotificationType;
40 import games.stendhal.common.Rand;
41 import games.stendhal.common.constants.Nature;
42 import games.stendhal.common.constants.SoundLayer;
43 import games.stendhal.common.constants.Testing;
44 import games.stendhal.common.grammar.Grammar;
45 import games.stendhal.common.parser.WordList;
46 import games.stendhal.server.actions.equip.DropAction;
47 import games.stendhal.server.core.engine.GameEvent;
48 import games.stendhal.server.core.engine.ItemLogger;
49 import games.stendhal.server.core.engine.SingletonRepository;
50 import games.stendhal.server.core.engine.StendhalRPZone;
51 import games.stendhal.server.core.engine.db.StendhalKillLogDAO;
52 import games.stendhal.server.core.engine.dbcommand.LogKillEventCommand;
53 import games.stendhal.server.core.events.TurnListener;
54 import games.stendhal.server.core.events.TutorialNotifier;
55 import games.stendhal.server.entity.creature.Creature;
56 import games.stendhal.server.entity.creature.Pet;
57 import games.stendhal.server.entity.item.CaptureTheFlagFlag;
58 import games.stendhal.server.entity.item.Corpse;
59 import games.stendhal.server.entity.item.Item;
60 import games.stendhal.server.entity.item.StackableItem;
61 import games.stendhal.server.entity.mapstuff.portal.Portal;
62 import games.stendhal.server.entity.npc.TrainingDummy;
63 import games.stendhal.server.entity.player.Player;
64 import games.stendhal.server.entity.slot.EntitySlot;
65 import games.stendhal.server.entity.slot.Slots;
66 import games.stendhal.server.entity.status.Status;
67 import games.stendhal.server.entity.status.StatusAttacker;
68 import games.stendhal.server.entity.status.StatusList;
69 import games.stendhal.server.entity.status.StatusType;
70 import games.stendhal.server.events.AttackEvent;
71 import games.stendhal.server.events.SoundEvent;
72 import games.stendhal.server.events.TextEvent;
73 import games.stendhal.server.util.CounterMap;
74 import marauroa.common.game.RPAction;
75 import marauroa.common.game.RPObject;
76 import marauroa.common.game.RPSlot;
77 import marauroa.common.game.SyntaxException;
78 import marauroa.server.db.command.DBCommandPriority;
79 import marauroa.server.db.command.DBCommandQueue;
80 import marauroa.server.game.Statistics;
81 import marauroa.server.game.db.DAORegister;
82 
83 public abstract class RPEntity extends GuidedEntity {
84 	/**
85 	 * The title attribute name.
86 	 */
87 	protected static final String ATTR_TITLE = "title";
88 	private static final float WEAPON_DEF_MULTIPLIER = 4.0f;
89 	private static final float BOOTS_DEF_MULTIPLIER = 1.0f;
90 	private static final float LEG_DEF_MULTIPLIER = 1.0f;
91 	private static final float HELMET_DEF_MULTIPLIER = 1.0f;
92 	private static final float CLOAK_DEF_MULTIPLIER = 1.5f;
93 	private static final float ARMOR_DEF_MULTIPLIER = 2.0f;
94 	private static final float SHIELD_DEF_MULTIPLIER = 4.0f;
95 	private static final float RING_DEF_MULTIPLIER = 1.0f;
96 	/**
97 	 * To prevent players from gaining attack and defense experience by fighting
98 	 * against very weak creatures, they only gain atk and def xp for so many
99 	 * turns after they have actually been damaged by the enemy. //
100 	 */
101 	private static final int TURNS_WHILE_FIGHT_XP_INCREASES = 12;
102 	/** the logger instance. */
103 	private static final Logger logger = Logger.getLogger(RPEntity.class);
104 	private static Statistics stats;
105 
106 	private String name;
107 	protected int atk;
108 	private int atk_xp;
109 	protected int def;
110 	private int def_xp;
111 	protected int ratk;
112 	private int ratk_xp;
113 	private int base_hp;
114 	private int hp;
115 	protected int lv_cap;
116 	private int xp;
117 	protected int level;
118 	private int mana;
119 	private int base_mana;
120 
121 	private String deathSound;
122 	private String bloodClass;
123 
124 	/** Entity uses a status attack */
125 	protected ImmutableList<StatusAttacker> statusAttackers = ImmutableList.of();
126 	/** a list of current statuses */
127 	protected StatusList statusList;
128 	/**
129 	 * Maps each enemy which has recently damaged this RPEntity to the turn when
130 	 * the last damage has occurred.
131 	 *
132 	 * You only get ATK and DEF experience by fighting against a creature that
133 	 * is in this list.
134 	 */
135 	private final Map<RPEntity, Integer> enemiesThatGiveFightXP;
136 	/** List of all enemies that are currently attacking this entity. */
137 	private final List<Entity> attackSources;
138 	/** the enemy that is currently attacked by this entity. */
139 	private RPEntity attackTarget;
140 
141 	/**
142 	 * Maps each attacker to the sum of hitpoint loss it has caused to this
143 	 * RPEntity.
144 	 */
145 	protected CounterMap<Entity> damageReceived;
146 	protected int totalDamageReceived;
147 
148 	/**
149 	 * To avoid using karma for damage calculations when the natural ability of
150 	 * the fighters would mean they need no luck, we only use karma when the
151 	 * levels are significantly different.
152 	 */
153 
154 	private static final double IGNORE_KARMA_MULTIPLIER = 0.2;
155 
156 	/**
157 	 * Level bonus for defence given to everyone. Prevents newbies killing each
158 	 * other too fast.
159 	 */
160 	private static final double NEWBIE_DEF = 10.0;
161 	/**
162 	 * Armor value of no armor. Prevents unarmored or lightly armored entities
163 	 * from being completely helpless
164 	 */
165 	private static final double SKIN_DEF = 10.0;
166 	/** Adjusts the weight of level. Larger means weight more */
167 	private static final double LEVEL_ATK = 0.03;
168 	/** Adjusts the weight of level. Larger means weight more */
169 	private static final double LEVEL_DEF = 0.03;
170 	/** General parameter for damage. Larger means more damage. */
171 	private static final double WEIGHT_ATK = 8.0;
172 	/** the level where relative damage curves start being linear. */
173 	private static final double EVEN_POINT = 1.2;
174 	/**
175 	 * Steepness of the damage vs level curves. The maximum bonus/penalty with
176 	 * weak enemies
177 	 */
178 	private static final double WEIGHT_EFFECT = 0.5;
179 
180 	/**
181 	 * A helper class for building a size limited list of killer names. If there
182 	 * are more killers than the limit, then "others" is set as the last killer.
183 	 * Only living creatures and online players are included in the killer name
184 	 * list. RPEntities on other zones are not included.
185 	 */
186 	private class KillerList {
187 		/** Maximum amount of killer names. */
188 		private static final int MAX_SIZE = 10;
189 		/** List of killer names. */
190 		private final LinkedList<String> list = new LinkedList<>();
191 		/**
192 		 * A flag for detecting when the killer list has grown over the
193 		 * maximum size.
194 		 */
195 		private boolean more;
196 
197 		/**
198 		 * Add an entity to the killer list.
199 		 *
200 		 * @param e entity
201 		 */
addEntity(Entity e)202 		void addEntity(Entity e) {
203 			if (e instanceof RPEntity) {
204 				// Only list the killers on the zone where the death happened.
205 				if (e.getZone() != getZone()) {
206 					return;
207 				}
208 				// Try to keep player names at the start of the list
209 				if (e instanceof Player) {
210 					if (((Player) e).isDisconnected()) {
211 						return;
212 					}
213 					list.addFirst(e.getName());
214 				} else {
215 					if (((RPEntity) e).getHP() <= 0) {
216 						return;
217 					}
218 					list.add(e.getName());
219 				}
220 			} else {
221 				list.add(e.getName());
222 			}
223 			trim();
224 		}
225 
226 		/**
227 		 * Set the official killer. If the killer was already on the list, move
228 		 * it first. Otherwise prepend the list with the official killer. This
229 		 * means that a creature can appear before players if it is the official
230 		 * killer. Also an item "poison" can be the first on the list this way.
231 		 * (And, as of this writing (2015-04-01) it is the only way anything but
232 		 * RPEntities can be shown on the killer list).
233 		 *
234 		 * @param killer The official killer
235 		 */
setKiller(String killer)236 		void setKiller(String killer) {
237 			if (list.contains(killer)) {
238 				list.remove(killer);
239 				list.addFirst(killer);
240 			} else {
241 				list.addFirst(killer);
242 				trim();
243 			}
244 		}
245 
246 		/**
247 		 * Keep the name list at most {@link #MAX_SIZE}.
248 		 */
trim()249 		private void trim() {
250 			if (list.size() > MAX_SIZE) {
251 				list.remove(list.size() - 1);
252 				more = true;
253 			}
254 		}
255 
256 		/**
257 		 * Get the name list of the added entities.
258 		 *
259 		 * @return name list.
260 		 */
asList()261 		List<String> asList() {
262 			if (more) {
263 				list.set(list.size() - 1, "others");
264 			}
265 			return Collections.unmodifiableList(list);
266 		}
267 	}
268 
269 	@Override
handlePortal(final Portal portal)270 	protected boolean handlePortal(final Portal portal) {
271 		if (isZoneChangeAllowed()) {
272 			if (logger.isDebugEnabled() || Testing.DEBUG) {
273 				logger.debug("Using portal " + portal);
274 			}
275 
276 			return portal.onUsed(this);
277 		}
278 		return super.handlePortal(portal);
279 	}
280 
generateRPClass()281     public static void generateRPClass() {
282         try {
283             stats = Statistics.getStatistics();
284             RPEntityRPClass.generateRPClass(ATTR_TITLE);
285         } catch (final SyntaxException e) {
286             logger.error("cannot generateRPClass", e);
287         }
288     }
289 
RPEntity(final RPObject object)290 	public RPEntity(final RPObject object) {
291 		super(object);
292 		attackSources = new ArrayList<>();
293 		damageReceived = new CounterMap<>(true);
294 		enemiesThatGiveFightXP = new WeakHashMap<>();
295 		totalDamageReceived = 0;
296 	}
297 
RPEntity()298 	public RPEntity() {
299 		super();
300 		attackSources = new ArrayList<>();
301 		damageReceived = new CounterMap<>(true);
302 		enemiesThatGiveFightXP = new WeakHashMap<>();
303 		totalDamageReceived = 0;
304 	}
305 
306 	/**
307 	 * Give the player some karma (good or bad).
308 	 *
309 	 * @param karma
310 	 *            An amount of karma to add/subtract.
311 	 */
addKarma(final double karma)312 	public void addKarma(final double karma) {
313 		// No nothing
314 	}
315 
316 	/**
317 	 * Get the current amount of karma.
318 	 *
319 	 * @return The current amount of karma.
320 	 *
321 	 * @see #addKarma(double)
322 	 */
getKarma()323 	public double getKarma() {
324 		// No karma (yet)
325 		return 0.0;
326 	}
327 
328 	/**
329 	 * Get some of the player's karma. A positive value indicates good
330 	 * luck/energy. A negative value indicates bad luck/energy. A value of zero
331 	 * should cause no change on an action or outcome.
332 	 *
333 	 * @param scale
334 	 *            A positive number.
335 	 *
336 	 * @return A number between -scale and scale.
337 	 */
useKarma(final double scale)338 	public double useKarma(final double scale) {
339 		// No impact
340 		return 0.0;
341 	}
342 
343 	/**
344 	 * Get some of the player's karma. A positive value indicates good
345 	 * luck/energy. A negative value indicates bad luck/energy. A value of zero
346 	 * should cause no change on an action or outcome.
347 	 *
348 	 * @param negLimit
349 	 *            The lowest negative value returned.
350 	 * @param posLimit
351 	 *            The highest positive value returned.
352 	 *
353 	 * @return A number within negLimit &lt;= 0 &lt;= posLimit.
354 	 */
useKarma(final double negLimit, final double posLimit)355 	public double useKarma(final double negLimit, final double posLimit) {
356 		// No impact
357 		return 0.0;
358 	}
359 
360 	/**
361 	 * Use some of the player's karma. A positive value indicates good
362 	 * luck/energy. A negative value indicates bad luck/energy. A value of zero
363 	 * should cause no change on an action or outcome.
364 	 *
365 	 * @param negLimit
366 	 *            The lowest negative value returned.
367 	 * @param posLimit
368 	 *            The highest positive value returned.
369 	 * @param granularity
370 	 *            The amount that any extracted karma is a multiple of.
371 	 *
372 	 * @return A number within negLimit &lt;= 0 &lt;= posLimit.
373 	 */
useKarma(final double negLimit, final double posLimit, final double granularity)374 	public double useKarma(final double negLimit, final double posLimit,
375 			final double granularity) {
376 		// No impact
377 		return 0.0;
378 	}
379 
380 	/**
381 	 * Heal this entity completely.
382 	 *
383 	 * @return The amount actually healed.
384 	 */
heal()385 	public int heal() {
386 		final int baseHP = getBaseHP();
387 		final int given = baseHP - getHP();
388 
389 		if (given != 0) {
390 			put("heal", given);
391 			setHP(baseHP);
392 		}
393 
394 		return given;
395 	}
396 
397 	/**
398 	 * Heal this entity.
399 	 *
400 	 * @param amount
401 	 *            The [maximum] amount to heal by.
402 	 *
403 	 * @return The amount actually healed.
404 	 */
heal(final int amount)405 	public int heal(final int amount) {
406 		return heal(amount, false);
407 	}
408 
409 	/**
410 	 * Heal this entity.
411 	 *
412 	 * @param amount
413 	 *            The [maximum] amount to heal by.
414 	 * @param tell
415 	 *            Whether to tell the entity they've been healed.
416 	 *
417 	 * @return The amount actually healed.
418 	 */
heal(final int amount, final boolean tell)419 	public int heal(final int amount, final boolean tell) {
420 		int tempHp = getHP();
421 		int given = 0;
422 
423 		// Avoid creating zombies out of dead creatures
424 		if (tempHp > 0) {
425 			given = Math.min(amount, getBaseHP() - tempHp);
426 
427 			if (given != 0) {
428 				tempHp += given;
429 
430 				if (tell) {
431 					put("heal", given);
432 				}
433 
434 				setHP(tempHp);
435 			}
436 		}
437 
438 		return given;
439 	}
440 
441 	/**
442 	 * Give mana to the entity.
443 	 *
444 	 * @param mana
445 	 * 			The amount of mana to add/substract.
446 	 * @param tell
447 	 * 			Whether to tell the entity that mana has been added.
448 	 *
449 	 * @return Amount of mana actually refilled.
450 	 */
addMana(int mana, boolean tell)451 	public int addMana(int mana, boolean tell) {
452 		int old_mana = getMana();
453 		int new_mana = old_mana + mana;
454 		int given = 0;
455 
456 		// no negative mana
457 		new_mana = Math.max(new_mana, 0);
458 
459 		// maximum is base_mana
460 		new_mana = Math.min(new_mana, getBaseMana());
461 
462 		given = new_mana - old_mana;
463 
464 		if(tell) {
465 			//TODO: Add notification for increased mana
466 		}
467 
468 		setMana(new_mana);
469 
470 		return given;
471 	}
472 
473 	@Override
update()474 	public void update() {
475 		super.update();
476 
477 		if (has("name")) {
478 			final String newName = get("name");
479 			registerNewName(newName, name);
480 			name = newName;
481 		}
482 
483 		if (has("atk_xp")) {
484 			atk_xp = getInt("atk_xp");
485 			setAtkXpInternal(atk_xp, false);
486 		}
487 
488 		if (has("def_xp")) {
489 			def_xp = getInt("def_xp");
490 			setDefXpInternal(def_xp, false);
491 		}
492 
493 		if (Testing.COMBAT && has("ratk_xp")) {
494 			ratk_xp = getInt("ratk_xp");
495 			setRatkXPInternal(ratk_xp, false);
496 		}
497 
498 		if (has("base_hp")) {
499 			base_hp = getInt("base_hp");
500 		}
501 		if (has("hp")) {
502 			hp = getInt("hp");
503 		}
504 
505 		if (has("lv_cap")) {
506 			lv_cap = getInt("lv_cap");
507 		}
508 		if (has("level")) {
509 			level = getInt("level");
510 		}
511 		if (has("xp")) {
512 			xp = getInt("xp");
513 		}
514 		if (has("mana")) {
515 			mana = getInt("mana");
516 		}
517 		if (has("base_mana")) {
518 			base_mana = getInt("base_mana");
519 		}
520 		if (has("base_speed")) {
521 			setBaseSpeed(getDouble("base_speed"));
522 		}
523 	}
524 
525 	/**
526 	 * Register the new name in the conversation parser word list.
527 	 *
528 	 * @param newName
529 	 * @param oldName
530 	 */
registerNewName(final String newName, final String oldName)531 	private static void registerNewName(final String newName, final String oldName) {
532 		if ((oldName != null) && !oldName.equals(newName)) {
533 			WordList.getInstance().unregisterSubjectName(oldName);
534 		}
535 
536 		if ((oldName == null) || !oldName.equals(newName)) {
537 			WordList.getInstance().registerSubjectName(newName);
538 		}
539 	}
540 
541 	/**
542 	 * Is called when this has hit the given defender. Determines how much
543 	 * hitpoints the defender will lose, based on this's ATK experience and
544 	 * weapon(s), the defender's DEF experience and defensive items, and a
545 	 * random generator.
546 	 *
547 	 * @param defender
548 	 *            The defender.
549 	 * @param attackingWeaponsValue
550 	 * 			  ATK-value of all attacking weapons/spells
551 	 * @param damageType nature of damage
552 	 * @param isRanged <code>true</code> if this is a ranged attack, otherwise
553 	 * 	<code>false</code>
554 	 * @param maxRange maximum range of a ranged attack
555 	 *
556 	 * @return The number of hitpoints that the target should lose. 0 if the
557 	 *         attack was completely blocked by the defender.
558 	 */
damageDone(RPEntity defender, double attackingWeaponsValue, Nature damageType, boolean isRanged, int maxRange)559 	int damageDone(RPEntity defender, double attackingWeaponsValue, Nature damageType,
560 			boolean isRanged, int maxRange) {
561 		// Don't start from 0 to mitigate weird behaviour at very low levels
562 		final int effectiveAttackerLevel = getLevel() + 5;
563 		final int effectiveDefenderLevel = defender.getLevel() + 5;
564 
565 		// Defending side
566 		final double armor = defender.getItemDef();
567 		final int targetDef = defender.getCappedDef();
568 		// Even strong players are vulnerable without any armor.
569 		// Armor def gets much higher with high level players unlike
570 		// weapon atk, so it can not be treated similarly. Using geometric
571 		// / mean to balance things a bit.
572 		final double maxDefence = Math.sqrt(targetDef * (SKIN_DEF + armor))
573 				* (NEWBIE_DEF + LEVEL_DEF * effectiveDefenderLevel);
574 
575 		double defence = Rand.rand() * maxDefence;
576 		/*
577 		 * Account for karma (+/-10%) But, the defender doesn't need luck to
578 		 * help him defend if he's a much higher level than this attacker
579 		 */
580 		final int levelDifferenceToNotNeedKarmaDefending = (int) (IGNORE_KARMA_MULTIPLIER * defender.getLevel());
581 
582 		// this attribute determines how karma is used in combat
583 		String karmaMode = null;
584 		if (defender.has(COMBAT_KARMA)) {
585 			karmaMode = defender.get(COMBAT_KARMA);
586 		}
587 
588 		boolean useKarma = false;
589 		if (karmaMode == null || karmaMode.equals(KARMA_SETTINGS.get(1))) {
590 			if (!(effectiveDefenderLevel - levelDifferenceToNotNeedKarmaDefending  > effectiveAttackerLevel)) {
591 				useKarma = true;
592 			}
593 		} else if (karmaMode.equals(KARMA_SETTINGS.get(2))) {
594 			useKarma = true;
595 		}
596 
597 		// using karma here decreases damage done by enemy
598 		if (useKarma) {
599 			defence += defence * defender.useKarma(0.1);
600 		}
601 
602 		/* Attacking with ranged weapon uses a separate strength value.
603 		 *
604 		 * XXX: atkStrength never used outside of debugger.
605 		 */
606 		final int atkStrength, sourceAtk;
607 		if (Testing.COMBAT && isRanged) {
608 			atkStrength = this.getRatk();
609 			sourceAtk = this.getCappedRatk();
610 		} else {
611 			atkStrength = this.getAtk();
612 			sourceAtk = this.getCappedAtk();
613 		}
614 
615 		// Attacking
616 		if (logger.isDebugEnabled() || Testing.DEBUG) {
617 			logger.debug("attacker has " + atkStrength + " (" + getCappedAtk()
618 					+ ") and uses a weapon of " + getItemAtk());
619 		}
620 
621 		// Make fast weapons efficient against weak enemies, and heavy
622 		// better against strong enemies.
623 		// Half a parabola; desceding for rate < 5; ascending for > 5
624 		double speedEffect = 1.0;
625 		if (effectiveDefenderLevel < EVEN_POINT * effectiveAttackerLevel) {
626 			final double levelPart = 1.0 - effectiveDefenderLevel
627 					/ (EVEN_POINT * effectiveAttackerLevel);
628 			// Gets values -1 at rate = 1, 0 at rate = 5,
629 			// and approaches 1 when rate approaches infinity.
630 			// We can't use a much simpler function as long as we need
631 			// to deal with open ended rate values.
632 			final double speedPart = 1 - 8 / (getAttackRate() + 3.0);
633 
634 			speedEffect = 1.0 - WEIGHT_EFFECT * speedPart * levelPart
635 					* levelPart;
636 		}
637 
638 		final double weaponComponent = 1.0 + attackingWeaponsValue;
639 		// XXX: Is correct to use sourceAtk here instead of atkStrength?
640 		final double maxAttack = sourceAtk * weaponComponent
641 				* (1 + LEVEL_ATK * effectiveAttackerLevel) * speedEffect;
642 		double attack = Rand.rand() * maxAttack;
643 
644 		/*
645 		 * Account for karma (+/-10%) But, don't need luck to help you attack if
646 		 * you're a much higher level than what you attack
647 		 */
648 		final int levelDifferenceToNotNeedKarmaAttacking = (int) (IGNORE_KARMA_MULTIPLIER * getLevel());
649 
650 		karmaMode = null;
651 		if (this.has(COMBAT_KARMA)) {
652 			karmaMode = this.get(COMBAT_KARMA);
653 		}
654 
655 		useKarma = false;
656 		if (karmaMode == null || karmaMode.equals(KARMA_SETTINGS.get(1))) {
657 			if (!(effectiveAttackerLevel - levelDifferenceToNotNeedKarmaAttacking > effectiveDefenderLevel)) {
658 				useKarma = true;
659 			}
660 		} else if (karmaMode.equals(KARMA_SETTINGS.get(2))) {
661 			useKarma = true;
662 		}
663 
664 		// using karma here increases damage to enemy
665 		if (useKarma) {
666 			attack += attack * useKarma(0.1);
667 		}
668 
669 		if (logger.isDebugEnabled() || Testing.DEBUG) {
670 			logger.debug("DEF MAX: " + maxDefence + "\t DEF VALUE: " + defence);
671 		}
672 
673 		// Apply defense and damage type effect
674 		int damage = (int) (defender.getSusceptibility(damageType)
675 				* (WEIGHT_ATK * attack - defence) / maxDefence);
676 
677 		/* FIXME: Can argument be removed and just use
678 		 *        RPEntity.usingRangedAttack() here?
679 		 */
680 		if (isRanged) {
681 			// The attacker is attacking either using a range weapon with
682 			// ammunition such as a bow and arrows, or a missile such as a
683 			// spear.
684 			damage = applyDistanceAttackModifiers(damage,
685 					squaredDistance(defender), maxRange);
686 		}
687 
688 		return damage;
689 	}
690 
691 	/**
692 	 * Is called when this has hit the given defender. Determines how much
693 	 * hitpoints the defender will lose, based on this's ATK experience and
694 	 * weapon(s), the defender's DEF experience and defensive items, and a
695 	 * random generator.
696 	 *
697 	 * @param defender
698 	 *            The defender.
699 	 * @param attackingWeaponsValue
700 	 * 			  ATK-value of all attacking weapons/spells
701 	 * @param damageType nature of damage
702 	 * @return The number of hitpoints that the target should lose. 0 if the
703 	 *         attack was completely blocked by the defender.
704 	 */
damageDone(final RPEntity defender, double attackingWeaponsValue, Nature damageType)705 	public int damageDone(final RPEntity defender, double attackingWeaponsValue, Nature damageType) {
706 		final int maxRange = getMaxRangeForArcher();
707 		boolean isRanged = ((maxRange > 0) && canDoRangeAttack(defender, maxRange));
708 
709 		return damageDone(defender, attackingWeaponsValue, damageType, isRanged, maxRange);
710 	}
711 
712 	/**
713 	 * Calculates the damage that will be done in a distance attack (bow and
714 	 * arrows, spear, etc.).
715 	 *
716 	 * @param damage
717 	 *            The damage that would have been done if there would be no
718 	 *            modifiers for distance attacks.
719 	 * @param squareDistance
720 	 *            the distance
721 	 * @param maxrange maximum attack range
722 	 * @return The damage that will be done with the distance attack.
723 	 */
applyDistanceAttackModifiers(final int damage, final double squareDistance, final double maxrange)724 	public static int applyDistanceAttackModifiers(final int damage,
725 			final double squareDistance, final double maxrange) {
726 		final double maxRangeSquared = maxrange * maxrange;
727 		if (maxRangeSquared < squareDistance) {
728 			return 0;
729 		} else if (squareDistance == 0) {
730 			// as a special case, make archers switch to melee when the enemy is
731 			// next to them
732 			return (int) (0.8 * damage);
733 		}
734 
735 		final double outOfRange = maxrange + 1;
736 		final double distance = Math.sqrt(squareDistance);
737 
738 		// a downward parabola with zero points at 0 and outOfRange
739 		return (int) (damage * ((distance * 4) / outOfRange - 4
740 				* squareDistance / (outOfRange * outOfRange)));
741 	}
742 
743 	/**
744 	 * Set the entity's name.
745 	 *
746 	 * @param name
747 	 *            The new name.
748 	 */
setName(final String name)749 	public void setName(final String name) {
750 		registerNewName(name, this.name);
751 
752 		this.name = name;
753 		put("name", name);
754 	}
755 
756 	/**
757 	 * Get the entity's name.
758 	 *
759 	 * @return The entity's name.
760 	 */
761 	@Override
getName()762 	public String getName() {
763 		if (name != null) {
764 			return name;
765 		}
766 		return super.getName();
767 	}
768 
769 	@Override
onAdded(final StendhalRPZone zone)770 	public void onAdded(final StendhalRPZone zone) {
771 		super.onAdded(zone);
772 		this.updateItemAtkDef();
773 	}
774 
775 
setLevel(final int level)776 	public void setLevel(final int level) {
777 		this.level = level;
778 		put("level", level);
779 		this.updateModifiedAttributes();
780 	}
781 
getLevel()782 	public int getLevel() {
783 		return this.level;
784 	}
785 
setAtk(final int atk)786 	public void setAtk(final int atk) {
787 		setAtkInternal(atk, true);
788 	}
789 
setAtkInternal(final int atk, boolean notify)790 	protected void setAtkInternal(final int atk, boolean notify) {
791 		this.atk = atk;
792 		put("atk", atk);  // visible atk
793 		if(notify) {
794 			this.updateModifiedAttributes();
795 		}
796 	}
797 
getAtk()798 	public int getAtk() {
799 		return this.atk;
800 	}
801 
802 	/**
803 	 * gets the capped atk level, which prevent players from training their atk way beyond what is reasonable for their level
804 	 *
805 	 * @return capped atk
806 	 */
getCappedAtk()807 	public int getCappedAtk() {
808 		return this.atk;
809 	}
810 
811 	/**
812 	 * Set attack XP.
813 	 *
814 	 * @param atk the new value
815 	 */
setAtkXP(final int atk)816 	public void setAtkXP(final int atk) {
817 		setAtkXpInternal(atk, true);
818 	}
819 
setAtkXpInternal(final int atk, boolean notify)820 	private void setAtkXpInternal(final int atk, boolean notify) {
821 		this.atk_xp = atk;
822 		put("atk_xp", atk_xp);
823 
824 		// Handle level changes
825 		final int newLevel = Level.getLevel(atk_xp);
826 		final int levels = newLevel - (this.atk - 10);
827 		if (levels != 0) {
828 			setAtkInternal(this.atk + levels, notify);
829 			new GameEvent(getName(), "atk", Integer.toString(getAtk())).raise();
830 		}
831 	}
832 
833 	/**
834 	 * Adjust entity's ATK XP by specified amount.
835 	 *
836 	 * @param xp
837 	 * 		Amount to add.
838 	 */
addAtkXP(final int xp)839 	public void addAtkXP(final int xp) {
840 		setAtkXP(getAtkXP() + xp);
841 	}
842 
getAtkXP()843 	public int getAtkXP() {
844 		return atk_xp;
845 	}
846 
847 	/**
848 	 * Increase attack XP by 1.
849 	 */
incAtkXP()850 	public void incAtkXP() {
851 		setAtkXP(atk_xp + 1);
852 	}
853 
setDef(final int def)854 	public void setDef(final int def) {
855 		setDefInternal(def, true);
856 	}
857 
setDefInternal(final int def, boolean notify)858 	protected void setDefInternal(final int def, boolean notify) {
859 		this.def = def;
860 		put("def", def);  // visible def
861 		if(notify) {
862 			this.updateModifiedAttributes();
863 		}
864 	}
865 
getDef()866 	public int getDef() {
867 		return this.def;
868 	}
869 
870 	/**
871 	 * gets the capped def level, which prevent players from training their def way beyond what is reasonable for their level
872 	 *
873 	 * @return capped def
874 	 */
getCappedDef()875 	public int getCappedDef() {
876 		return this.def;
877 	}
878 
879 	/**
880 	 * Set defense XP.
881 	 *
882 	 * @param defXp the new value
883 	 */
setDefXP(final int defXp)884 	public void setDefXP(final int defXp) {
885 		setDefXpInternal(defXp, true);
886 	}
887 
setDefXpInternal(final int defXp, boolean notify)888 	private void setDefXpInternal(final int defXp, boolean notify) {
889 		this.def_xp = defXp;
890 		put("def_xp", def_xp);
891 
892 		// Handle level changes
893 		final int newLevel = Level.getLevel(def_xp);
894 		final int levels = newLevel - (this.def - 10);
895 		if (levels != 0) {
896 			setDefInternal(this.def + levels, notify);
897 			new GameEvent(getName(), "def", Integer.toString(this.def)).raise();
898 		}
899 	}
900 
901 	/**
902 	 * Adjust entity's DEF XP by specified amount.
903 	 *
904 	 * @param xp
905 	 * 		Amount to add.
906 	 */
addDefXP(final int xp)907 	public void addDefXP(final int xp) {
908 		setDefXP(getDefXP() + xp);
909 	}
910 
getDefXP()911 	public int getDefXP() {
912 		return def_xp;
913 	}
914 
915 	/**
916 	 * Increase defense XP by 1.
917 	 */
incDefXP()918 	public void incDefXP() {
919 		setDefXP(def_xp + 1);
920 	}
921 
922 
923 /* ### --- START RANGED --- ### */
924 
925 	/**
926 	 * Set the value of the entity's ranged attack level.
927 	 *
928 	 * @param ratk
929 	 * 		Integer value representing new ranged attack level
930 	 */
setRatk(final int ratk)931 	public void setRatk(final int ratk) {
932 		setRatkInternal(ratk, true);
933 	}
934 
935 	/**
936 	 * Set the entity's ranged attack level.
937 	 *
938 	 * @param ratk
939 	 * 		Integer value representing new ranged attack level
940 	 * @param notify
941 	 * 		Update stat in real-time
942 	 */
setRatkInternal(final int ratk, boolean notify)943 	protected void setRatkInternal(final int ratk, boolean notify) {
944 		this.ratk = ratk;
945 		put("ratk", ratk);  // visible ratk
946 		if(notify) {
947 			this.updateModifiedAttributes();
948 		}
949 	}
950 
951 	/**
952 	 * Gets the entity's current ranged attack level.
953 	 *
954 	 * @return
955 	 * 		Integer value of ranged attack level
956 	 */
getRatk()957 	public int getRatk() {
958 		return this.ratk;
959 	}
960 
961 	/**
962 	 * gets the capped ranged attack level which prevents players from training
963 	 * ratk way beyond what is reasonable for their level.
964 	 *
965 	 * @return
966 	 * 		The maximum value player's ranged attack level can be at current
967 	 * 		level
968 	 */
getCappedRatk()969 	public int getCappedRatk() {
970 		return this.ratk;
971 	}
972 
973 	/**
974 	 * Sets the entity's ranged attack experience.
975 	 *
976 	 * @param ratkXP
977 	 * 		Integer value of the target experience
978 	 */
setRatkXP(final int ratkXP)979 	public void setRatkXP(final int ratkXP) {
980 		setRatkXPInternal(ratkXP, true);
981 	}
982 
983 	/**
984 	 * Sets the entity's ranged attack experience.
985 	 *
986 	 * @param ratkXP
987 	 * 		Integer value of the target experience
988 	 * @param notify
989 	 * 		Update ranged attack experience in real-time
990 	 */
setRatkXPInternal(final int ratkXP, boolean notify)991 	protected void setRatkXPInternal(final int ratkXP, boolean notify) {
992 		this.ratk_xp = ratkXP;
993 		put("ratk_xp", ratk_xp);
994 
995 		// Handle level changes
996 		final int newLevel = Level.getLevel(ratk_xp);
997 		final int levels = newLevel - (this.ratk - 10);
998 
999 		// In case we level up several levels at a single time.
1000 		if (levels != 0) {
1001 			setRatkInternal(this.ratk + levels, notify);
1002 			new GameEvent(getName(), "ratk", Integer.toString(this.ratk)).raise();
1003 		}
1004 	}
1005 
1006 	/**
1007 	 * Adjust entity's RATK XP by specified amount.
1008 	 *
1009 	 * @param xp
1010 	 * 		Amount to add.
1011 	 */
addRatkXP(final int xp)1012 	public void addRatkXP(final int xp) {
1013 		setRatkXP(getRatkXP() + xp);
1014 	}
1015 
1016 	/**
1017 	 * Get's the entity's current ranged attack experience.
1018 	 *
1019 	 * @return
1020 	 * 		Integer representation of current experience
1021 	 */
getRatkXP()1022 	public int getRatkXP() {
1023 		return ratk_xp;
1024 	}
1025 
1026 	/**
1027 	 * Increase ranged XP by 1.
1028 	 */
incRatkXP()1029 	public void incRatkXP() {
1030 		setRatkXP(ratk_xp + 1);
1031 	}
1032 
1033 /* ### --- END RANGED --- ### */
1034 
1035 
1036 	/**
1037 	 * Set the base and current HP.
1038 	 *
1039 	 * @param hp
1040 	 *            The HP to set.
1041 	 */
initHP(final int hp)1042 	public void initHP(final int hp) {
1043 		setBaseHP(hp);
1044 		setHP(hp);
1045 	}
1046 
1047 	/**
1048 	 * Set the base HP.
1049 	 *
1050 	 * @param newhp
1051 	 *            The base HP to set.
1052 	 */
setBaseHP(final int newhp)1053 	public void setBaseHP(final int newhp) {
1054 		this.base_hp = newhp;
1055 		try {
1056 			put("base_hp", newhp);
1057 		} catch (IllegalArgumentException e) {
1058 			logger.error("Failed to set base HP to " + newhp + ". Entity was: " + this, e);
1059 		}
1060 		this.updateModifiedAttributes();
1061 	}
1062 
1063 	/**
1064 	 * Get the base HP.
1065 	 *
1066 	 * @return The current HP.
1067 	 */
getBaseHP()1068 	public int getBaseHP() {
1069 		return this.base_hp;
1070 	}
1071 
1072 	/**
1073 	 * Set the HP. <br>
1074 	 * DO NOT USE THIS UNLESS YOU REALLY KNOW WHAT YOU ARE DOING. <br>
1075 	 * Use the appropriate damage(), and heal() methods instead.
1076 	 *
1077 	 * @param hp
1078 	 *            The HP to set.
1079 	 */
setHP(final int hp)1080 	public void setHP(final int hp) {
1081 		setHpInternal(hp, true);
1082 	}
1083 
setHpInternal(final int hp, final boolean notify)1084 	private void setHpInternal(final int hp, final boolean notify) {
1085 		this.hp = hp;
1086 		try {
1087 			put("hp", hp);
1088 		} catch (IllegalArgumentException e) {
1089 			logger.error("Failed to set HP to " + hp + ". Entity was: " + this, e);
1090 		}
1091 		if(notify) {
1092 			this.updateModifiedAttributes();
1093 		}
1094 	}
1095 
1096 	/**
1097 	 * Get the current HP.
1098 	 *
1099 	 * @return The current HP.
1100 	 */
getHP()1101 	public int getHP() {
1102 		return this.hp;
1103 	}
1104 
1105 	/**
1106 	 * Get the lv_cap.
1107 	 *
1108 	 * @return The current lv_cap.
1109 	 */
getLVCap()1110 	public int getLVCap() {
1111 		return this.lv_cap;
1112 	}
1113 
1114 	/**
1115 	 * Gets the mana (magic).
1116 	 *
1117 	 * @return mana
1118 	 */
getMana()1119 	public int getMana() {
1120 		return this.mana;
1121 	}
1122 
1123 	/**
1124 	 * Gets the base mana (like base_hp).
1125 	 *
1126 	 * @return base mana
1127 	 */
getBaseMana()1128 	public int getBaseMana() {
1129 		return this.base_mana;
1130 	}
1131 
1132 	/**
1133 	 * Sets the available mana.
1134 	 *
1135 	 * @param newMana
1136 	 *            new amount of mana
1137 	 */
setMana(final int newMana)1138 	public void setMana(final int newMana) {
1139 		setManaInternal(newMana, true);
1140 	}
1141 
setManaInternal(final int newMana, boolean notify)1142 	private void setManaInternal(final int newMana, boolean notify) {
1143 		mana = newMana;
1144 		put("mana", newMana);
1145 		if(notify) {
1146 			this.updateModifiedAttributes();
1147 		}
1148 	}
1149 
1150 	/**
1151 	 * Sets the base mana (like base_hp).
1152 	 *
1153 	 * @param newBaseMana
1154 	 *            new amount of base mana
1155 	 */
setBaseMana(final int newBaseMana)1156 	public void setBaseMana(final int newBaseMana) {
1157 		base_mana = newBaseMana;
1158 		put("base_mana", newBaseMana);
1159 		this.updateModifiedAttributes();
1160 	}
1161 
1162 	/**
1163 	 * adds to base mana (like addXP).
1164 	 *
1165 	 * @param newBaseMana
1166 	 *            amount of base mana to be added
1167 	 */
addBaseMana(final int newBaseMana)1168 	public void addBaseMana(final int newBaseMana) {
1169 		base_mana += newBaseMana;
1170 		put("base_mana", base_mana);
1171 	}
1172 
setLVCap(final int newLVCap)1173 	public void setLVCap(final int newLVCap) {
1174 		lv_cap = newLVCap;
1175 		put("lv_cap", newLVCap);
1176 		this.updateModifiedAttributes();
1177 	}
1178 
setXP(final int newxp)1179 	public final void setXP(final int newxp) {
1180 		if (newxp < 0) {
1181 			return;
1182 		}
1183 		this.xp = newxp;
1184 		put("xp", xp);
1185 	}
1186 
subXP(final int newxp)1187 	public void subXP(final int newxp) {
1188 		addXP(-newxp);
1189 	}
1190 
addXP(final int newxp)1191 	public void addXP(final int newxp) {
1192 		if (Integer.MAX_VALUE - this.xp <= newxp) {
1193 			return;
1194 		}
1195 		if (newxp == 0) {
1196 			return;
1197 		}
1198 
1199 		// Increment experience points
1200 		this.xp += newxp;
1201 		put("xp", xp);
1202 		String[] params = { Integer.toString(newxp) };
1203 
1204 		new GameEvent(getName(), "added xp", params).raise();
1205 		new GameEvent(getName(), "xp", String.valueOf(xp)).raise();
1206 
1207 		updateLevel();
1208 	}
1209 
1210 	/**
1211 	 * Change the level to match the XP, if needed.
1212 	 */
updateLevel()1213 	protected void updateLevel() {
1214 		final int newLevel = Level.getLevel(getXP());
1215 		final int oldLevel = has("level") ? getInt("level") : 0;
1216 		final int levels = newLevel - oldLevel;
1217 
1218 		// In case we level up several levels at a single time.
1219 		for (int i = 0; i < Math.abs(levels); i++) {
1220 			setBaseHP(getBaseHP() + (int) Math.signum(levels) * 10);
1221 			setHP(getBaseHP());
1222 			new GameEvent(getName(), "level", Integer.toString(oldLevel+(i+1)*((int) Math.signum(levels)))).raise();
1223 			setLevel(newLevel);
1224 		}
1225 	}
1226 
getXP()1227 	public int getXP() {
1228 		return xp;
1229 	}
1230 
1231 	/**
1232 	 * Get a multiplier for a given damage type when this
1233 	 * entity is damaged.
1234 	 *
1235 	 * @param type Type of the damage
1236 	 * @return damage multiplier
1237 	 */
getSusceptibility(Nature type)1238 	protected double getSusceptibility(Nature type) {
1239 		return 1.0;
1240 	}
1241 
1242 	/**
1243 	 * Get the type of the damage this entity inflicts
1244 	 *
1245 	 * @return type of damage
1246 	 */
getDamageType()1247 	protected Nature getDamageType() {
1248 		return Nature.CUT;
1249 	}
1250 
1251 	/**
1252 	 * Get the nature of the damage the entity inflicts in ranged attacks.
1253 	 *
1254 	 * @return type of damage
1255 	 */
getRangedDamageType()1256 	protected Nature getRangedDamageType() {
1257 		/*
1258 		 * Default to the same as the base damage type. Entities needing more
1259 		 * complicated behavior (ie. fire breathing dragons) should override the
1260 		 * method.
1261 		 */
1262 		return getDamageType();
1263 	}
1264 
1265 	/***************************************************************************
1266 	 * * Attack handling code. * *
1267 	 **************************************************************************/
1268 
1269 	/**
1270 	 * @return true if this RPEntity is attackable.
1271 	 */
isAttackable()1272 	public boolean isAttackable() {
1273 		return true;
1274 	}
1275 
1276 	/**
1277 	 * Modify the entity to order to attack the target entity.
1278 	 *
1279 	 * @param target
1280 	 */
setTarget(final RPEntity target)1281 	public void setTarget(final RPEntity target) {
1282 		put("target", target.getID().getObjectID());
1283 		if (attackTarget != null) {
1284 			attackTarget.attackSources.remove(this);
1285 		}
1286 		attackTarget = target;
1287 	}
1288 
1289 	/** Modify the entity to stop attacking. */
stopAttack()1290 	public void stopAttack() {
1291 		if (has("heal")) {
1292 			remove("heal");
1293 		}
1294 		if (has("target")) {
1295 			remove("target");
1296 		}
1297 
1298 		if (attackTarget != null) {
1299 			attackTarget.attackSources.remove(this);
1300 
1301 			// remove opponent here to avoid memory leak
1302 			enemiesThatGiveFightXP.remove(attackTarget);
1303 
1304 			attackTarget = null;
1305 		}
1306 	}
1307 
getsFightXpFrom(final RPEntity enemy)1308 	public boolean getsFightXpFrom(final RPEntity enemy) {
1309 		if (enemy instanceof TrainingDummy) {
1310 			// training dummies always give fight XP
1311 			return true;
1312 		}
1313 
1314 		final Integer turnWhenLastDamaged = enemiesThatGiveFightXP.get(enemy);
1315 		if (turnWhenLastDamaged == null) {
1316 			return false;
1317 		}
1318 		final int currentTurn = SingletonRepository.getRuleProcessor()
1319 				.getTurn();
1320 		if (currentTurn - turnWhenLastDamaged > TURNS_WHILE_FIGHT_XP_INCREASES) {
1321 			enemiesThatGiveFightXP.remove(enemy);
1322 			return false;
1323 		}
1324 		return true;
1325 	}
1326 
stopAttacking(final Entity attacker)1327 	public void stopAttacking(final Entity attacker) {
1328 		if (attacker.has("target")) {
1329 			attacker.remove("target");
1330 		}
1331 	}
1332 
rememberAttacker(final Entity attacker)1333 	public void rememberAttacker(final Entity attacker) {
1334 		if (!attackSources.contains(attacker)) {
1335 			attackSources.add(attacker);
1336 		}
1337 	}
1338 
1339 	/**
1340 	 * sets the blood class
1341 	 *
1342 	 * @param name name of blood class
1343 	 */
setBlood(final String name)1344 	public final void setBlood(final String name) {
1345 		this.bloodClass = name;
1346 	}
1347 
1348 	/**
1349 	 * gets the name of the blood class
1350 	 *
1351 	 * @return bloodClass or <code>null</code>
1352 	 */
getBloodClass()1353 	public final String getBloodClass() {
1354 		return this.bloodClass;
1355 	}
1356 
1357 	/**
1358 	 * Creates a blood pool on the ground under this entity, but only if there
1359 	 * isn't a blood pool at that position already.
1360 	 */
bleedOnGround()1361 	private void bleedOnGround() {
1362 		final Rectangle2D rect = getArea();
1363 		final int bx = (int) rect.getX();
1364 		final int by = (int) rect.getY();
1365 		final StendhalRPZone zone = getZone();
1366 
1367 		if (zone.getBlood(bx, by) == null) {
1368 			final Blood blood = new Blood(bloodClass);
1369 			blood.setPosition(bx, by);
1370 
1371 			zone.add(blood);
1372 		}
1373 	}
1374 
1375 	/**
1376 	 * return list of all droppable items in entity's hands.
1377 	 *
1378 	 * currently only considers items in hands.  no other part of body
1379 	 *
1380 	 * currently, there is only one type of droppable item - CaptureTheFlagFlag.
1381 	 *     need some more general solution
1382 	 *
1383 	 * @return list of droppable items.  returns null if no droppable items found
1384 	 */
getDroppables()1385 	public List<Item> getDroppables() {
1386 		final String[] slots = { "lhand", "rhand" };
1387 		Stream<Item> items = Stream.of(slots).map(this::getSlot).filter(Objects::nonNull).flatMap(this::slotStream);
1388 		return items.filter(CaptureTheFlagFlag.class::isInstance).collect(Collectors.toList());
1389 	}
1390 
1391 	/**
1392 	 * Drop specified item from entity's equipment
1393 	 *
1394 	 * note: seems like this.drop(droppable) should work, but
1395 	 *       the item just disappears - does not end up on ground.
1396 	 *
1397 	 * TODO: probably need to refactor this in to the general drop system
1398 	 *       (maybe fixing some of the other code paths)
1399 	 *
1400 	 * @param droppable item to be dropped
1401 	 */
dropDroppableItem(Item droppable)1402 	public void dropDroppableItem(Item droppable) {
1403 
1404 		// note: this.drop() does not do all necessary operations -
1405 		//       item disappears from hand, but disappears competely
1406 
1407 		Player    player = (Player) this;
1408 		RPObject  parent = droppable.getContainer();
1409 		RPAction  action = new RPAction();
1410 
1411 		action.put("type",                        "drop");
1412 		action.put("baseitem",                    droppable.getID().getObjectID());
1413 		action.put(EquipActionConsts.BASE_OBJECT, parent.getID().getObjectID());
1414 		action.put(EquipActionConsts.BASE_SLOT,   droppable.getContainerSlot().getName());
1415 
1416 		// TODO: better to drop "behind" the player, if they have been running
1417 		action.put("x", this.getX());
1418 		action.put("y", this.getY() + 1);
1419 
1420 		DropAction dropAction = new DropAction();
1421 		dropAction.onAction(player, action);
1422 
1423 		// TODO: send message to player - you dropped ...
1424 
1425 		this.notifyWorldAboutChanges();
1426 	}
1427 
1428 	/**
1429 	 * if defender (this entity) is carrying a droppable item,
1430 	 * then attacker and defender both roll d20, and if attacker
1431 	 * rolls higher, the defender drops the droppable.
1432 	 *
1433 	 * note that separate rolls are performed for each droppable
1434 	 * that the entity is carrying.
1435 	 *
1436 	 * XXX this does not belong here - should be in some Effect framework
1437 	 *
1438 	 * returns string - what happened.  no effect returns null
1439 	 *
1440 	 * @param attacker
1441 	 * @return event description
1442 	 */
maybeDropDroppables(RPEntity attacker)1443 	public String maybeDropDroppables(RPEntity attacker) {
1444 		List<Item> droppables = getDroppables();
1445 		if (droppables.isEmpty()) {
1446 			return null;
1447 		}
1448 
1449 		for (Item droppable : droppables) {
1450 			// roll two dice, tie goes to defender
1451 			//   TODO: integrate skills, ctf atk/def
1452 			int attackerRoll = Rand.roll1D20();
1453 			int defenderRoll = Rand.roll1D20();
1454 
1455 System.out.printf("  drop: %2d %2d\n", attackerRoll, defenderRoll);
1456 
1457 			if (attackerRoll > defenderRoll) {
1458 				this.dropDroppableItem(droppable);
1459 				// XXX get description from droppable - what color, ...
1460 				return "dropped the flag";
1461 			}
1462 		}
1463 		return null;
1464 	}
1465 
1466 	/**
1467 	 * This method is called when this entity has been attacked by Entity
1468 	 * attacker and it has been damaged with damage points.
1469 	 *
1470 	 * @param attacker
1471 	 * @param damage
1472 	 */
onDamaged(final Entity attacker, final int damage)1473 	public void onDamaged(final Entity attacker, final int damage) {
1474 		if (logger.isDebugEnabled() || Testing.DEBUG) {
1475 			logger.debug("Damaged " + damage + " points by " + attacker.getID());
1476 		}
1477 
1478 		bleedOnGround();
1479 		if (attacker instanceof RPEntity) {
1480 			final int currentTurn = SingletonRepository.getRuleProcessor()
1481 					.getTurn();
1482 			enemiesThatGiveFightXP.put((RPEntity) attacker, currentTurn);
1483 		}
1484 
1485 		final int leftHP = getHP() - damage;
1486 
1487 		totalDamageReceived += damage;
1488 
1489 		// remember the damage done so that the attacker can later be rewarded
1490 		// XP etc.
1491 		damageReceived.add(attacker, damage);
1492 
1493 		if (leftHP > 0) {
1494 			setHP(leftHP);
1495 		} else {
1496 			kill(attacker);
1497 		}
1498 
1499 		notifyWorldAboutChanges();
1500 	}
1501 
1502 	/**
1503 	 * Apply damage to this entity. This is normally called from one of the
1504 	 * other damage() methods to account for death.
1505 	 *
1506 	 * @param amount
1507 	 *            The HP to take.
1508 	 *
1509 	 * @return The damage actually taken (in case HP was < amount).
1510 	 */
damage(final int amount)1511 	private int damage(final int amount) {
1512 		int tempHp = getHP();
1513 		final int taken = Math.min(amount, tempHp);
1514 
1515 		tempHp -= taken;
1516 		setHP(tempHp);
1517 
1518 		return taken;
1519 	}
1520 
1521 	/**
1522 	 * Apply damage to this entity, and call onDead() if HP reaches 0.
1523 	 *
1524 	 * @param amount
1525 	 *            The HP to take.
1526 	 * @param attacker
1527 	 *            The attacking entity.
1528 	 *
1529 	 * @return The damage actually taken (in case HP was < amount).
1530 	 */
damage(final int amount, final Killer attacker)1531 	public int damage(final int amount, final Killer attacker) {
1532 		final int taken = damage(amount);
1533 
1534 		if (hp <= 0) {
1535 			onDead(attacker);
1536 		}
1537 
1538 		return taken;
1539 	}
1540 
1541 	/**
1542 	 * Apply damage to this entity, delaying the damage to happen in a turn
1543 	 * notifier. To be used when dying could result in concurrent modification
1544 	 * in the zone's entity list, such as sheep starving. Call onDead() if HP
1545 	 * reaches 0.
1546 	 *
1547 	 * @param amount
1548 	 *            The HP to take.
1549 	 * @param attackerName
1550 	 *            The name of the attacker.
1551 	 */
delayedDamage(final int amount, final String attackerName)1552 	public void delayedDamage(final int amount, final String attackerName) {
1553 		final RPEntity me = this;
1554 		/*
1555 		 * Use a dummy damager rpentity, so that we can follow the
1556 		 * normal code path. Important when dying.
1557 		 */
1558 		final Entity attacker = new RPEntity(this) {
1559 			@Override
1560 			public String getTitle() {
1561 				return attackerName;
1562 			}
1563 
1564 			@Override
1565 			protected void dropItemsOn(Corpse corpse) {
1566 
1567 			}
1568 
1569 			@Override
1570 			public void logic() {
1571 
1572 			}
1573 		};
1574 
1575 		SingletonRepository.getTurnNotifier().notifyInTurns(1, new TurnListener() {
1576 			@Override
1577 			public void onTurnReached(int turn) {
1578 				me.damage(amount, attacker);
1579 			}
1580 		});
1581 	}
1582 
1583 	/**
1584 	 * Kills this RPEntity.
1585 	 *
1586 	 * @param killer
1587 	 *            The killer
1588 	 */
kill(final Entity killer)1589 	private void kill(final Entity killer) {
1590 		setHP(0);
1591 		SingletonRepository.getRuleProcessor().killRPEntity(this, killer);
1592 	}
1593 
1594 	/**
1595 	 * For rewarding killers. Get the entity as a Player, if the entity is a
1596 	 * Player. If the player has logged out, try to get the corresponding online
1597 	 * player.
1598 	 *
1599 	 * @param entity entity to be checked
1600 	 * @return online Player corresponding to the entity, or {@code null} if the
1601 	 * 	entity is not a Player, or if the equivalent player is not online
1602 	 */
entityAsOnlinePlayer(Entity entity)1603 	protected Player entityAsOnlinePlayer(Entity entity) {
1604 		if (!(entity instanceof Player)) {
1605 			return null;
1606 		}
1607 		Player killer = (Player) entity;
1608 		if (killer.isDisconnected()) {
1609 			// Try to get the corresponding online player:
1610 			killer = SingletonRepository.getRuleProcessor().getPlayer(killer.getName());
1611 		}
1612 		return killer;
1613 	}
1614 
entityAsPet(Entity entity)1615 	protected Pet entityAsPet(Entity entity) {
1616 		if (!(entity instanceof Pet)) {
1617 			return null;
1618 		}
1619 		Pet killerPet = (Pet) entity;
1620 		/* isDisconnected is undefined in object Pet;
1621 		if (killer.isDisconnected()) {
1622 			// Try to get the corresponding online player:
1623 			killer = SingletonRepository.getRuleProcessor().getPlayer(killer.getName());
1624 		}
1625 		*/
1626 		return killerPet;
1627 	}
1628 
1629 	/**
1630 	 * Gives XP to every player who has helped killing this RPEntity.
1631 	 *
1632 	 * @param oldXP
1633 	 *            The XP that this RPEntity had before being killed.
1634 	 */
rewardKillers(final int oldXP)1635 	protected void rewardKillers(final int oldXP) {
1636 		final int xpReward = (int) (oldXP * 0.05);
1637 
1638 		for (Entry<Entity, Integer> entry : damageReceived.entrySet()) {
1639 			final int damageDone = entry.getValue();
1640 			if (damageDone == 0) {
1641 				continue;
1642 			}
1643 
1644 			Player killer = entityAsOnlinePlayer(entry.getKey());
1645 			if (killer == null) {
1646 				continue;
1647 			}
1648 
1649 			TutorialNotifier.killedSomething(killer);
1650 
1651 			if (logger.isDebugEnabled() || Testing.DEBUG) {
1652 				final String killName;
1653 				if (killer.has("name")) {
1654 					killName = killer.get("name");
1655 				} else {
1656 					killName = killer.get("type");
1657 				}
1658 
1659 				logger.debug(killName + " did " + damageDone + " of "
1660 						+ totalDamageReceived + ". Reward was " + xpReward);
1661 			}
1662 
1663 			final int xpEarn = (int) (xpReward * ((float) damageDone / (float) totalDamageReceived));
1664 
1665 			if (logger.isDebugEnabled() || Testing.DEBUG) {
1666 				logger.debug("OnDead: " + xpReward + "\t" + damageDone + "\t"
1667 						+ totalDamageReceived + "\t");
1668 			}
1669 
1670 			int reward = xpEarn;
1671 
1672 			// We ensure that the player gets at least 1 experience
1673 			// point, because getting nothing lowers motivation.
1674 			if (reward == 0) {
1675 				reward = 1;
1676 			}
1677 
1678 			killer.addXP(reward);
1679 
1680 			// For some quests etc., it is required that the player kills a
1681 			// certain creature without the help of others.
1682 			// Find out if the player killed this RPEntity on his own, but
1683 			// don't overwrite solo with shared.
1684 			final String killedName = getName();
1685 
1686 			if (killedName == null) {
1687 				logger.warn("This entity returns null as name: " + this);
1688 			} else {
1689 				if (damageDone == totalDamageReceived) {
1690 					killer.setSoloKill(killedName);
1691 				} else {
1692 					killer.setSharedKill(killedName);
1693 				}
1694 			}
1695 
1696 			SingletonRepository.getAchievementNotifier().onKill(killer);
1697 
1698 			killer.notifyWorldAboutChanges();
1699 		}
1700 	}
1701 
1702 	/*
1703 	 * Reward pets who kill enemies.  don't perks like AchievementNotifier that players.
1704 	 */
rewardKillerAnimals(final int oldXP)1705 	protected void rewardKillerAnimals(final int oldXP) {
1706 		if (!System.getProperty("stendhal.petleveling", "false").equals("true")) {
1707 			return;
1708 		}
1709 		final int xpReward = (int) (oldXP * 0.05);
1710 
1711 		for (Entry<Entity, Integer> entry : damageReceived.entrySet()) {
1712 			final int damageDone = entry.getValue();
1713 			if (damageDone == 0) {
1714 				continue;
1715 			}
1716 
1717 			Pet killer = entityAsPet(entry.getKey());
1718 			if (killer == null) {
1719 				continue;
1720 			}
1721 
1722 			if (logger.isDebugEnabled() || Testing.DEBUG) {
1723 				final String killName;
1724 				if (killer.has("name")) {
1725 					killName = killer.get("name");
1726 				} else {
1727 					killName = killer.get("type");
1728 				}
1729 
1730 				logger.debug(killName + " did " + damageDone + " of "
1731 						+ totalDamageReceived + ". Reward was " + xpReward);
1732 			}
1733 
1734 			final int xpEarn = (int) (xpReward * ((float) damageDone / (float) totalDamageReceived));
1735 
1736 			if (logger.isDebugEnabled() || Testing.DEBUG) {
1737 				logger.debug("OnDead: " + xpReward + "\t" + damageDone + "\t"
1738 						+ totalDamageReceived + "\t");
1739 			}
1740 
1741 			int reward = xpEarn;
1742 
1743 			// We ensure it gets at least 1 experience
1744 			// point, because getting nothing lowers motivation.
1745 			if (reward == 0) {
1746 				reward = 1;
1747 			}
1748 
1749 			if (killer.getLevel() >= killer.getLVCap())
1750 			{
1751 				reward = 0;
1752 			}
1753 
1754 			killer.addXP(reward);
1755 
1756 			/*
1757 			// For some quests etc., it is required that the player kills a
1758 			// certain creature without the help of others.
1759 			// Find out if the player killed this RPEntity on his own, but
1760 			// don't overwrite solo with shared.
1761 			final String killedName = getName();
1762 
1763 			if (killedName == null) {
1764 				logger.warn("This entity returns null as name: " + this);
1765 			} else {
1766 				if (damageDone == totalDamageReceived) {
1767 					killer.setSoloKill(killedName);
1768 				} else {
1769 					killer.setSharedKill(killedName);
1770 				}
1771 			}
1772 
1773 			SingletonRepository.getAchievementNotifier().onKill(killer);
1774 			*/
1775 
1776 			killer.notifyWorldAboutChanges();
1777 		}
1778 	}
1779 
1780 	/**
1781 	 * This method is called when the entity has been killed ( hp==0 ).
1782 	 *
1783 	 * @param killer
1784 	 *            The entity who caused the death
1785 	 */
onDead(final Killer killer)1786 	public final void onDead(final Killer killer) {
1787 	    onDead(killer, true);
1788 	}
1789 
1790 	/**
1791 	 * This method is called when this entity has been killed (hp == 0).
1792 	 *
1793 	 * @param killer
1794 	 *            The entity who caused the death, i.e. who did the last hit.
1795 	 * @param remove
1796 	 *            true iff this entity should be removed from the world. For
1797 	 *            almost everything remove is true, but not for the players, who
1798 	 *            are instead moved to afterlife ("reborn").
1799 	 */
onDead(final Killer killer, final boolean remove)1800 	public void onDead(final Killer killer, final boolean remove) {
1801 		StendhalKillLogDAO killLog = DAORegister.get().get(StendhalKillLogDAO.class);
1802 		String killerName = killer.getName();
1803 
1804 		if (killer instanceof RPEntity) {
1805 			new GameEvent(killerName, "killed", this.getName(), killLog.entityToType(killer), killLog.entityToType(this)).raise();
1806 		}
1807 
1808 		DBCommandQueue.get().enqueue(new LogKillEventCommand(this, killer), DBCommandPriority.LOW);
1809 
1810 		die(killer, remove);
1811 	}
1812 
1813 	/**
1814 	 * Build a list of killer names.
1815 	 *
1816 	 * @param killerName The "official" killer. This will be always included in
1817 	 *	the list
1818 	 * @return list of killers
1819 	 */
buildKillerList(String killerName)1820 	private List<String> buildKillerList(String killerName) {
1821 		KillerList killers = new KillerList();
1822 
1823 		for (Entry<Entity, Integer> entry : damageReceived.entrySet()) {
1824 			final int damageDone = entry.getValue();
1825 			if (damageDone == 0) {
1826 				continue;
1827 			}
1828 
1829 			killers.addEntity(entry.getKey());
1830 		}
1831 		if (killerName != null) {
1832 			killers.setKiller(killerName);
1833 		}
1834 		return killers.asList();
1835 	}
1836 
1837 	/**
1838 	 * This method is called when this entity has been killed (hp == 0).
1839 	 *
1840 	 * @param killer the "official" killer
1841 	 * @param remove
1842 	 *            <code>true</code> to remove entity from world.
1843 	 */
die(Killer killer, final boolean remove)1844 	private void die(Killer killer, final boolean remove) {
1845 		StendhalRPZone zone = this.getZone();
1846 		if ((zone == null) || !zone.has(this.getID())) {
1847 			logger.warn("RPEntity died but is not in a zone");
1848 			return;
1849 		}
1850 
1851 		String killerName = killer.getName();
1852 		// Needs to be done while the killer map still has the contents
1853 		List<String> killers = buildKillerList(killerName);
1854 
1855 		final int oldXP = this.getXP();
1856 
1857 		// Establish how much xp points your are rewarded
1858 		// give XP to everyone who helped killing this RPEntity
1859 		rewardKillers(oldXP);
1860 		rewardKillerAnimals(oldXP);
1861 
1862 		if (!(killer instanceof Player) && !(killer instanceof Status) && !(killer instanceof Pet)) {
1863 			/*
1864 			 * Prettify the killer name for the corpse. Should be done only
1865 			 * after the more plain version has been used for the killer list.
1866 			 * Players are unique, so they should not get an article. Also
1867 			 * statuses should not, so that "killed by poison" does not become
1868 			 * "killed by a bottle of poison".
1869 			 */
1870 			killerName = Grammar.a_noun(killerName);
1871 		}
1872 		// Add a corpse
1873 		final Corpse corpse = makeCorpse(killerName);
1874 		damageReceived.clear();
1875 		totalDamageReceived = 0;
1876 
1877 		// Stats about dead
1878 		if (has("name")) {
1879 			stats.add("Killed " + get("name"), 1);
1880 		} else {
1881 			stats.add("Killed " + get("type"), 1);
1882 		}
1883 
1884 		// Add some reward inside the corpse
1885 		dropItemsOn(corpse);
1886 		updateItemAtkDef();
1887 
1888 		// Adding to zone clears events, so the sound needs to be added after that.
1889 		zone.add(corpse);
1890 		if (deathSound != null) {
1891 			corpse.addEvent(new SoundEvent(deathSound, 23, 100, SoundLayer.FIGHTING_NOISE));
1892 			corpse.notifyWorldAboutChanges();
1893 		}
1894 
1895 		StringBuilder deathMessage = new StringBuilder(getName());
1896 		deathMessage.append(" has been killed");
1897 		if (!killers.isEmpty()) {
1898 			deathMessage.append(" by ");
1899 			deathMessage.append(Grammar.enumerateCollection(killers));
1900 		}
1901 		corpse.addEvent(new TextEvent(deathMessage.toString()));
1902 
1903 		// Corpse may want to know who this entity was attacking (RaidCreatureCorpse does),
1904 		// so defer stopping.
1905 		stopAttack();
1906 		if (statusList != null) {
1907 			statusList.removeAll();
1908 		}
1909 		if (remove) {
1910 			zone.remove(this);
1911 		}
1912 	}
1913 
1914 	/**
1915 	 * Make a corpse belonging to this entity
1916 	 *
1917 	 * @param killer Name of the killer
1918 	 * @return The corpse of a dead RPEntity
1919 	 */
makeCorpse(String killer)1920 	protected Corpse makeCorpse(String killer) {
1921 		return new Corpse(this, killer);
1922 	}
1923 
1924 	/**
1925 	 * Get the corpse image name to be used for the entity.
1926 	 * Defaults to a player corpse.
1927 	 *
1928 	 * @return Identification string for corpse. This is the corpse
1929 	 * image shown by the client without the path or file extension.
1930 	 */
getCorpseName()1931 	public String getCorpseName() {
1932 		return "player";
1933 	}
1934 
getHarmlessCorpseName()1935 	public String getHarmlessCorpseName() {
1936 		return "harmless_player";
1937 	}
1938 
getCorpseWidth()1939 	public int getCorpseWidth() {
1940 		return 1;
1941 	}
1942 
getCorpseHeight()1943 	public int getCorpseHeight() {
1944 		return 1;
1945 	}
1946 
dropItemsOn(Corpse corpse)1947 	protected abstract void dropItemsOn(Corpse corpse);
1948 
1949 	/**
1950 	 * Determine if the entity is invisible to creatures.
1951 	 *
1952 	 * @return <code>true</code> if invisible.
1953 	 */
isInvisibleToCreatures()1954 	public boolean isInvisibleToCreatures() {
1955 		return false;
1956 	}
1957 
1958 	/**
1959 	 * Return true if this entity is attacked.
1960 	 *
1961 	 * @return true if no attack sources found
1962 	 */
isAttacked()1963 	public boolean isAttacked() {
1964 		return !attackSources.isEmpty();
1965 	}
1966 
1967 	/**
1968 	 * Returns the Entities that are attacking this character.
1969 	 *
1970 	 * @return list of all attacking entities
1971 	 */
getAttackSources()1972 	public List<Entity> getAttackSources() {
1973 		return attackSources;
1974 	}
1975 
1976 	/**
1977 	 * Returns the RPEntities that are attacking this character.
1978 	 *
1979 	 * @return list of all attacking RPEntities
1980 	 */
getAttackingRPEntities()1981 	public List<RPEntity> getAttackingRPEntities() {
1982 		final List<RPEntity> list = new ArrayList<>();
1983 
1984 		for (final Entity entity : getAttackSources()) {
1985 			if (entity instanceof RPEntity) {
1986 				list.add((RPEntity) entity);
1987 			}
1988 		}
1989 		return list;
1990 	}
1991 
1992 	/**
1993 	 * Checks whether the attacktarget is null. Sets attacktarget to null if hp
1994 	 * of attacktarget <=0;
1995 	 *
1996 	 * @return true if attacktarget != null and not dead
1997 	 */
isAttacking()1998 	public boolean isAttacking() {
1999 		if (attackTarget != null) {
2000 			if (attackTarget.getHP() <= 0) {
2001 				attackTarget = null;
2002 			}
2003 		} else {
2004 			return false;
2005 		}
2006 		return attackTarget != null;
2007 	}
2008 
2009 	/**
2010 	 * Return the RPEntity that this entity is attacking.
2011 	 *
2012 	 * @return the attack target of this
2013 	 */
getAttackTarget()2014 	public RPEntity getAttackTarget() {
2015 		return attackTarget;
2016 	}
2017 
2018 	/***************************************************************************
2019 	 * * Equipment handling. * *
2020 	 **************************************************************************/
2021 
2022 	/**
2023 	 * Tries to equip an item in the appropriate slot.
2024 	 *
2025 	 * @param item
2026 	 *            the item
2027 	 * @return true if the item can be equipped, else false
2028 	 */
equipToInventoryOnly(final Item item)2029 	public final boolean equipToInventoryOnly(final Item item) {
2030 		final RPSlot slot = getSlotToEquip(item);
2031 		if (slot != null) {
2032 			return equipIt(slot, item);
2033 		} else {
2034 			return false;
2035 		}
2036 	}
2037 
2038 	/**
2039 	 * Check if an object is a stackable item that can be merged to an existing
2040 	 * item stack.
2041 	 *
2042 	 * @param item stackable item
2043 	 * @param object merge candidate
2044 	 * @return <code>true</code> if the items can be merged, <code>false</code>
2045 	 * 	otherwise
2046 	 */
canMergeItems(StackableItem item, RPObject object)2047 	private boolean canMergeItems(StackableItem item, RPObject object) {
2048 		if (object instanceof StackableItem) {
2049 			final StackableItem other = (StackableItem) object;
2050 			if (other.isStackable(item)) {
2051 				return true;
2052 			}
2053 		}
2054 		return false;
2055 	}
2056 
2057 	/**
2058 	 * Find slot where an item could be merged, looking recursively inside a
2059 	 * slot and the content slots of the items in that slot.
2060 	 *
2061 	 * @param item item for which the merge location is sought for
2062 	 * @param slot starting location slot
2063 	 * @return slot where the item can be merged, or <code>null</code> if no
2064 	 * 	suitable location was found
2065 	 */
getSlotToMerge(StackableItem item, RPSlot slot)2066 	private RPSlot getSlotToMerge(StackableItem item, RPSlot slot) {
2067 		if (slot instanceof EntitySlot) {
2068 			if (!((EntitySlot) slot).isReachableForThrowingThingsIntoBy(this)) {
2069 				return null;
2070 			}
2071 		}
2072 		// Try first merging the item in the parent slot, so that the item
2073 		// appears as visibly as possible
2074 		if (item.getPossibleSlots().contains(slot.getName())) {
2075 			for (RPObject obj : slot) {
2076 				if (canMergeItems(item, obj)) {
2077 					return slot;
2078 				}
2079 			}
2080 		}
2081 		// Then check the slots of the contained items
2082 		for (RPObject obj : slot) {
2083 			for (RPSlot childSlot : obj.slots()) {
2084 				RPSlot tmp = getSlotToMerge(item, childSlot);
2085 				if (tmp != null) {
2086 					return tmp;
2087 				}
2088 			}
2089 		}
2090 
2091 		return null;
2092 	}
2093 
2094 	/**
2095 	 * Find a target slot where an item can be equipped. The slots are sought
2096 	 * recursively starting from a specified initial slot, and then proceeding
2097 	 * to the content slots of the items in that slot.
2098 	 *
2099 	 * @param item item to be equipped
2100 	 * @param slot starting slot
2101 	 * @return slot where the item can be equipped, or <code>null</code> if no
2102 	 * 	suitable location was found
2103 	 */
getSlotToEquip(Item item, RPSlot slot)2104 	private RPSlot getSlotToEquip(Item item, RPSlot slot) {
2105 		if (slot instanceof EntitySlot) {
2106 			if (!((EntitySlot) slot).isReachableForThrowingThingsIntoBy(this)) {
2107 				return null;
2108 			}
2109 		}
2110 		if (item.getPossibleSlots().contains(slot.getName())) {
2111 			if (!slot.isFull()) {
2112 				return slot;
2113 			}
2114 		}
2115 		for (RPObject obj : slot) {
2116 			for (RPSlot childSlot : obj.slots()) {
2117 				RPSlot tmp = getSlotToEquip(item, childSlot);
2118 				if (tmp != null) {
2119 					return tmp;
2120 				}
2121 			}
2122 		}
2123 
2124 		return null;
2125 	}
2126 
2127 	/**
2128 	 * Gets the slot in which the entity can equip the item, preferring
2129 	 * locations where the item can be merged with existing item stacks.
2130 	 *
2131 	 * @param item
2132 	 * @return the slot for the item or null if there is no matching slot
2133 	 *         in the entity
2134 	 */
getSlotToEquip(final Item item)2135 	public final RPSlot getSlotToEquip(final Item item) {
2136 		if (item instanceof StackableItem) {
2137 			// Try merging the item first
2138 			for (RPSlot slot : slots()) {
2139 				RPSlot tmp = getSlotToMerge((StackableItem) item, slot);
2140 				if (tmp != null) {
2141 					return tmp;
2142 				}
2143 			}
2144 		}
2145 
2146 		// We can't stack it on another item. Check if we can simply
2147 		// add it to an empty cell.
2148 		for (RPSlot slot : slots()) {
2149 			RPSlot tmp = getSlotToEquip(item, slot);
2150 			if (tmp != null) {
2151 				return tmp;
2152 			}
2153 		}
2154 		return null;
2155 	}
2156 
2157 	/**
2158 	 * Tries to equip an item in the appropriate slot.
2159 	 *
2160 	 * @param item the item
2161 	 * @return true if the item can be equipped, else false
2162 	 */
equipOrPutOnGround(final Item item)2163 	public final boolean equipOrPutOnGround(final Item item) {
2164 		if (equipToInventoryOnly(item)) {
2165 			return true;
2166 		} else {
2167 			item.setPosition(getX(), getY());
2168 			getZone().add(item);
2169 			this.sendPrivateText("You dropped the new item onto the ground because your bag is full.");
2170 			return false;
2171 		}
2172 	}
2173 
2174 
2175 
2176 	/**
2177 	 * Tries to equip one unit of an item in the given slot. Note: This doesn't
2178 	 * check if it is allowed to put the given item into the given slot, e.g. it
2179 	 * is possible to wear your helmet at your feet using this method.
2180 	 *
2181 	 * @param slotName
2182 	 *            the name of the slot
2183 	 * @param item
2184 	 *            the item
2185 	 * @return true if the item can be equipped, else false
2186 	 */
equip(final String slotName, final Item item)2187 	public final boolean equip(final String slotName, final Item item) {
2188 		RPSlot slot = getSlot(slotName);
2189 		if (equipIt(slot, item)) {
2190 			updateItemAtkDef();
2191 			return true;
2192 		}
2193 		return false;
2194 	}
2195 
2196 	/**
2197 	 * Removes a specific amount of an item from the RPEntity. The item can
2198 	 * either be stackable or non-stackable. The units can be distributed over
2199 	 * different slots. If the RPEntity doesn't have enough units of the item,
2200 	 * doesn't remove anything.
2201 	 *
2202 	 * @param name
2203 	 *            The name of the item
2204 	 * @param amount
2205 	 *            The number of units that should be dropped
2206 	 * @return true iff dropping the desired amount was successful.
2207 	 */
drop(final String name, final int amount)2208 	public boolean drop(final String name, final int amount) {
2209 		return drop(nameMatches(name), amount);
2210 	}
2211 
isEquipped(Predicate<Item> condition, int amount)2212 	private boolean isEquipped(Predicate<Item> condition, int amount) {
2213 		Iterable<Item> matching = getAllEquipped(condition)::iterator;
2214 		int count = 0;
2215 		for (Item item : matching) {
2216 			count += item.getQuantity();
2217 			if (count >= amount) {
2218 				return true;
2219 			}
2220 		}
2221 		return false;
2222 	}
2223 
drop(Predicate<Item> condition, int amount)2224 	private boolean drop(Predicate<Item> condition, int amount) {
2225 		if (!isEquipped(condition, amount)) {
2226 			return false;
2227 		}
2228 
2229 		int toDrop = amount;
2230 		Iterable<Item> matchingItems = equippedStream().filter(condition)::iterator;
2231 		for (Item item : matchingItems) {
2232 			toDrop -= dropItem(item, toDrop);
2233 			if (toDrop == 0) {
2234 				return true;
2235 			}
2236 		}
2237 
2238 		logger.error("Not enough items dropped even though the entity was checked to have them", new Throwable());
2239 		return false;
2240 	}
2241 
2242 	/**
2243 	 * Low level drop. <b>Does not check the containing slot or owner. This is
2244 	 * meant to be used only by higher level drop() methods.</b>
2245 	 *
2246 	 * @param item dropped item
2247 	 * @param amount maximum amout to drop
2248 	 * @return dropped amount
2249 	 */
dropItem(Item item, int amount)2250 	private int dropItem(Item item, int amount) {
2251 		RPSlot slot = item.getContainerSlot();
2252 		if (item instanceof StackableItem) {
2253 			// The item is stackable, we try to remove
2254 			// multiple ones.
2255 			final int quantity = item.getQuantity();
2256 			if (amount >= quantity) {
2257 				new ItemLogger().destroy(this, slot, item);
2258 				slot.remove(item.getID());
2259 				return quantity;
2260 			} else {
2261 				((StackableItem) item).setQuantity(quantity - amount);
2262 				new ItemLogger().splitOff(this, item, amount);
2263 				return amount;
2264 			}
2265 		} else {
2266 			// The item is not stackable, so we only remove a
2267 			// single one.
2268 			slot.remove(item.getID());
2269 			new ItemLogger().destroy(this, slot, item);
2270 			return 1;
2271 		}
2272 	}
2273 
2274 	/**
2275 	 * Removes one unit of an item from the RPEntity. The item can either be
2276 	 * stackable or non-stackable. If the RPEntity doesn't have enough the item,
2277 	 * doesn't remove anything.
2278 	 *
2279 	 * @param name
2280 	 *            The name of the item
2281 	 * @return true iff dropping the item was successful.
2282 	 */
drop(final String name)2283 	public boolean drop(final String name) {
2284 		return drop(name, 1);
2285 	}
2286 
2287 	/**
2288 	 * Removes the given item from the RPEntity. The item can either be
2289 	 * stackable or non-stackable. If the RPEntity doesn't have the item,
2290 	 * doesn't remove anything.
2291 	 *
2292 	 * @param item
2293 	 *            the item that should be removed
2294 	 * @return true iff dropping the item was successful.
2295 	 */
drop(final Item item)2296 	public boolean drop(final Item item) {
2297 		return drop(it -> item == it, 1);
2298 	}
2299 
2300 	/**
2301 	 * Removes a specific amount of an item with matching info string from
2302 	 * the RPEntity. The item can either be stackable or non-stackable.
2303 	 * The units can be distributed over different slots. If the RPEntity
2304 	 * doesn't have enough units of the item, doesn't remove anything.
2305 	 *
2306 	 * @param name
2307 	 * 		Name of item to remove.
2308 	 * @param infostring
2309 	 * 		Required item info string to match.
2310 	 * @param amount
2311 	 * 		Number of items to remove from entity.
2312 	 * @return
2313 	 * 		<code>true</code> if dropping the item(s) was successful.
2314 	 */
dropWithInfostring(final String name, final String infostring, final int amount)2315 	public boolean dropWithInfostring(final String name, final String infostring, final int amount) {
2316 		return drop(item -> (name.equals(item.getName()) && infostring.equals(item.getInfoString())), amount);
2317 	}
2318 
2319 	/**
2320 	 * Removes a single item with matching info string from the RPEntity.
2321 	 * The item can either be stackable or non-stackable. The units can
2322 	 * be distributed over different slots. If the RPEntity doesn't have
2323 	 * enough units of the item, doesn't remove anything.
2324 	 *
2325 	 * @param name
2326 	 * 		Name of item to remove.
2327 	 * @param infostring
2328 	 * 		Required item info string to match.
2329 	 * @return
2330 	 * 		<code>true</code> if dropping the item(s) was successful.
2331 	 */
dropWithInfostring(final String name, final String infostring)2332 	public boolean dropWithInfostring(final String name, final String infostring) {
2333 		return dropWithInfostring(name, infostring, 1);
2334 	}
2335 
2336 	/**
2337 	 * Determine if this entity is equipped with a minimum quantity of an item.
2338 	 *
2339 	 * @param name
2340 	 *            The item name.
2341 	 * @param amount
2342 	 *            The minimum amount.
2343 	 *
2344 	 * @return <code>true</code> if the item is equipped with the minimum
2345 	 *         number.
2346 	 */
isEquipped(final String name, final int amount)2347 	public boolean isEquipped(final String name, final int amount) {
2348 		return isEquipped(nameMatches(name), amount);
2349 	}
2350 
2351 	/**
2352 	 * Determine if this entity is equipped with an item.
2353 	 *
2354 	 * @param name
2355 	 *            The item name.
2356 	 *
2357 	 * @return <code>true</code> if the item is equipped.
2358 	 */
isEquipped(final String name)2359 	public boolean isEquipped(final String name) {
2360 		return isEquipped(name, 1);
2361 	}
2362 
2363 	/**
2364 	 * Checks if entity carry a number of items with specified info string.
2365 	 *
2366 	 * @param name
2367 	 * 		Name of item to check.
2368 	 * @param infostring
2369 	 * 		Info string of item to check.
2370 	 * @param amount
2371 	 * 		Quantity of carried items to check.
2372 	 * @return
2373 	 * 		<code>true</code> if entity is carrying at least specified amount of items matching name & infostring.
2374 	 */
isEquippedWithInfostring(final String name, final String infostring, final int amount)2375 	public boolean isEquippedWithInfostring(final String name, final String infostring, final int amount) {
2376 		return getAllEquippedWithInfostring(name, infostring).size() >= amount;
2377 	}
2378 
2379 	/**
2380 	 * Checks if entity carry a number of items with specified info string.
2381 	 *
2382 	 * @param name
2383 	 * 		Name of item to check.
2384 	 * @param infostring
2385 	 * 		Info string of item to check.
2386 	 * @return
2387 	 * 		<code>true</code> if entity is carrying at least one of items matching name & infostring.
2388 	 */
isEquippedWithInfostring(final String name, final String infostring)2389 	public boolean isEquippedWithInfostring(final String name, final String infostring) {
2390 		return isEquippedWithInfostring(name, infostring, 1);
2391 	}
2392 
2393 	/**
2394 	 * Gets the number of items of the given name that are carried by the
2395 	 * RPEntity. The item can either be stackable or non-stackable.
2396 	 *
2397 	 * @param name
2398 	 *            The item's name
2399 	 * @return The number of carried items
2400 	 */
getNumberOfEquipped(final String name)2401 	public int getNumberOfEquipped(final String name) {
2402 		return equippedStream().filter(nameMatches(name))
2403 				.mapToInt(Item::getQuantity).sum();
2404 	}
2405 
2406 	/**
2407 	 * Gets the number of items of the given name including bank.
2408 	 * The item can either be stackable or non-stackable.
2409 	 *
2410 	 * @param name
2411 	 *            The item's name
2412 	 * @return The number of carried items
2413 	 */
getTotalNumberOf(final String name)2414 	public int getTotalNumberOf(final String name) {
2415 		Stream<Item> allItems = slots().stream().flatMap(this::slotStream);
2416 		return allItems.filter(nameMatches(name)).mapToInt(Item::getQuantity).sum();
2417 	}
2418 
2419 	/**
2420 	 * Gets an item that is carried by the RPEntity. If the item is stackable,
2421 	 * gets all that are on the first stack that is found.
2422 	 *
2423 	 * @param name
2424 	 *            The item's name
2425 	 * @return The item, or a stack of stackable items, or null if nothing was
2426 	 *         found
2427 	 */
getFirstEquipped(final String name)2428 	public Item getFirstEquipped(final String name) {
2429 		return equippedStream().filter(nameMatches(name)).findFirst().orElse(null);
2430 	}
2431 
2432 	/**
2433 	 * Gets an item that is carried by the RPEntity. If the item is stackable,
2434 	 * gets all that are on the first stack that is found.
2435 	 *
2436 	 * @param name
2437 	 *            The item's name
2438 	 * @return The item, or a stack of stackable items, or an empty list if nothing was
2439 	 *         found
2440 	 */
getAllEquipped(final String name)2441 	public List<Item> getAllEquipped(final String name) {
2442 		return getAllEquipped(nameMatches(name));
2443 	}
2444 
getAllEquipped(Predicate<Item> condition)2445 	private List<Item> getAllEquipped(Predicate<Item> condition) {
2446 		return equippedStream().filter(condition).collect(Collectors.toList());
2447 	}
2448 
2449 	/**
2450 	 * Retrieves all of an item with matching info string.
2451 	 *
2452 	 * @param name
2453 	 * 		Name of item to match.
2454 	 * @param infostring
2455 	 * 		Info string of item to match.
2456 	 * @return
2457 	 * 		List<Item>
2458 	 */
getAllEquippedWithInfostring(String name, String infostring)2459 	public List<Item> getAllEquippedWithInfostring(String name, String infostring) {
2460 		return getAllEquipped(item -> name.equals(item.getName())
2461 				&& infostring.equalsIgnoreCase(item.getInfoString()));
2462 	}
2463 
2464 	/**
2465 	 * checks if an item of class <i>clazz</i> is equipped in slot <i>slot</i>
2466 	 * returns true if it is, else false.
2467 	 *
2468 	 * @param slot
2469 	 * @param clazz
2470 	 * @return true if so false otherwise
2471 	 */
isEquippedItemClass(final String slot, final String clazz)2472 	public boolean isEquippedItemClass(final String slot, final String clazz) {
2473 		if (hasSlot(slot)) {
2474 			// get slot if the this entity has one
2475 			final RPSlot rpslot = getSlot(slot);
2476 			// traverse all slot items
2477 			for (final RPObject item : rpslot) {
2478 				if ((item instanceof Item) && ((Item) item).isOfClass(clazz)) {
2479 					return true;
2480 				}
2481 			}
2482 		}
2483 
2484 		// no slot, free slot or wrong item type
2485 		return false;
2486 	}
2487 
2488 
2489 	/**
2490 	 * checks if an item is equipped in a slot
2491 	 *
2492 	 * @param slot
2493 	 * @param item
2494 	 * @return true if so false otherwise
2495 	 */
isEquippedItemInSlot(final String slot, final String item)2496 	public boolean isEquippedItemInSlot(final String slot, final String item) {
2497 		if (hasSlot(slot)) {
2498 			final RPSlot rpslot = getSlot(slot);
2499 			for (final RPObject object : rpslot) {
2500 				if ((object instanceof Item) && ((Item) object).getName().equals(item)) {
2501 					return true;
2502 				}
2503 			}
2504 		}
2505 
2506 		// no slot, free slot or wrong item type
2507 		return false;
2508 	}
2509 
2510 	/**
2511 	 * Finds the first item of class <i>clazz</i> from the slot.
2512 	 *
2513 	 * @param slot
2514 	 * @param clazz
2515 	 * @return the item or <code>null</code> if there is no item with the
2516 	 *         requested clazz.
2517 	 */
getEquippedItemClass(final String slot, final String clazz)2518 	public Item getEquippedItemClass(final String slot, final String clazz) {
2519 		if (hasSlot(slot)) {
2520 			// get slot if the this entity has one
2521 			final RPSlot rpslot = getSlot(slot);
2522 			// traverse all slot items
2523 			for (final RPObject object : rpslot) {
2524 				// is it the right type
2525 				if (object instanceof Item) {
2526 					final Item item = (Item) object;
2527 					if (item.isOfClass(clazz)) {
2528 						return item;
2529 					}
2530 				}
2531 			}
2532 		}
2533 
2534 		// no slot, free slot or wrong item type
2535 		return null;
2536 	}
2537 
2538 
2539 	/**
2540 	 * Gets the weapon that this entity is holding in its hands.
2541 	 *
2542 	 * @return The weapon, or null if this entity is not holding a weapon. If
2543 	 *         the entity has a weapon in each hand, returns the weapon in its
2544 	 *         left hand.
2545 	 */
getWeapon()2546 	public Item getWeapon() {
2547 		final String[] weaponsClasses = {"club", "sword", "axe", "ranged", "missile"};
2548 
2549 		for (final String weaponClass : weaponsClasses) {
2550 			final String[] slots = { "lhand", "rhand" };
2551 			for (final String slot : slots) {
2552 				final Item item = getEquippedItemClass(slot, weaponClass);
2553 				if (item != null) {
2554 					return item;
2555 				}
2556 			}
2557 		}
2558 
2559 		return null;
2560 	}
2561 
getWeapons()2562 	public List<Item> getWeapons() {
2563 		final List<Item> weapons = new ArrayList<>();
2564 		Item weaponItem = getWeapon();
2565 		if (weaponItem != null) {
2566 			weapons.add(weaponItem);
2567 
2568 			// pair weapons
2569 			if (weaponItem.getName().startsWith("l hand ")) {
2570 				// check if there is a matching right-hand weapon in
2571 				// the other hand.
2572 				final String rpclass = weaponItem.getItemClass();
2573 				weaponItem = getEquippedItemClass("rhand", rpclass);
2574 				if ((weaponItem != null)
2575 						&& (weaponItem.getName().startsWith("r hand "))) {
2576 					weapons.add(weaponItem);
2577 				} else {
2578 					// You can't use a left-hand weapon without the matching
2579 					// right-hand weapon. Hmmm... but why not?
2580 					weapons.clear();
2581 				}
2582 			} else {
2583 				// You can't hold a right-hand weapon with your left hand, for
2584 				// ergonomic reasons ;)
2585 				if (weaponItem.getName().startsWith("r hand ")) {
2586 					weapons.clear();
2587 				}
2588 			}
2589 		}
2590 
2591 		return weapons;
2592 	}
2593 
2594 	/**
2595 	 * Gets the range weapon (bow etc.) that this entity is holding in its
2596 	 * hands.
2597 	 *
2598 	 * @return The range weapon, or null if this entity is not holding a range
2599 	 *         weapon. If the entity has a range weapon in each hand, returns
2600 	 *         one in its left hand.
2601 	 */
getRangeWeapon()2602 	public Item getRangeWeapon() {
2603 		for (final Item weapon : getWeapons()) {
2604 			if (weapon.isOfClass("ranged")) {
2605 				return weapon;
2606 			}
2607 		}
2608 
2609 		return null;
2610 	}
2611 
2612 	/**
2613 	 * Gets the stack of ammunition (arrows or similar) that this entity is
2614 	 * holding in its hands.
2615 	 *
2616 	 * @return The ammunition, or null if this entity is not holding ammunition.
2617 	 *         If the entity has ammunition in each hand, returns the ammunition
2618 	 *         in its left hand.
2619 	 */
getAmmunition()2620 	public StackableItem getAmmunition() {
2621 		final String[] slots = { "lhand", "rhand" };
2622 
2623 		for (final String slot : slots) {
2624 			final StackableItem item = (StackableItem) getEquippedItemClass(
2625 					slot, "ammunition");
2626 			if (item != null) {
2627 				return item;
2628 			}
2629 		}
2630 
2631 		return null;
2632 	}
2633 
2634 	/**
2635 	 * Gets the stack of missiles (spears or similar) that this entity is
2636 	 * holding in its hands, but only if it is not holding another, non-missile
2637 	 * weapon in the other hand.
2638 	 *
2639 	 * You can only throw missiles while you're not holding another weapon. This
2640 	 * restriction is a workaround because of the way attack strength is
2641 	 * determined; otherwise, one could increase one's spear attack strength by
2642 	 * holding an ice sword in the other hand.
2643 	 *
2644 	 * @return The missiles, or null if this entity is not holding missiles. If
2645 	 *         the entity has missiles in each hand, returns the missiles in its
2646 	 *         left hand.
2647 	 */
getMissileIfNotHoldingOtherWeapon()2648 	public StackableItem getMissileIfNotHoldingOtherWeapon() {
2649 		StackableItem missileWeaponItem = null;
2650 		boolean holdsOtherWeapon = false;
2651 
2652 		for (final Item weaponItem : getWeapons()) {
2653 			if (weaponItem.isOfClass("missile")) {
2654 				missileWeaponItem = (StackableItem) weaponItem;
2655 			} else {
2656 				holdsOtherWeapon = true;
2657 			}
2658 		}
2659 
2660 		if (holdsOtherWeapon) {
2661 			return null;
2662 		} else {
2663 			return missileWeaponItem;
2664 		}
2665 	}
2666 
2667 	/** @return true if the entity has an item of class shield equipped. */
hasShield()2668 	public boolean hasShield() {
2669 		return isEquippedItemClass("lhand", "shield")
2670 				|| isEquippedItemClass("rhand", "shield");
2671 	}
2672 
getShield()2673 	public Item getShield() {
2674 		final Item item = getEquippedItemClass("lhand", "shield");
2675 		if (item != null) {
2676 			return item;
2677 		} else {
2678 			return getEquippedItemClass("rhand", "shield");
2679 		}
2680 	}
2681 
hasArmor()2682 	public boolean hasArmor() {
2683 		return isEquippedItemClass("armor", "armor");
2684 	}
2685 
getArmor()2686 	public Item getArmor() {
2687 		return getEquippedItemClass("armor", "armor");
2688 	}
2689 
hasHelmet()2690 	public boolean hasHelmet() {
2691 		return isEquippedItemClass("head", "helmet");
2692 	}
2693 
getHelmet()2694 	public Item getHelmet() {
2695 		return getEquippedItemClass("head", "helmet");
2696 	}
2697 
hasLegs()2698 	public boolean hasLegs() {
2699 		return isEquippedItemClass("legs", "legs");
2700 	}
2701 
getLegs()2702 	public Item getLegs() {
2703 		return getEquippedItemClass("legs", "legs");
2704 	}
2705 
hasBoots()2706 	public boolean hasBoots() {
2707 		return isEquippedItemClass("feet", "boots");
2708 	}
2709 
getBoots()2710 	public Item getBoots() {
2711 		return getEquippedItemClass("feet", "boots");
2712 	}
2713 
hasCloak()2714 	public boolean hasCloak() {
2715 		return isEquippedItemClass("cloak", "cloak");
2716 	}
2717 
getCloak()2718 	public Item getCloak() {
2719 		return getEquippedItemClass("cloak", "cloak");
2720 	}
2721 
hasRing()2722 	public boolean hasRing() {
2723 		return isEquippedItemClass("finger", "ring");
2724 	}
2725 
getRing()2726 	public Item getRing() {
2727 		return getEquippedItemClass("finger", "ring");
2728 	}
2729 
2730 	@Override
describe()2731 	public String describe() {
2732 		String text = super.describe();
2733 		if (getLevel() > 0) {
2734 			boolean showLevel = true;
2735 			if (this instanceof Creature) {
2736 				/**
2737 				 * Hide level information of chess pieces.
2738 				 *
2739 				 * Don't call "Creature.isAbnormal" method here since it also checks "rare" attribute.
2740 				 */
2741 				if (((Creature) this).getAIProfiles().containsKey("abnormal") &&
2742 						(this.getName().startsWith("chaos") || this.getName().startsWith("madaram"))) {
2743 					showLevel = false;
2744 				}
2745 			}
2746 
2747 			if (showLevel) {
2748 				text += " It is level " + getLevel() + ".";
2749 			}
2750 		}
2751 
2752 		return text;
2753 	}
2754 
2755 	/**
2756 	 * Sends a message that only this RPEntity can read. In this default
2757 	 * implementation, this method does nothing; it can be overridden in
2758 	 * subclasses.
2759 	 *
2760 	 * @param text
2761 	 *            The message.
2762 	 */
sendPrivateText(final String text)2763 	public void sendPrivateText(final String text) {
2764 		// does nothing in this implementation.
2765 	}
2766 
2767 	/**
2768 	 * Sends a message that only this player can read.
2769 	 *
2770 	 * @param type
2771 	 *            NotificationType
2772 	 * @param text
2773 	 *            the message.
2774 	 */
sendPrivateText(final NotificationType type, final String text)2775 	public void sendPrivateText(final NotificationType type, final String text) {
2776 		// does nothing in this implementation.
2777 	}
2778 
2779 	/**
2780 	 * Retrieves total ATK value of held weapons.
2781 	 */
getItemAtk()2782 	public float getItemAtk() {
2783 		int weapon = 0;
2784 		int ring = 0;
2785 
2786 		final List<Item> weapons = getWeapons();
2787 		for (final Item weaponItem : weapons) {
2788 			weapon += weaponItem.getAttack();
2789 		}
2790 
2791 		// calculate ammo when not using RATK stat
2792 		if (!Testing.COMBAT && weapons.size() > 0) {
2793 			if (getWeapons().get(0).isOfClass("ranged")) {
2794 				weapon += getAmmoAtk();
2795 			}
2796 		}
2797 
2798 		if (hasRing()) {
2799 			ring = getRing().getAttack();
2800 		}
2801 
2802 		return weapon + ring;
2803 	}
2804 
2805 	/**
2806 	 * Retrieves total range attack value of held weapon & ammunition.
2807 	 */
getItemRatk()2808 	public float getItemRatk() {
2809 		float ratk = 0;
2810 		final List<Item> weapons = getWeapons();
2811 
2812 		if (weapons.size() > 0) {
2813 			final Item held = getWeapons().get(0);
2814 			ratk += held.getRangedAttack();
2815 
2816 			if (held.isOfClass("ranged")) {
2817 				ratk += getAmmoAtk();
2818 			}
2819 		}
2820 
2821 		return ratk;
2822 	}
2823 
2824 	/**
2825 	 * Retrieves ATK or RATK (depending on testing.combat system property) value of equipped ammunition.
2826 	 */
getAmmoAtk()2827 	private float getAmmoAtk() {
2828 		float ammo = 0;
2829 
2830 		final StackableItem ammoItem = getAmmunition();
2831 		if (ammoItem != null) {
2832 			if (Testing.COMBAT) {
2833 				ammo = ammoItem.getRangedAttack();
2834 			} else {
2835 				ammo = ammoItem.getAttack();
2836 			}
2837 		}
2838 
2839 		return ammo;
2840 	}
2841 
getItemDef()2842 	public float getItemDef() {
2843 		int shield = 0;
2844 		int armor = 0;
2845 		int helmet = 0;
2846 		int legs = 0;
2847 		int boots = 0;
2848 		int cloak = 0;
2849 		int weapon = 0;
2850 		int ring = 0;
2851 
2852 		Item item;
2853 
2854 		if (hasShield()) {
2855 			item = getShield();
2856 			shield = (int) (item.getDefense() / getItemLevelModifier(item));
2857 		}
2858 
2859 		if (hasArmor()) {
2860 			item = getArmor();
2861 			armor = (int) (item.getDefense() / getItemLevelModifier(item));
2862 		}
2863 
2864 		if (hasHelmet()) {
2865 			item = getHelmet();
2866 			helmet = (int) (item.getDefense() / getItemLevelModifier(item));
2867 		}
2868 
2869 		if (hasLegs()) {
2870 			item = getLegs();
2871 			legs = (int) (item.getDefense() / getItemLevelModifier(item));
2872 		}
2873 
2874 		if (hasBoots()) {
2875 			item = getBoots();
2876 			boots = (int) (item.getDefense() / getItemLevelModifier(item));
2877 		}
2878 
2879 		if (hasCloak()) {
2880 			item = getCloak();
2881 			cloak = (int) (item.getDefense() / getItemLevelModifier(item));
2882 		}
2883 
2884 		if (hasRing()) {
2885 			item = getRing();
2886 			ring = (int) (item.getDefense() / getItemLevelModifier(item));
2887 		}
2888 
2889 		final List<Item> targetWeapons = getWeapons();
2890 		for (final Item weaponItem : targetWeapons) {
2891 			weapon += weaponItem.getDefense() / getItemLevelModifier(weaponItem);
2892 		}
2893 
2894 		return SHIELD_DEF_MULTIPLIER * shield + ARMOR_DEF_MULTIPLIER * armor
2895 				+ CLOAK_DEF_MULTIPLIER * cloak + HELMET_DEF_MULTIPLIER * helmet
2896 				+ LEG_DEF_MULTIPLIER * legs + BOOTS_DEF_MULTIPLIER * boots
2897 				+ WEAPON_DEF_MULTIPLIER * weapon + RING_DEF_MULTIPLIER * ring;
2898 	}
2899 
2900 	/**
2901 	 * get all items that affect a player's defensive value except the weapon
2902 	 *
2903 	 * @return a list of all equipped defensive items
2904 	 */
getDefenseItems()2905 	public List<Item> getDefenseItems() {
2906 		List<Item> items = new LinkedList<>();
2907 		if (hasShield()) {
2908 			items.add(getShield());
2909 		}
2910 		if (hasArmor()) {
2911 			items.add(getArmor());
2912 		}
2913 		if (hasHelmet()) {
2914 			items.add(getHelmet());
2915 		}
2916 		if (hasLegs()) {
2917 			items.add(getLegs());
2918 		}
2919 
2920 		if (hasBoots()) {
2921 			items.add(getBoots());
2922 		}
2923 		if (hasCloak()) {
2924 			items.add(getCloak());
2925 		}
2926 		return items;
2927 	}
2928 
2929 	/**
2930 	 * Recalculates item based atk and def.
2931 	 */
updateItemAtkDef()2932 	public void updateItemAtkDef() {
2933 		put("atk_item", ((int) getItemAtk()));
2934 		if (Testing.COMBAT) {
2935 			put("ratk_item", ((int) getItemRatk()));
2936 		}
2937 		put("def_item", ((int) getItemDef()));
2938 		notifyWorldAboutChanges();
2939 	}
2940 
2941 	/**
2942 	 * Can this entity do a distance attack on the given target?
2943 	 *
2944 	 * @param target
2945 	 * @param maxrange maximum attack distance
2946 	 *
2947 	 * @return true if this entity is armed with a distance weapon and if the
2948 	 *         target is in range.
2949 	 */
canDoRangeAttack(final RPEntity target, final int maxrange)2950 	public boolean canDoRangeAttack(final RPEntity target, final int maxrange) {
2951 		// the target's in range
2952 		return (squaredDistance(target) <= maxrange * maxrange);
2953 	}
2954 
2955 	/**
2956 	 * Check if the entity has a line of sight to the the center of another
2957 	 * entity. Only static collisions are checked.
2958 	 *
2959 	 * @param target target entity
2960 	 * @return <code>true</code> if there are no collisions blocking the line
2961 	 *	of sight, <code>false</code> otherwise
2962 	 */
hasLineOfSight(final Entity target)2963 	public boolean hasLineOfSight(final Entity target) {
2964 		return !getZone().collidesOnLine((int) (getX() + getWidth() / 2),
2965 				(int) (getY() + getHeight() / 2),
2966 				(int) (target.getX() + target.getWidth() / 2),
2967 				(int) (target.getY() + target.getHeight() / 2));
2968 	}
2969 
2970 	/**
2971 	 * Get the maximum distance attack range.
2972 	 *
2973 	 * @return maximum range, or 0 if the entity can't attack from distance
2974 	 */
getMaxRangeForArcher()2975 	public int getMaxRangeForArcher() {
2976 		final Item rangeWeapon = getRangeWeapon();
2977 		final StackableItem ammunition = getAmmunition();
2978 		final StackableItem missiles = getMissileIfNotHoldingOtherWeapon();
2979 		int maxRange;
2980 		if ((rangeWeapon != null) && (ammunition != null)
2981 				&& (ammunition.getQuantity() > 0)) {
2982 			maxRange = rangeWeapon.getInt("range") + ammunition.getInt("range");
2983 		} else if ((missiles != null) && (missiles.getQuantity() > 0)) {
2984 			maxRange = missiles.getInt("range");
2985 		} else {
2986 			// The entity doesn't hold the necessary distance weapons.
2987 			maxRange = 0;
2988 		}
2989 		return maxRange;
2990 	}
2991 
2992 	/**
2993 	 * Set the entity's formatted title.
2994 	 *
2995 	 * @param title
2996 	 *            The title, or <code>null</code>.
2997 	 */
setTitle(final String title)2998 	public void setTitle(final String title) {
2999 		if (title != null) {
3000 			put(ATTR_TITLE, title);
3001 		} else if (has(ATTR_TITLE)) {
3002 			remove(ATTR_TITLE);
3003 		}
3004 	}
3005 
3006 	//
3007 	// Entity
3008 	//
3009 
3010 	/**
3011 	 * Returns the name or something that can be used to identify the entity for
3012 	 * the player.
3013 	 *
3014 	 * @param definite
3015 	 *            <code>true</code> for "the", and <code>false</code> for "a/an"
3016 	 *            in case the entity has no name.
3017 	 *
3018 	 * @return The description name.
3019 	 */
3020 	@Override
getDescriptionName(final boolean definite)3021 	public String getDescriptionName(final boolean definite) {
3022 		if (name != null) {
3023 			return name;
3024 		} else {
3025 			return super.getDescriptionName(definite);
3026 		}
3027 	}
3028 
3029 	/**
3030 	 * Get the nicely formatted entity title/name.
3031 	 *
3032 	 * @return The title, or <code>null</code> if unknown.
3033 	 */
3034 	@Override
getTitle()3035 	public String getTitle() {
3036 		if (has(ATTR_TITLE)) {
3037 			return get(ATTR_TITLE);
3038 		} else if (name != null) {
3039 			return name;
3040 		} else {
3041 			return super.getTitle();
3042 		}
3043 	}
3044 
3045 	/**
3046 	 * Perform cycle logic.
3047 	 */
logic()3048 	public abstract void logic();
3049 
3050 	/**
3051 	 * Chooses randomly if this has hit the defender, or if this missed him.
3052 	 * Note that, even if this method returns true, the damage done might be 0
3053 	 * (if the defender blocks the attack).
3054 	 *
3055 	 * @param defender
3056 	 *            The attacked RPEntity.
3057 	 * @return true if the attacker has hit the defender (the defender may still
3058 	 *         block this); false if the attacker has missed the defender.
3059 	 */
canHit(final RPEntity defender)3060 	public boolean canHit(final RPEntity defender) {
3061 		int roll = Rand.roll1D20();
3062 		final int defenderDEF = defender.getCappedDef();
3063 
3064 		// Check if attacking from distance
3065 		boolean usesRanged = false;
3066 		if (!this.nextTo(defender)) {
3067 			usesRanged = true;
3068 		}
3069 
3070 		final int attackerATK;
3071 		if (Testing.COMBAT && usesRanged) {
3072 			attackerATK = this.getCappedRatk(); // player is using ranged weapon
3073 		} else {
3074 			attackerATK = this.getCappedAtk(); // player is using hand-to-hand
3075 		}
3076 
3077 		/*
3078 		 * Use some karma unless attacker is much stronger than defender, in
3079 		 * which case attacker doesn't need luck to help him hit.
3080 		 */
3081 		final int levelDifferenceToNotNeedKarmaAttacking = (int) (IGNORE_KARMA_MULTIPLIER * getLevel());
3082 
3083 		String karmaMode = null;
3084 		if (this.has(COMBAT_KARMA)) {
3085 			karmaMode = this.get(COMBAT_KARMA);
3086 		}
3087 
3088 		boolean useKarma = false;
3089 		if (karmaMode == null || karmaMode.equals(KARMA_SETTINGS.get(1))) {
3090 			if (!(getLevel() - levelDifferenceToNotNeedKarmaAttacking > defender.getLevel())) {
3091 				useKarma = true;
3092 			}
3093 		} else if (karmaMode.equals(KARMA_SETTINGS.get(2))) {
3094 			useKarma = true;
3095 		}
3096 
3097 		// using karma here increases chance to hit enemy
3098 		if (useKarma) {
3099 			final double karmaMultiplier = this.useKarma(0.1);
3100 			// the karma effect must be cast to an integer to affect the roll
3101 			// but in most cases this means the karma use was lost. so multiply by 2 to
3102 			// make the same amount of karma use be more useful
3103 			final double karmaEffect = roll * karmaMultiplier * 2.0;
3104 			roll -= (int) karmaEffect;
3105 		}
3106 
3107 		int risk = calculateRiskForCanHit(roll, defenderDEF, attackerATK);
3108 
3109 		if (logger.isDebugEnabled() || Testing.DEBUG) {
3110 			logger.debug("attack from " + this + " to " + defender
3111 					+ ": Risk to strike: " + risk);
3112 		}
3113 
3114 		if (risk < 0) {
3115 			risk = 0;
3116 		}
3117 
3118 		if (risk > 1) {
3119 			risk = 1;
3120 		}
3121 
3122 		return (risk != 0);
3123 	}
3124 
calculateRiskForCanHit(final int roll, final int defenderDEF, final int attackerATK)3125 	int calculateRiskForCanHit(final int roll, final int defenderDEF,
3126 			final int attackerATK) {
3127 		return 20 * attackerATK - roll * defenderDEF;
3128 	}
3129 
3130 	/**
3131 	 * Returns the attack rate, the lower the better.
3132 	 *
3133 	 * @return the attack rate
3134 	 */
getAttackRate()3135 	public int getAttackRate() {
3136 
3137 		boolean meleeDistance = isAttacking() && nextTo(getAttackTarget());
3138 
3139 		final List<Item> weapons = getWeapons();
3140 
3141 		if (weapons.isEmpty()) {
3142 			return Item.getDefaultAttackRate();
3143 		}
3144 		int best = weapons.get(0).getAttackRate(meleeDistance);
3145 		for (final Item weapon : weapons) {
3146 			final int res = weapon.getAttackRate(meleeDistance);
3147 			if (res < best) {
3148 				best = res;
3149 			}
3150 		}
3151 
3152 		// Level effect
3153 		best = (int) Math.ceil(best * getItemLevelModifier(weapons.get(0)));
3154 
3155 		return best;
3156 	}
3157 
3158 	/**
3159 	 * Get a modifier to be used when an item has a higher min_level
3160 	 * than the entity's level. For any item where the entity's level
3161 	 * is high enough, the modifier is 1. For anything else, > 1 depending
3162 	 * on the ratio between the required, and the possessed level.
3163 	 *
3164 	 * @param item the item to be examined
3165 	 * @return modifier for item properties
3166 	 */
getItemLevelModifier(Item item)3167 	private double getItemLevelModifier(Item item) {
3168 		final String minLevelS = item.get("min_level");
3169 
3170 		if (minLevelS != null) {
3171 			final int minLevel = Integer.parseInt(minLevelS);
3172 			final int level = getLevel();
3173 			if (minLevel > level) {
3174 				return 1 - Math.log(((double) level + 1) / (minLevel + 1));
3175 			}
3176 		}
3177 
3178 		return 1.0;
3179 	}
3180 
3181 	/**
3182 	 * Lets the attacker attack its target.
3183 	 *
3184 	 * @return true iff the attacker has done damage to the defender.
3185 	 *
3186 	 */
attack()3187 	public boolean attack() {
3188 		boolean result = false;
3189 		final RPEntity defender = this.getAttackTarget();
3190 
3191 		// isInZoneandNotDead(defender);
3192 
3193 		defender.rememberAttacker(this);
3194 
3195 		final int maxRange = getMaxRangeForArcher();
3196 		/*
3197 		 * The second part (damage type check) ensures that normal archers need
3198 		 * distance attack modifiers for melee, but creatures with special
3199 		 * ranged attacks (dragons) pay the price only when using their ranged
3200 		 * powers (yes, it's a bit of a hack).
3201 		 */
3202 		boolean isRanged = ((maxRange > 0) && canDoRangeAttack(defender, maxRange))
3203 			&& (((getDamageType() == getRangedDamageType()) || squaredDistance(defender) > 0));
3204 
3205 		Nature nature;
3206 		final float itemAtk;
3207 		if (isRanged) {
3208 			nature = getRangedDamageType();
3209 			itemAtk = getItemRatk();
3210 		} else {
3211 			nature = getDamageType();
3212 			itemAtk = getItemAtk();
3213 		}
3214 
3215 		// Try to inflict a status effect
3216 		for (StatusAttacker statusAttacker : statusAttackers) {
3217 			statusAttacker.onAttackAttempt(defender, this);
3218 		}
3219 
3220 		// Weapon for the use in the attack event
3221 		Item attackWeapon = getWeapon();
3222 		String weaponName = null;
3223 		if (attackWeapon != null) {
3224 			weaponName = attackWeapon.getWeaponType();
3225 		}
3226 
3227 		if (this.canHit(defender)) {
3228 			defender.applyDefXP(this);
3229 
3230 			int damage = damageDone(defender, itemAtk, nature, isRanged, maxRange);
3231 
3232 			if (damage > 0) {
3233 
3234 				// limit damage to target HP
3235 				damage = Math.min(damage, defender.getHP());
3236 				this.handleLifesteal(this, this.getWeapons(), damage);
3237 
3238 				defender.onDamaged(this, damage);
3239 
3240 				if (logger.isDebugEnabled() || Testing.DEBUG) {
3241 					logger.debug("attack from " + this.getID() + " to "
3242 							+ defender.getID() + ": Damage: " + damage);
3243 				}
3244 
3245 				result = true;
3246 			} else {
3247 				// The attack was too weak, it was blocked
3248 
3249 				if (logger.isDebugEnabled() || Testing.DEBUG) {
3250 					logger.debug("attack from " + this.getID() + " to "
3251 							+ defender.getID() + ": Damage: " + 0);
3252 				}
3253 			}
3254 			this.addEvent(new AttackEvent(true, damage, nature, weaponName, isRanged));
3255 
3256 			// Try to inflict a status effect
3257 			for (StatusAttacker statusAttacker : statusAttackers) {
3258 				statusAttacker.onHit(defender, this, damage);
3259 			}
3260 
3261 		} else {
3262 			// Missed
3263 			if (logger.isDebugEnabled() || Testing.DEBUG) {
3264 				logger.debug("attack from " + this.getID() + " to "
3265 						+ defender.getID() + ": Missed");
3266 			}
3267 
3268 			this.addEvent(new AttackEvent(false, 0, nature, weaponName, isRanged));
3269 		}
3270 
3271 		this.notifyWorldAboutChanges();
3272 		return result;
3273 	}
3274 
applyDefXP(final RPEntity entity)3275 	protected void applyDefXP(final RPEntity entity) {
3276 		// implemented in sub classes
3277 	}
3278 
3279 	/**
3280 	 * Calculate lifesteal and update hp of source.
3281 	 *
3282 	 * @param attacker
3283 	 *            the RPEntity doing the hit
3284 	 * @param attackerWeapons
3285 	 *            the weapons of the RPEntity doing the hit
3286 	 * @param damage
3287 	 *            the damage done by this hit.
3288 	 */
handleLifesteal(final RPEntity attacker, final List<Item> attackerWeapons, final int damage)3289 	public void handleLifesteal(final RPEntity attacker,
3290 			final List<Item> attackerWeapons, final int damage) {
3291 
3292 		// Calculate the lifesteal value based on the configured factor
3293 		// In case of a lifesteal weapon used together with a non-lifesteal
3294 		// weapon,
3295 		// weight it based on the atk-values of the weapons.
3296 		float sumAll = 0;
3297 		float sumLifesteal = 0;
3298 
3299 		// Creature with lifesteal profile?
3300 		if (attacker instanceof Creature) {
3301 			sumAll = 1;
3302 			final String value = ((Creature) attacker)
3303 					.getAIProfile("lifesteal");
3304 			if (value == null) {
3305 				// The creature doesn't steal life.
3306 				return;
3307 			}
3308 			sumLifesteal = Float.parseFloat(value);
3309 		} else {
3310 			// weapons with lifesteal attribute for players
3311 			for (final Item weaponItem : attackerWeapons) {
3312 				sumAll += weaponItem.getAttack();
3313 				if (weaponItem.has("lifesteal")) {
3314 					sumLifesteal += weaponItem.getAttack()
3315 							* weaponItem.getDouble("lifesteal");
3316 				}
3317 			}
3318 		}
3319 
3320 		// process the lifesteal
3321 		if (sumLifesteal != 0) {
3322 			// 0.5f is used for rounding
3323 			final int lifesteal = (int) (damage * sumLifesteal / sumAll + 0.5f);
3324 
3325 			if (lifesteal >= 0) {
3326 				attacker.heal(lifesteal, true);
3327 			} else {
3328 				/*
3329 				 * Negative lifesteal means that we hurt ourselves.
3330 				 */
3331 				attacker.damage(-lifesteal, attacker);
3332 			}
3333 
3334 			attacker.notifyWorldAboutChanges();
3335 		}
3336 	}
3337 
3338 	/**
3339 	 * Equips the item in the specified slot.
3340 	 *
3341 	 * @param rpslot
3342 	 * @param item
3343 	 * @return true if successful*/
equipIt(final RPSlot rpslot, final Item item)3344 	private boolean equipIt(final RPSlot rpslot, final Item item) {
3345 		if (rpslot  == null || (item == null)) {
3346 			return false;
3347 		}
3348 
3349 		if (item instanceof StackableItem) {
3350 			final StackableItem stackEntity = (StackableItem) item;
3351 			// find a stackable item of the same type
3352 			for (final RPObject object : rpslot) {
3353 				if (object instanceof StackableItem) {
3354 					// found another stackable
3355 					final StackableItem other = (StackableItem) object;
3356 					if (other.isStackable(stackEntity)) {
3357 						// other is the same type...merge them
3358 						new ItemLogger().merge(this, stackEntity, other);
3359 						other.add(stackEntity);
3360 						updateItemAtkDef();
3361 						return true;
3362 					}
3363 				}
3364 			}
3365 		}
3366 
3367 		// We can't stack it on another item. Check if we can simply
3368 		// add it to an empty cell.
3369 		if (rpslot.isFull()) {
3370 			return false;
3371 		} else {
3372 			rpslot.add(item);
3373 			updateItemAtkDef();
3374 			return true;
3375 		}
3376 	}
3377 
3378 	/**
3379 	 * Gets the name of the player who deserves the corpse.
3380 	 *
3381 	 * @return name of player who deserves the corpse or <code>null</code>.
3382 	 */
getCorpseDeserver()3383 	public String getCorpseDeserver() {
3384 		return null;
3385 	}
3386 
3387 	/**
3388 	 * gets the language
3389 	 *
3390 	 * @return language
3391 	 */
getLanguage()3392 	public String getLanguage() {
3393 		return null;
3394 	}
3395 
3396 	/**
3397 	 * Sets the sound played at entity death
3398 	 *
3399 	 * @param sound Name of sound
3400 	 */
setDeathSound(final String sound)3401 	public void setDeathSound(final String sound) {
3402 		deathSound = sound;
3403 	}
3404 
3405 	/**
3406 	 * @return Name of sound played at entity death
3407 	 */
getDeathSound()3408 	public String getDeathSound() {
3409 		return deathSound;
3410 	}
3411 
3412 	/**
3413 	 * Add a status attack type to the entity
3414 	 *
3415 	 * @param statusAttacker Status attacker
3416 	 */
addStatusAttacker(final StatusAttacker statusAttacker)3417 	public void addStatusAttacker(final StatusAttacker statusAttacker) {
3418 		// the immutable statusAttackers list is shared between multiple instances of Creatures to reduce memory usage
3419 		Builder<StatusAttacker> builder = ImmutableList.builder();
3420 		statusAttackers = builder.addAll(statusAttackers).add(statusAttacker).build();
3421 	}
3422 
3423 	/**
3424 	 * gets the status list
3425 	 *
3426 	 * @return StatusList
3427 	 */
getStatusList()3428 	public StatusList getStatusList() {
3429 		if (statusList == null) {
3430 			statusList = new StatusList(this);
3431 		}
3432 		return statusList;
3433 	}
3434 
3435 	/**
3436 	 * Find if the entity has a specified status
3437 	 *
3438 	 * @param statusType the status type to check for
3439 	 * @return true, if the entity has status; false otherwise
3440 	 */
hasStatus(StatusType statusType)3441 	public boolean hasStatus(StatusType statusType) {
3442 		if (statusList == null) {
3443 			return false;
3444 		}
3445 		return statusList.hasStatus(statusType);
3446 	}
3447 
3448 	@Override
onRemoved(StendhalRPZone zone)3449 	public void onRemoved(StendhalRPZone zone) {
3450 		super.onRemoved(zone);
3451 
3452 		// Stop other creatures and players attacks on me.
3453 		// Probably I am dead, and I don't want to die again with a second corpse.
3454 		for (Entity attacker : new LinkedList<>(attackSources)) {
3455 			if (attacker instanceof RPEntity) {
3456 				((RPEntity) attacker).stopAttack();
3457 			}
3458 		}
3459 	}
3460 
3461 	/**
3462 	 * Gets an items as a stream of items, followed by any contained items
3463 	 * recursively.
3464 	 *
3465 	 * @param item
3466 	 * @return stream of items
3467 	 */
itemStream(Item item)3468 	private Stream<Item> itemStream(Item item) {
3469 		Stream<Item> stream = Stream.of(item);
3470 		if (item.slots().isEmpty()) {
3471 			return stream;
3472 		}
3473 		Stream<RPSlot> slots = item.slots().stream();
3474 		Stream<Item> internalItems = slots.flatMap(this::slotStream);
3475 		return Stream.concat(stream, internalItems);
3476 	}
3477 
3478 	/**
3479 	 * Get a stream of all items in a slot.
3480 	 *
3481 	 * @param slot
3482 	 * @return items in the slot
3483 	 */
slotStream(RPSlot slot)3484 	private Stream<Item> slotStream(RPSlot slot) {
3485 		Stream<RPObject> objects = StreamSupport.stream(slot.spliterator(), false);
3486 		Stream<Item> items = objects.filter(Item.class::isInstance).map(Item.class::cast);
3487 		return items.flatMap(this::itemStream);
3488 	}
3489 
3490 	/**
3491 	 * Get a stream of all equipped items.
3492 	 *
3493 	 * @return equipped items
3494 	 */
equippedStream()3495 	private Stream<Item> equippedStream() {
3496 		Stream<String> slotNames = Slots.CARRYING.getNames().stream();
3497 		Stream<RPSlot> slots = slotNames.map(this::getSlot).filter(Objects::nonNull);
3498 		return slots.flatMap(this::slotStream);
3499 	}
3500 
3501 	/**
3502 	 * A convenience method for getting a method for matching item names.
3503 	 *
3504 	 * @param name name to match
3505 	 * @return a predicate for matching the name
3506 	 */
nameMatches(String name)3507 	private Predicate<Item> nameMatches(String name) {
3508 		return it -> name.equals(it.getName());
3509 	}
3510 
3511 	/**
3512 	 * Sets the attribute to define the shadow that the client should use.
3513 	 *
3514 	 * @param st
3515 	 * 		String name of the shadow to use.
3516 	 */
setShadowStyle(final String st)3517 	public void setShadowStyle(final String st) {
3518 		if (st == null || st.equals("none")) {
3519 			remove("shadow_style");
3520 			put("no_shadow", "");
3521 			return;
3522 		}
3523 
3524 		put("shadow_style", st);
3525 		remove("no_shadow");
3526 	}
3527 }
3528