1 //
2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
4 //
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
8 // any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 // more details.
14 //
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 // Suite 330, Boston, MA 02111-1307 USA
18 //
19 // Gordon McNutt
20 // gmcnutt@users.sourceforge.net
21 //
22 #include "character.h"
23 #include "conv.h"
24 #include "dice.h"
25 #include "effect.h"
26 #include "gob.h"
27 #include "map.h"
28 #include "console.h"
29 #include "place.h"
30 #include "Container.h"
31 #include "Arms.h"
32 #include "player.h"
33 #include "status.h"
34 #include "sound.h"
35 #include "common.h"
36 #include "screen.h"
37 #include "knapsack.h"
38 #include "occ.h"
39 #include "species.h"
40 #include "sched.h"
41 #include "combat.h"
42 #include "cmdwin.h"
43 #include "terrain.h"
44 #include "cmd.h"
45 #include "event.h"
46 #include "vehicle.h"
47 #include "foogod.h"
48 #include "ctrl.h"
49 #include "session.h"
50 #include "sprite.h"
51 #include "mmode.h"
52 #include "log.h"
53 #include "factions.h"
54 #include "kern_intvar.h"
55
56 #include <string.h>
57 #include <stdlib.h>
58 #include <math.h>
59
60 /**
61 * Used when calculating an evasion vector away from hostiles.
62 */
63 struct evasionVectorInfo {
64 class Character *subj; /**< The character doing the evading. */
65 int dx; /**< The x component of the evasion vector. */
66 int dy; /**< The y component of the evasion vector. */
67 };
68
wrapReady(void * item,void * context)69 static int wrapReady(void *item, void *context)
70 {
71 class ArmsType *type = (class ArmsType *) item;
72 class Character *npc = (class Character *) context;
73 return (npc->hasAmmo(type) && npc->ready(type) == Character::Readied);
74 }
75
wrapUnready(void * item,void * context)76 static void wrapUnready(void *item, void *context)
77 {
78 class ArmsType *type = (class ArmsType *) item;
79 class Character *npc = (class Character *) context;
80 npc->unready(type);
81
82 }
83
myConsiderArms(struct inv_entry * ie,void * data)84 static void myConsiderArms(struct inv_entry *ie, void *data)
85 {
86 struct knapsack *ks = (struct knapsack *) data;
87 if (!ie->type->isType(ARMS_TYPE_ID))
88 return;
89 class ArmsType *arms = (class ArmsType *) ie->type;
90 int val = 2 * dice_average(arms->getDamageDice()) * arms->getRange()
91 + 2 * dice_average(arms->getArmorDice())
92 + 2 * dice_average(arms->getToDefendDice())
93 + dice_average(arms->getToHitDice())
94 ;
95 if (val) {
96 for (int i = 0;
97 i < ie->count && ks->n_items < MAX_N_ITEMS;
98 i++, ks->n_items++) {
99 ks->item[ks->n_items] = arms;
100 ks->value[ks->n_items] = val;
101 }
102 }
103 }
104
105 /**
106 * Accumulate the sum of the vectors from hostile foes. This is passed as an
107 * argument to place_for_each() to scan for hostiles. It sums up the vectors
108 * from hostile foes to the subject in order to calculate the best vector to
109 * flee.
110 *
111 * @param obj An object in the current place.
112 * @param data The evasion vector struct which refers to the calling object and
113 * accumulates results.
114 */
getEvasionVectorVisitor(Object * obj,void * data)115 static void getEvasionVectorVisitor(Object *obj, void *data)
116 {
117 struct evasionVectorInfo *info;
118 class Character *subj;
119
120 info = (struct evasionVectorInfo *)data;
121 subj = info->subj;
122
123 /* Filter out non-beings */
124 if (obj->getLayer() != being_layer)
125 return;
126
127 /* Filter out non-hostiles */
128 if (! are_hostile(subj, (class Being*)obj))
129 return;
130
131 #if 0
132 /* Cheat a bit here, both for performance and to increase the
133 * effectiveness of townsfolk trying to flee raiders. */
134
135 /* Filter out objects not in los of the subject */
136 if (! place_in_los(subj->getPlace(),subj->getX(),subj->getY(),
137 obj->getPlace(),obj->getX(),obj->getY()))
138 return;
139
140 /* Filter out object not in the vision radius of the subject */
141 if (place_flying_distance(subj->getPlace(),subj->getX(),subj->getY(),
142 obj->getX(),obj->getY())
143 > subj->getVisionRadius())
144 return;
145
146 /* Filter out invisible objects */
147 if (! obj->isVisible())
148 return;
149 #endif
150 /* Add the vector going from the hostile to the subject to the
151 * cumulative vector. */
152 info->dx += (subj->getX() - obj->getX());
153 info->dy += (subj->getX() - obj->getX());
154 }
155
Character(const char * tag,const char * name,struct sprite * sprite,struct species * species,struct occ * occ,int str,int intl,int dex,int hpmod,int hpmult,int mpmod,int mpmult,int hp,int xp_,int mp,int AP_per_round,int lvl)156 Character::Character(const char *tag, const char *name,
157 struct sprite *sprite,
158 struct species *species, struct occ *occ,
159 int str, int intl, int dex,
160 int hpmod, int hpmult,
161 int mpmod, int mpmult,
162 int hp, int xp_,
163 int mp,
164 int AP_per_round, // aka speed
165 int lvl
166 )
167 : hm(0), xp(xp_), order(-1),
168 sleeping(false),
169 ac(0),
170 str(str), intl(intl),
171 dex(dex), mana(mp), lvl(lvl),
172 solo(false), target(NULL),
173 rdyArms(NULL),
174 fleeing(false), burden(0),
175 inCombat(false),
176 container(NULL),
177 //sprite(sprite),
178 sched_chars_node(0),
179 forceContainerDrop(false)
180 , fleePathFound(false)
181 , fleeX(0)
182 , fleeY(0)
183 , fleePathFlags(0)
184 , currentMmode(0)
185 , known(false)
186 , taskname(NULL)
187 , taskproc(NULL)
188 , taskgob(NULL)
189 , taskInterruptOnDamage(false)
190 {
191 if (tag) {
192 this->tag = strdup(tag);
193 assert(this->tag);
194 } else {
195 this->tag = NULL;
196 }
197
198 setName(name);
199
200 plnode = NULL;
201 setPlayerControlled(false); // by default
202 this->current_sprite = sprite;
203 this->light = MIN_PLAYER_LIGHT;
204 this->party = 0;
205 this->conv = conv;
206 this->species = species;
207 this->occ = occ;
208 if (occ)
209 occ_ref(occ);
210 this->is_clone = false;
211 this->visible = 1;
212 this->target = 0;
213 this->damage_sound = NULL_SOUND;
214 this->charmed = false;
215 this->resting = false;
216 this->loitering = false;
217 this->guarding = false;
218 this->mp_mod = mpmod;
219 this->mp_mult = mpmult;
220 this->hp_mod = hpmod;
221 this->hp_mult = hpmult;
222 this->sched = NULL;
223 this->appt = 0;
224 this->is_leader = false;
225 this->hp = hp;
226 this->defenseBonus = 0;
227 factionSwitch = 0;
228 tmpFaction = NIL_FACTION;
229 ambushedWhileCamping = false;
230
231 setDead(hp <= 0);
232
233 setActivity(NONE);
234
235
236 initCommon();
237
238 this->hp = min(this->hp, getMaxHp());
239 this->mana = min(this->mana, getMaxMana());
240 this->AP_per_round = AP_per_round;
241
242 setOnMap(false);
243 if (xp == 0)
244 this->xp = getXpForLevel(this->lvl);
245 if (xp < 0)
246 {
247 if (this->lvl == 1)
248 {
249 this->xp = rand() % getXpForLevel(this->lvl + 1);
250 }
251 else
252 {
253 this->xp = getXpForLevel(this->lvl) + (rand() % getXpForLevel(this->lvl));
254 }
255 }
256 }
257
Character()258 Character::Character():hm(0), xp(0), order(-1),
259 sleeping(false),
260 ac(0),
261 str(0), intl(0),
262 dex(0), mana(0),
263 AP_per_round(kern_intvar_get("AP_TOTAL:normal_human")),
264 lvl(0),
265 playerControlled(true), solo(false),
266 target(NULL),
267 rdyArms(NULL),
268 fleeing(false), burden(0),
269 inCombat(false),
270 container(NULL),
271 //sprite(0),
272 sched_chars_node(0),
273 forceContainerDrop(false)
274 , fleePathFound(false)
275 , fleeX(0)
276 , fleeY(0)
277 , fleePathFlags(0)
278 , currentMmode(0)
279 , known(false)
280 , taskname(NULL)
281 , taskproc(NULL)
282 , taskgob(NULL)
283 {
284 // This method is probably obsolete now
285
286 plnode = NULL;
287 setPlayerControlled(false); // by default
288 setBaseFaction(NIL_FACTION);
289
290 light = MIN_PLAYER_LIGHT;
291 tag = 0;
292 party = 0;
293 conv = 0;
294 species = 0;
295 occ = 0;
296 is_clone = false;
297 visible = 1;
298 occ = 0;
299 target = 0;
300 damage_sound = NULL_SOUND;
301 charmed = false;
302 resting = false;
303 loitering = false;
304 guarding = false;
305 mp_mod = 0;
306 mp_mult = 0;
307 hp_mod = 0;
308 hp_mult = 0;
309 sched = NULL;
310 appt = 0;
311 is_leader = false;
312 factionSwitch= 0;
313 tmpFaction = NIL_FACTION;
314 ambushedWhileCamping = false;
315
316 AP_per_round = AP_per_round;
317
318 setDead(hp <= 0);
319
320 //assert(place);
321
322 setActivity(NONE);
323
324 // ------------------------------------------------------------------
325 // Initially always off-map. Loader will position us, put us in a
326 // party, or whatever.
327 // ------------------------------------------------------------------
328
329 setPlace(0);
330 setX(-1);
331 setY(-1);
332 setOnMap(false);
333 }
334
335
~Character()336 Character::~Character()
337 {
338 obj_dec_ref_safe(container);
339
340 if (rdyArms != NULL) {
341 free(rdyArms);
342 }
343
344
345 if (party)
346 party->removeMember(this);
347
348 if (ai)
349 closure_unref(ai);
350
351 /* Hack: make sure you deref occ after deleting the
352 * container. Currently the container references traps that are built
353 * into the occ struct. The container unrefs the traps, and if the occ
354 * isn't still holding a ref then you get a double-deallocation and
355 * usually a crash. This will get cleaned up when I rework the way NPCs
356 * are factoried; the traps won't be kept as part of occ any more. */
357 if (occ)
358 occ_unref(occ);
359
360 // subtle: use setAttackTarget to unref the target; it will do so
361 // safely even if 'this' is the target
362 if (target)
363 setAttackTarget(NULL);
364
365 /* remove self from session's special list for chars with multi-place
366 * schedules */
367 if (sched_chars_node)
368 session_rm_sched_char(sched_chars_node);
369 }
370
yes_no_ignore(struct KeyHandler * kh,int key,int keymod)371 static int yes_no_ignore(struct KeyHandler * kh, int key, int keymod)
372 {
373 int *yesno = (int *) kh->data;
374
375 switch (key) {
376 case 'y':
377 case 'Y':
378 *yesno = 'y';
379 return 1;
380 case 'i':
381 case 'I':
382 *yesno = 'i';
383 return 1;
384 case 'n':
385 case 'N':
386 case CANCEL:
387 *yesno = 'n';
388 return 1;
389 default:
390 return 0;
391 }
392 }
393
taskPromptToAbort()394 void Character::taskPromptToAbort()
395 {
396 cmdwin_clear();
397 log_continue("^c+y%s damaged - abort task?^c-\n", getName());
398 log_flush();
399 cmdwin_spush("Abort");
400 cmdwin_spush("(Y/N/Ignore)");
401 int key;
402 getkey(&key, yes_no_ignore);
403 cmdwin_pop();
404 switch (key) {
405 case 'y':
406 taskAbort();
407 cmdwin_spush("abort!");
408 break;
409 case 'i':
410 taskInterruptOnDamage = false;
411 cmdwin_spush("ignore");
412 break;
413 case 'n':
414 default:
415 cmdwin_spush("no");
416 break;
417 }
418 }
419
damage(int amount)420 void Character::damage(int amount)
421 {
422 if (hp <= 0)
423 return;
424
425 // This will run the "on-damage-hook":
426 Object::damage(amount);
427
428 // setHP() might call kill(), which calls remove(), which will destroy
429 // most objects
430 obj_inc_ref(this);
431 setHp(hp - amount);
432
433 if (isPlayerControlled()) {
434 statusFlash(getOrder(), Red);
435 if (!isDead()
436 && engagedInTask()
437 && taskInterruptOnDamage
438 ) {
439 taskPromptToAbort();
440 }
441 } else {
442 if (!isDead() && inCombat && (getHp() < (getMaxHp() / 4))) {
443 setFleeing(true);
444 }
445 }
446 obj_dec_ref(this);
447 }
448
inflictDamage(int amount,class Character * attacker)449 void Character::inflictDamage(int amount, class Character *attacker)
450 {
451 bool mightkill = false;
452 if (!isDead())
453 {
454 mightkill = true;
455 harm_relations(attacker, this);
456 }
457 obj_inc_ref(this);
458 damage(amount);
459 if (mightkill && isDead())
460 {
461 attacker->addExperience(getExperienceValue());
462 }
463 obj_dec_ref(this);
464 }
465
ready(class ArmsType * arms)466 enum Character::ReadyResult Character::ready(class ArmsType * arms)
467 {
468 bool foundSlotType = false;
469 int slot = arms->getSlotMask();
470
471 if (burden + arms->getWeight() > getStrength())
472 return TooHeavy;
473
474 for (int i = 0; i < species->n_slots; i++) {
475
476 // Is the slot the right type?
477 if ((slot & species->slots[i]) == 0)
478 continue;
479
480 foundSlotType = true;
481
482 // Is the slot occupied?
483 if (rdyArms[i] != NULL)
484 continue;
485
486 // At this point we've found an empty slot of the correct
487 // type. If this is a two-handed item then we also need the
488 // next slot to be empty.
489 if (arms->getNumHands() == 2) {
490
491 if (i >= species->n_slots - 1)
492 continue;
493
494 // Is the slot occupied?
495 if (rdyArms[i + 1] != NULL)
496 continue;
497
498 // Is the slot the right type?
499 if ((slot & species->slots[i + 1]) == 0)
500 continue;
501
502 rdyArms[i + 1] = arms;
503 }
504 // Ready the item. Recalculate armour class.
505 rdyArms[i] = arms;
506 burden += arms->getWeight();
507
508 // Run the "ready" hook, passing in the arms type being readied
509 // and the (first) changed equipment slot.
510 runHook(OBJ_HOOK_READY_EQUIP, "pd", arms, i);
511
512 // Bugfix: for party members, have to change the party
513 // inventory ref here. cmdReady() used to do this, but the
514 // script often calls directly here for things like disarm() or
515 // acid effects.
516 if (isPlayerPartyMember()) {
517 player_party->refInventoryObject(arms);
518 }
519
520 // Bugfix: for others, take the item out of personal
521 // inventory.
522 else if (container) {
523 container->takeOut(arms, 1);
524 }
525
526 return Readied;
527 }
528
529 if (foundSlotType)
530 return NoAvailableSlot;
531 return WrongType;
532 }
533
unready(class ArmsType * arms)534 bool Character::unready(class ArmsType * arms)
535 {
536 for (int i = 0; i < species->n_slots; i++) {
537
538 // Is it in this slot?
539 if (rdyArms[i] != arms)
540 continue;
541
542 // Is this a 2h item (in which case it should be in the next
543 // slot, also)?
544 if (arms->getNumHands() == 2) {
545
546 assert(i < species->n_slots - 1);
547 assert(rdyArms[i + 1] == arms);
548 rdyArms[i + 1] = NULL;
549 }
550 // Unready the item. Recacalculate armour class.
551 rdyArms[i] = NULL;
552 burden -= arms->getWeight();
553
554 // Run the "uunready" hook, passing in the arms type being
555 // unreadied and the (first) changed equipment slot.
556 runHook(OBJ_HOOK_UNREADY_EQUIP, "pd", arms, i);
557
558 // Bugfix: for party members, have to change the party
559 // inventory ref here. cmdReady() used to do this, but the
560 // script often calls directly here for things like disarm() or
561 // acid effects.
562 if (isPlayerPartyMember()) {
563 player_party->unrefInventoryObject(arms);
564 }
565
566 // Bugfix: for others, put the item back into personal
567 // inventory. This fixes (e.g.) Haxima's acid effect on NPC's,
568 // which first unreadies, and then removes, which is correct
569 // for PC's.
570 else if (container) {
571 container->add(arms, 1);
572 }
573
574 return true;
575 }
576
577
578 return false;
579 }
580
getWoundDescription()581 const char *Character::getWoundDescription()
582 {
583 static const char *desc[] = {
584 "Critical",
585 "Heavily wounded",
586 "Moderately wounded",
587 "Barely wounded",
588 "Unscathed"
589 };
590
591 if (isDead())
592 return "Killed";
593
594 if (isFleeing())
595 return "Fleeing";
596
597 return desc[(getHp() * 4) / getMaxHp()];
598 return desc[(getHp() * 4) / getMaxHp()];
599 }
600
groupExitTo(struct place * dest_place,int dest_x,int dest_y,struct closure * cutscene)601 void Character::groupExitTo(struct place *dest_place, int dest_x, int dest_y,
602 struct closure *cutscene)
603 {
604 struct place *oldPlace = getPlace();
605
606 player_party->removeMembers();
607
608 // --------------------------------------------------------------------
609 // If the party is in a vehicle check if we need to disembark before
610 // exiting. If the destination is not wilderness or it's impassable
611 // wilderness then we'll disembark.
612 //
613 // When we disembark, we want to put the vehicle on the parent place
614 // of the place we're leaving.
615 // --------------------------------------------------------------------
616
617 class Vehicle *vehicle = party->getVehicle();
618 if (vehicle
619 && (! place_is_wilderness(dest_place)
620 || ! place_is_passable(dest_place, dest_x, dest_y,
621 party, 0))) {
622
623 assert(getPlace());
624 assert(getPlace()->location.place);
625
626 vehicle->setOccupant(0);
627 vehicle->relocate(getPlace()->location.place,
628 getPlace()->location.x,
629 getPlace()->location.y);
630 vehicle = NULL;
631 }
632
633
634 if (cutscene) {
635 mapUpdate(0);
636 closure_exec(cutscene, "");
637 }
638
639 // --------------------------------------------------------------------
640 // If combat is active then run its state machine after removing
641 // everybody.
642 // --------------------------------------------------------------------
643
644 //if (combat_get_state() != COMBAT_STATE_DONE) {
645 combat_analyze_results_of_last_turn();
646 //}
647
648 place_exit(oldPlace);
649
650 player_party->relocate(dest_place, dest_x, dest_y, REL_NOSTEP);
651 endTurn();
652 }
653
move(int dx,int dy)654 enum MoveResult Character::move(int dx, int dy)
655 {
656 int newx, newy;
657 class Character *occupant;
658
659 if (isStationary())
660 return StationaryObject;
661
662 this->dx = dx;
663 this->dy = dy;
664
665 // ------------------------------------------------------------------
666 // Let's give this next a try, in order to make the code for teleport
667 // spells simpler. If a teleport spell says to teleport the caster,
668 // then perhaps it shouldn't have to concern itself with whether the
669 // caster is in party mode or member mode. It just tells the caster to
670 // move, and the caster then checks its context to see if this means
671 // "move the member" or "move the whole party".
672 // ------------------------------------------------------------------
673
674 if (!isOnMap()) {
675 if (isPlayerControlled()) {
676 return (player_party->move(dx, dy));
677 } else {
678 // I don't know how I got here, but I did with an NPC
679 // in wilderness combat, so its party was NULL. Maybe
680 // it was summoned?
681 dbg("%s not on map and not player-controlled...WTF?\n");
682 if (! party)
683 return OffMap;
684 return (party->move(dx, dy) ? MovedOk : WasImpassable);
685 }
686 }
687
688 // Calculate new coordinates.
689 newx = getX() + dx;
690 newy = getY() + dy;
691
692
693 // ------------------------------------------------------------------
694 // Is the character walking off the edge of the map? The same rules as
695 // entering a portal apply here: the party must be in follow mode, and
696 // all other members must be able to pathfind to this location.
697 //
698 // Addendum: since this is not strictly necessary for wilderness
699 // combat, and it is something of an inconvenience to the user, I skip
700 // the checks for wilderness combat.
701 // ------------------------------------------------------------------
702
703 if (place_off_map(getPlace(), newx, newy)) {
704
705 // -----------------------------------------------------------
706 // Npc characters can just step off and will be removed from
707 // the game.
708 // -----------------------------------------------------------
709
710 // Note: the following used to be || isCharmed(), but since I
711 // changed charm to be just a temporary faction switch I don't
712 // think this is good any more. I don't want charmed player
713 // party members getting destroyed.
714 if (! isPlayerControlled()) {
715 remove();
716 destroy();
717 endTurn();
718 return ExitedMap;
719 }
720
721 if (place_is_wilderness_combat(getPlace())) {
722 remove();
723 endTurn();
724 return ExitedMap;
725 }
726
727 if (player_party->get_num_living_members() == 1) {
728 // Force to follow mode to avoid the annoying case
729 // where only one member is in the party and the player
730 // wants to leave a combat map.
731 player_party->enableFollowMode();
732 }
733
734 /* Let's try switching the order used to look for an exit
735 * place. Instead of checking the parent first, check for a
736 * neighbor first. */
737
738 // Look for a neighbor in that direction
739 int dir = place_off_map_dir(getPlace(),newx, newy);
740 struct place *dest_place = place_get_neighbor(getPlace(), dir);
741 if (dest_place != NULL) {
742
743 int dest_x, dest_y;
744
745 // Check if the neighbor has an edge entrance
746 if (place_get_edge_entrance(dest_place, dir, &dest_x,
747 &dest_y)) {
748 return NoDestination;
749 }
750
751 // For parties of size 1, force to follow mode
752 if (player_party->getSize() == 1) {
753 player_party->enableFollowMode();
754 }
755
756 // Ensure in follow mode
757 if (player_party->getPartyControlMode()
758 != PARTY_CONTROL_FOLLOW) {
759 return NotFollowMode;
760 }
761
762 // Rendezvous other party members or abort
763 if (!player_party->rendezvous(getPlace(), getX(),
764 getY())) {
765 return CantRendezvous;
766 }
767
768 // Goto neighbor
769 groupExitTo(dest_place, dest_x, dest_y, NULL);
770 endTurn();
771 return ExitedMap;
772 }
773
774
775 if (place_get_parent(getPlace()) != NULL) {
776
777 if (player_party->getPartyControlMode()
778 != PARTY_CONTROL_FOLLOW) {
779 return NotFollowMode;
780 }
781
782 if (!player_party->rendezvous(getPlace(), getX(),
783 getY())) {
784 return CantRendezvous;
785 }
786
787 groupExitTo(place_get_parent(getPlace()),
788 place_get_x(getPlace()) + dx,
789 place_get_y(getPlace()) + dy,
790 NULL);
791
792 endTurn();
793 return ExitedMap;
794 }
795
796 return OffMap;
797 }
798
799 // ------------------------------------------------------------------
800 // Check passability. If commuting then ignore closed doors (and other
801 // blocking mechs).
802 // ------------------------------------------------------------------
803
804 if (!place_move_is_passable(
805 getPlace(), getX(), getY(), newx, newy, this,
806 PFLAG_MOVEATTEMPT |
807 (getActivity() == COMMUTING ? PFLAG_IGNOREMECHS : 0))) {
808 return WasImpassable;
809 }
810
811 // Are the new coordinates already occupied by another character?
812 if ((occupant = (class Character *) place_get_object(getPlace(),
813 newx, newy,
814 being_layer))) {
815
816
817 // Is the occupant an enemy?
818 if (are_hostile(this, occupant)) {
819 return WasImpassable;
820 }
821
822 // *** Switch ***
823
824 if (isPlayerControlled()
825
826 // don't allow switching with sleeping NPC's; this would
827 // allow the player to kick THEM out of bed!
828 && SLEEPING != occupant->getActivity()
829 //&& occupant->isPlayerControlled()
830 //&& isSelected()
831 ) {
832
833 // Special case: if both the occupant and this
834 // character are player-controlled then have them
835 // switch places if possible. This prevents the
836 // situation where the player party is in follow mode
837 // and the leader gets boxed in by the other members.
838 // Note that in this case I'm ignoring movement cost
839 // (pretend that the characters help each other across
840 // the rough terrain... yeah, that's it!).
841
842 // Wait - first have to check if the other character
843 // can occupy this tile (may have different
844 // passability). If this fails then go ahead and
845 // approve the move without switching. Stacking members
846 // of the same party is permitted in order to resolve
847 // certain corner cases which could be very unpleasant
848 // or confusing for the user. (For example: the party
849 // leader is a gazer, the party enters a dungeon, the
850 // portal destination is surrounded by water, the gazer
851 // steps onto the water... oops! Either we allow the
852 // gazer to stack back onto the party or we require the
853 // user to figure out how to switch the party order
854 // just to pick a new leader just to get out of this
855 // mess...)
856 if (!place_is_passable(getPlace(), getX(), getY(),
857 occupant, 0)) {
858 relocate(getPlace(), newx, newy);
859 runHook(OBJ_HOOK_MOVE_DONE, "pdd", getPlace(),
860 newx, newy);
861 decActionPoints(
862 place_get_diagonal_movement_cost(
863 getPlace(),
864 getX(), getY(),
865 newx, newy,
866 this, (getActivity() == COMMUTING ? PFLAG_IGNOREMECHS : 0)));
867 return MovedOk;
868
869 }
870
871 // In follow mode only the leader can switch with other
872 // party members, otherwise two or more followers can
873 // spin-in-place.
874 if (occupant->isPlayerControlled()
875 && CONTROL_MODE_FOLLOW == getControlMode()) {
876 return WasOccupied;
877 }
878
879 switchPlaces(occupant);
880 return SwitchedOccupants;
881 }
882
883 return WasOccupied;
884 }
885
886 runHook(OBJ_HOOK_MOVE_DONE, "pdd", getPlace(), getX(), getY());
887 decActionPoints(place_get_diagonal_movement_cost(getPlace(),
888 getX(), getY(),
889 newx,
890 newy, this, (getActivity() == COMMUTING ? PFLAG_IGNOREMECHS : 0)));
891 relocate(getPlace(), newx, newy);
892
893 // If this move was visible to the player
894 if (mapTileIsVisible(getX(), getY())) {
895 // And this is a party member following the player
896 if (isPlayerPartyMember()
897 && (CONTROL_MODE_FOLLOW == getControlMode())) {
898 // Don't update the map now, just mark it dirty. This
899 // helps reduce the sluggishness in town maps.
900 mapSetDirty();
901 } else {
902 // If this character is being moved by the player and
903 // they are not in view of the camera
904 if (isPlayerControlled()
905 && ! mapIsInCameraView(getPlace(), getX(), getY())) {
906 // Recenter the camera to keep the character in view.
907 mapCenterCamera(getX(), getY());
908 }
909 // Repaint the map now
910 mapUpdate(0);
911 }
912 }
913
914 return MovedOk;
915 }
916
remove()917 void Character::remove()
918 {
919 obj_inc_ref(this);
920 Object::remove();
921 setAttackTarget(this);
922 mapSetDirty();
923
924 // ------------------------------------------------------------------
925 // Handle changes to party control.
926 // ------------------------------------------------------------------
927
928 if (isSolo()) {
929 player_party->enableRoundRobinMode();
930 } else if (isLeader()) {
931 player_party->enableFollowMode();
932 }
933 obj_dec_ref(this);
934 }
935
enumerateWeapons(int * armsIndex)936 class ArmsType *Character::enumerateWeapons(int *armsIndex)
937 {
938 *armsIndex = -1;
939 class ArmsType *currentArms;
940 currentArms = getNextWeapon(armsIndex);
941 if (!currentArms)
942 currentArms = species->weapon;
943 return currentArms;
944 }
945
getNextWeapon(int * armsIndex)946 class ArmsType *Character::getNextWeapon(int *armsIndex)
947 {
948 class ArmsType *currentArms;
949 do {
950 currentArms = getNextArms(armsIndex);
951 } while (currentArms != NULL
952 && dice_average(currentArms->getDamageDice()) <= 0);
953 return currentArms;
954 }
955
956 /* If target is null, then it cant be in range.
957 If weapon is null, then theres no range limitation, so target is
958 considered 'in range' */
isAttackTargetInRange(class ArmsType * weapon)959 bool Character::isAttackTargetInRange(class ArmsType *weapon)
960 {
961 int dx, dy, distance;
962
963 if (target == NULL)
964 return false;
965
966 if (weapon == NULL)
967 return true;
968
969 dx = target->getX() - getX();
970 dy = target->getY() - getY();
971
972 if (dx < 0)
973 dx = -dx;
974 if (dy < 0)
975 dy = -dy;
976
977 distance = (dx > dy) ? (dx + (dy >> 1)) : (dy + (dx >> 1));
978
979 return (weapon->getRange() >= distance);
980 }
981
getAttackTarget(class ArmsType * weapon)982 class Character *Character::getAttackTarget(class ArmsType *weapon)
983 {
984 // Is the old target still valid?
985 if (!target || !target->isOnMap() || target->isDead() ||
986 !isAttackTargetInRange(weapon) || !target->isVisible()) {
987 setAttackTarget(this);
988 return this;
989 }
990
991 return target;
992 }
993
hasReadied(class ArmsType * arms)994 bool Character::hasReadied(class ArmsType * arms)
995 {
996 int armsIndex = -1;
997 class ArmsType *readied = enumerateArms(&armsIndex);
998 while (readied != NULL && readied != arms)
999 readied = getNextArms(&armsIndex);
1000 return readied == arms;
1001 }
1002
enumerateArms(int * armsIndex)1003 class ArmsType *Character::enumerateArms(int *armsIndex)
1004 {
1005 *armsIndex = -1;
1006 return getNextArms(armsIndex);
1007 }
1008
getNextArms(int * armsIndex)1009 class ArmsType *Character::getNextArms(int *armsIndex)
1010 {
1011 // Advance to the next slot
1012 // *armsIndex++ doesnt work for some reason...
1013 *armsIndex=*armsIndex+1;
1014
1015 // Search remaining slots for a weapon
1016 for (; *armsIndex < species->n_slots; *armsIndex=*armsIndex+1) {
1017
1018 // Is anything in this slot?
1019 if (rdyArms[*armsIndex] == NULL)
1020 continue;
1021
1022 // Is this just another slot for the same weapon (happens in
1023 // the case of multi-slotted weapons like 2h swords)?
1024 if (*armsIndex > 0 && rdyArms[*armsIndex-1] == rdyArms[*armsIndex] &&
1025 rdyArms[*armsIndex]->getNumHands() == 2)
1026 continue;
1027
1028 return rdyArms[*armsIndex];
1029 }
1030
1031 return 0;
1032 }
1033
getArmsInSlot(int slot)1034 ArmsType *Character::getArmsInSlot(int slot)
1035 {
1036 if (slot < 0
1037 || slot >= species->n_slots)
1038 return NULL;
1039 return rdyArms[slot];
1040 }
1041
hasAmmo(class ArmsType * weapon)1042 int Character::hasAmmo (class ArmsType * weapon)
1043 {
1044 // SAM: Changed this from returning bool to
1045 // returning int (0 for no ammo, n for amount)
1046 if (weapon->ammoIsUbiquitous())
1047 return 1; // One more available, that is.
1048
1049 if (isPlayerControlled()) {
1050 struct inv_entry *ie;
1051
1052 if (weapon->isMissileWeapon()) {
1053 ie = player_party->inventory->search(weapon->getAmmoType());
1054 if (ie == NULL)
1055 return 0; // No ammo
1056 return ie->count; // 1 or more
1057 }
1058 else if (weapon->isThrownWeapon()) {
1059 ie = player_party->inventory->search(weapon);
1060 if (ie == NULL) {
1061 unready(weapon);
1062 return 0; // No more
1063 }
1064 assert(ie->count > 0);
1065 return ie->count; // 1 or more
1066 }
1067 return 1; // Melee weapons are like ubiquitous
1068 } else {
1069 // SAM: Not bothering with quantity of NPC ammo for now
1070 return (!weapon->isMissileWeapon() ||
1071 (container != NULL &&
1072 container->search(weapon->getAmmoType())));
1073 }
1074 } // Character::hasAmmo()
1075
hasInInventory(class ObjectType * type)1076 bool Character::hasInInventory (class ObjectType *type)
1077 {
1078 if (isPlayerControlled()) {
1079 return player_party->hasInInventory(type);
1080 } else {
1081 return (container != NULL &&
1082 container->search(type) != NULL);
1083 }
1084 }
1085
setLight(int val)1086 void Character::setLight(int val)
1087 {
1088 light = max(val, MIN_PLAYER_LIGHT);
1089 mapSetDirty();
1090 }
1091
addMana(int delta)1092 void Character::addMana(int delta)
1093 {
1094 mana += delta;
1095 mana = max(mana, 0);
1096 mana = min(mana, getMaxMana());
1097 }
1098
changeSleep(bool val)1099 void Character::changeSleep(bool val)
1100 {
1101 if (sleeping == val)
1102 return;
1103
1104 sleeping = val;
1105 statusRepaint();
1106 mapSetDirty();
1107
1108 if (sleeping) {
1109
1110 // -----------------------------------------------------------
1111 // Going to sleep.
1112 // -----------------------------------------------------------
1113 if (engagedInTask()) {
1114 taskAbort();
1115 }
1116
1117
1118 if (isLeader()) {
1119 assert(isPlayerControlled());
1120 player_party->enableFollowMode();
1121 } else if (isSolo()) {
1122 assert(isPlayerControlled());
1123 player_party->enableRoundRobinMode();
1124 }
1125
1126 } else {
1127
1128 if (isPlayerControlled()) {
1129
1130 // ----------------------------------------------------
1131 // Upon waking up, set this character's control mode
1132 // based on the party's control mode.
1133 // ----------------------------------------------------
1134
1135 assert(! isLeader());
1136 assert(! isSolo());
1137
1138 switch (player_party->getPartyControlMode()) {
1139
1140 case PARTY_CONTROL_FOLLOW:
1141 if (this != player_party->get_leader()) {
1142 setControlMode(CONTROL_MODE_FOLLOW);
1143 }
1144 break;
1145
1146 case PARTY_CONTROL_SOLO:
1147 setControlMode(CONTROL_MODE_IDLE);
1148 break;
1149
1150 case PARTY_CONTROL_ROUND_ROBIN:
1151 setControlMode(CONTROL_MODE_PLAYER);
1152 break;
1153
1154 default:
1155 assert(false);
1156 break;
1157 }
1158
1159 }
1160
1161 }
1162
1163 }
1164
awaken(void)1165 void Character::awaken(void)
1166 {
1167 if (isAsleep()
1168 && ! isResting()
1169 && ! isDead()) {
1170 changeSleep(false);
1171 log_msg("%s wakes up!", getName());
1172 }
1173 }
1174
setFleeing(bool val)1175 void Character::setFleeing(bool val)
1176 {
1177 if (fleeing == val)
1178 return;
1179
1180 fleeing = val;
1181 fleePathFound = false;
1182 fleePathFlags = 0;
1183 cachedPath = 0;
1184 }
1185
1186 /**
1187 * Check if it's a good idea to try and move here in the given place. Checks
1188 * for passability and known hazards.
1189 */
locationIsOk(int x2,int y2)1190 bool Character::locationIsOk(int x2, int y2)
1191 {
1192 return (place_is_passable(getPlace(), x2, y2, this, 0)
1193 && (! place_is_occupied(getPlace(), x2, y2))
1194 && (! place_is_hazardous(getPlace(), x2, y2)));
1195 }
1196
1197 /**
1198 * Compute the optimal vector to move away from hostiles.
1199 *
1200 * @param dx The x component of the resulting evasion vector.
1201 * @param dy The y component of the resulting evasion vector.
1202 */
getEvasionVector(int * dx,int * dy)1203 void Character::getEvasionVector(int *dx, int *dy)
1204 {
1205 struct evasionVectorInfo info;
1206 int x2, y2;
1207
1208 /* Get the vector away from foes. */
1209 memset(&info, 0, sizeof(info));
1210 info.subj = this;
1211 place_for_each_object(getPlace(), getEvasionVectorVisitor, &info);
1212
1213 /* Normalize the vector. */
1214 clamp(info.dx, -1, 1);
1215 clamp(info.dy, -1, 1);
1216
1217 /* Compute the new location. */
1218 x2 = getX() + info.dx;
1219 y2 = getY() + info.dy;
1220
1221 /* Check if ok. */
1222 if (locationIsOk(x2, y2)) {
1223 goto done;
1224 }
1225
1226 /* Not ok, try the vertical. */
1227 if (info.dx) {
1228 x2 -= info.dx;
1229 if (locationIsOk(x2, y2)) {
1230 goto done;
1231 }
1232 x2 += info.dx;
1233 }
1234
1235 /* Not ok, try the horizontal. */
1236 if (info.dy) {
1237 y2 -= info.dy;
1238 if (locationIsOk(x2, y2)) {
1239 goto done;
1240 }
1241 y2 += info.dx;
1242 }
1243
1244 /* No place to go. */
1245 info.dx = 0;
1246 info.dy = 0;
1247
1248 done:
1249 *dx = info.dx;
1250 *dy = info.dy;
1251 }
1252
movedOk(enum MoveResult result)1253 static bool movedOk(enum MoveResult result)
1254 {
1255 switch (result) {
1256 case NotApplicable:
1257 case NoDestination:
1258 case WasImpassable:
1259 case WasOccupied:
1260 return false;
1261 default:
1262 return true;
1263 }
1264 }
1265
mapHasEdge()1266 bool Character::mapHasEdge()
1267 {
1268 // This is the simple thing to do, and works for towns and wilderness
1269 // maps. Dungeon rooms could have edges shared with other rooms, but I
1270 // don't want to deal with that right now, mostly because it
1271 // complicates the pathfinding heuristics, and I want to get this case
1272 // right first.
1273 return (place_get_parent(getPlace()));
1274 }
1275
exitMap()1276 bool Character::exitMap()
1277 {
1278 if (getX() == 0) {
1279 return movedOk(move(-1, 0));
1280 } else if (getX() == (place_w(getPlace()) - 1)) {
1281 return movedOk(move(1, 0));
1282 } else if (getY() == 0) {
1283 return movedOk(move(0, -1));
1284 } else if (getY() == (place_h(getPlace()) - 1)) {
1285 return movedOk(move(0, 1));
1286 } else {
1287 return false;
1288 }
1289 }
1290
followFleePath()1291 bool Character::followFleePath()
1292 {
1293 return (exitMap()
1294 || pathfindTo(getPlace(), fleeX, fleeY, fleePathFlags));
1295 }
1296
findFleePath()1297 bool Character::findFleePath()
1298 {
1299 // Just look for the nearest map edge. Note that this might not be the
1300 // best choice, because it might not have a path. Get this working
1301 // first, then get fancy if you want.
1302 int leftx = getX();
1303 int rightx = place_w(getPlace()) - getX();
1304 int boty = getY();
1305 int topy = place_h(getPlace()) - getY();
1306 int minx, miny;
1307
1308 // Is the left edge nearer than the right edge?
1309 if (leftx < rightx) {
1310 fleeX = 0;
1311 minx = leftx;
1312 } else {
1313 fleeX = place_w(getPlace()) - 1;
1314 minx = leftx;
1315 }
1316
1317 // Is the top edge nearer than the bottom edge?
1318 if (boty < topy) {
1319 fleeY = 0;
1320 miny = topy;
1321 } else {
1322 fleeY = place_h(getPlace()) - 1;
1323 miny = boty;
1324 }
1325
1326 // Is the vertical edge nearer than the horizontal edge?
1327 if (minx < miny) {
1328 fleePathFlags = PFLAG_HORZ;
1329 } else {
1330 fleePathFlags = PFLAG_VERT;
1331 }
1332
1333 fleePathFound = true;
1334 return true;
1335 }
1336
pathfindToMapEdge()1337 bool Character::pathfindToMapEdge()
1338 {
1339 if (fleePathFound
1340 && followFleePath()) {
1341 return true;
1342 }
1343
1344 if (! findFleePath()) {
1345 return false;
1346 }
1347
1348 return followFleePath();
1349 }
1350
fleeToMapEdge()1351 bool Character::fleeToMapEdge()
1352 {
1353 return (mapHasEdge()
1354 && pathfindToMapEdge());
1355 }
1356
evade()1357 bool Character::evade()
1358 {
1359 int dx = 0, dy = 0;
1360 getEvasionVector(&dx, &dy);
1361 if (!dx && !dy) {
1362 return false;
1363 }
1364
1365 return movedOk(move(dx, dy));
1366 }
1367
flee()1368 bool Character::flee()
1369 {
1370 return (!isStationary()
1371 && (fleeToMapEdge()
1372 || evade()));
1373 }
1374
dropRdyArms()1375 void Character::dropRdyArms()
1376 {
1377 assert(!isPlayerControlled());
1378
1379 for (int i = 0; i < species->n_slots; i++) {
1380
1381 // Anything in this slot?
1382 if (rdyArms[i] == NULL)
1383 continue;
1384
1385 // roll to drop
1386 if ((rand() % 100) > ITEM_DROP_PROB)
1387 continue;
1388
1389 // Create an object of this type and drop it on the map
1390 class Object *object = new Object();
1391 if (!object)
1392 continue;
1393 object->init(rdyArms[i]);
1394 object->relocate(getPlace(), getX(), getY());
1395
1396 // Unready it
1397 unready(rdyArms[i]);
1398
1399 }
1400 }
1401
unreadyAll()1402 void Character::unreadyAll()
1403 {
1404 for (int i = 0; i < species->n_slots; i++) {
1405
1406 class ArmsType *type = rdyArms[i];
1407
1408 // Anything in this slot?
1409 if (!type)
1410 continue;
1411
1412 // Unready it
1413 unready(type);
1414 }
1415 }
1416
dropItems()1417 bool Character::dropItems()
1418 {
1419 assert(!isPlayerControlled());
1420
1421 if (container == NULL)
1422 return false;
1423
1424 if (container->isEmpty())
1425 return true;
1426
1427 if (! container->isEmpty()
1428 && (forceContainerDrop
1429 || (rand() % 100) <= CHEST_DROP_PROB))
1430 container->relocate(getPlace(), getX(), getY());
1431 obj_dec_ref(container);
1432 container = NULL;
1433
1434 return true;
1435 }
1436
kill()1437 void Character::kill()
1438 {
1439 // Why not turn this on again...? At least dropItems?
1440 // if (!isPlayerControlled() && isOnMap()) {
1441 // dropRdyArms();
1442 // dropItems();
1443 // }
1444
1445 // when a PC dies unready all arms so other party members can use them
1446 if (isPlayerControlled()) {
1447 unreadyAll();
1448 }
1449
1450 if (isOnMap()
1451 && container
1452 && forceContainerDrop) {
1453 container->relocate(getPlace(), getX(), getY());
1454 obj_dec_ref(container);
1455 container = NULL;
1456 }
1457
1458 if (engagedInTask()) {
1459 taskAbort();
1460 }
1461
1462 if (isPlayerControlled()) {
1463 log_msg("%s has fallen!!", getName());
1464 }
1465
1466 hp = 0;
1467 setDead(true);
1468
1469 // Similarly, run the on-death hook
1470 runHook(OBJ_HOOK_ON_DEATH, 0);
1471 remove();
1472 }
1473
useAmmo(class ArmsType * weapon)1474 void Character::useAmmo(class ArmsType *weapon)
1475 {
1476 if (weapon->ammoIsUbiquitous())
1477 return;
1478
1479 if (isPlayerControlled()) {
1480 struct inv_entry *ie;
1481
1482 if (weapon->isMissileWeapon()) {
1483 class ObjectType *ammoType = weapon->getAmmoType();
1484 takeOut(ammoType, 1);
1485 } else if (weapon->isThrownWeapon()) {
1486
1487 // Handle the case where multiple party members have
1488 // all readied the same throwable weapon, and there is
1489 // no longer enough to supply them all, by forcing the
1490 // character that just threw to unready.
1491 ie = player_party->inventory->search(weapon);
1492 assert(ie);
1493 assert(ie->ref <= ie->count);
1494 if (ie->ref == ie->count) {
1495 unready(weapon);
1496 log_msg("%s : %s now out of ammo\n",
1497 getName(), weapon->getName());
1498 }
1499 takeOut(weapon, 1);
1500 }
1501 } else {
1502 if (weapon->isMissileWeapon()) {
1503 container->takeOut(weapon->getAmmoType(), 1);
1504 if (!hasAmmo(weapon)) {
1505 unready(weapon);
1506 rearm = true;
1507 }
1508 } else if (weapon->isThrownWeapon()) {
1509 if (container && container->search(weapon)) {
1510 container->takeOut(weapon, 1);
1511 } else {
1512 unready(weapon);
1513 rearm = true;
1514 }
1515 }
1516 }
1517 }
1518
1519 /*****************************************************************************/
1520
armThyself(void)1521 void Character::armThyself(void)
1522 {
1523 struct knapsack ks;
1524
1525 //assert(!isPlayerControlled());
1526
1527 if (container == NULL)
1528 return;
1529
1530 // Setup the context to solve this using the knapsack algorithm
1531 memset(&ks, 0, sizeof(ks));
1532 ks.item = new void *[MAX_N_ITEMS];
1533 ks.value = new int[MAX_N_ITEMS];
1534 ks.solution = new unsigned char[MAX_N_ITEMS];
1535 if (!ks.item || !ks.value || !ks.solution)
1536 goto destroy_ks;
1537 ks.put = wrapReady;
1538 ks.remove = wrapUnready;
1539 ks.context = this;
1540
1541 container->forEach(myConsiderArms, &ks);
1542
1543 knapsack_solve(&ks);
1544
1545 // Ready the chosen items
1546 for (int i = 0; i < ks.n_items; i++) {
1547 if (!ks.solution[i])
1548 continue;
1549 class ArmsType *arms = (class ArmsType *) ks.item[i];
1550 if (ready(arms) != Character::Readied)
1551 continue;
1552 }
1553
1554 destroy_ks:
1555 if (ks.item)
1556 delete [] ks.item;
1557 if (ks.value)
1558 delete [] ks.value;
1559 if (ks.solution)
1560 delete [] ks.solution;
1561
1562 rearm = false;
1563 }
1564
needToRearm()1565 bool Character::needToRearm()
1566 {
1567 // gmcnutt: I currently don't have any way to tell a charmed player
1568 // party member how to rearm themselves. Player characters pull from
1569 // party inventory. Non-player characters pull from their own personal
1570 // inventory. I really need to merge both types into the same behaviour
1571 // to simplify things.
1572 if (party == (Party*)player_party)
1573 return false;
1574
1575 return rearm;
1576 }
1577
initCommon(void)1578 bool Character::initCommon(void)
1579 {
1580 if (species && species->n_slots > 0) {
1581 rdyArms = (class ArmsType**)calloc(species->n_slots, sizeof(class ArmsType*));
1582 assert(rdyArms);
1583 }
1584
1585 ai = NULL;
1586
1587 return true;
1588 }
1589
initStock(struct species * species,struct occ * occ,struct sprite * sprite,char * nameStr,int order)1590 bool Character::initStock(struct species * species, struct occ * occ,
1591 struct sprite * sprite, char *nameStr, int order)
1592 {
1593 // This method is now only used to initialize cloned characters.
1594
1595 this->species = species;
1596 this->occ = occ;
1597 if (occ)
1598 occ_ref(occ);
1599 this->current_sprite = sprite;
1600
1601 if (!initCommon())
1602 return false;
1603
1604 setName(nameStr);
1605 this->order = order;
1606
1607 lvl = 1; // fixme: hardcoded hack!
1608
1609 hp = getMaxHp();
1610 mana = getMaxMana();
1611 AP_per_round = species->spd;
1612 setDead(false);
1613
1614 defenseBonus = 0;
1615
1616 return true;
1617 }
1618
1619 /*****************************************************************************/
1620
resurrect(void)1621 void Character::resurrect(void)
1622 {
1623 assert(isPlayerControlled());// shotgun assert put here during refactor
1624 setHp(min(10, getMaxHp()));
1625 setDead(false);
1626 statusFlash(getOrder(), Blue);
1627
1628 // ------------------------------------------------------------------
1629 // If we're in wilderness mode then we're done. Otherwise we need to
1630 // put this character near the other party member's on the map.
1631 // ------------------------------------------------------------------
1632
1633 if (player_party->isOnMap())
1634 return;
1635
1636 assert(player_party->get_leader());
1637
1638 putOnMap(player_party->get_leader()->getPlace(),
1639 player_party->get_leader()->getX(),
1640 player_party->get_leader()->getY(), 4,
1641 0);
1642
1643 assert(isOnMap());
1644
1645 }
1646
1647
getSprite()1648 struct sprite *Character::getSprite()
1649 {
1650 if ((isAsleep() || isDead()) && species->sleep_sprite)
1651 return species->sleep_sprite;
1652 return current_sprite;
1653 }
1654
rest(int hours)1655 void Character::rest(int hours)
1656 {
1657 int healAmount = HP_RECOVERED_PER_HOUR_OF_REST;
1658 int manaAmount = MANA_RECOVERED_PER_HOUR_OF_REST;
1659
1660 /* Partial fix for SF BUG [ 1526910 ] "starvation is lame". Don't let
1661 * the player completely rest his way out of starvation. */
1662 if (isPlayerPartyMember()) {
1663 if (!player_party->food) {
1664 healAmount /= 2;
1665 manaAmount /= 2;
1666 }
1667 }
1668
1669 while (hours) {
1670 if (!isDead()) {
1671 heal(healAmount);
1672 addMana(manaAmount);
1673 }
1674 hours--;
1675 }
1676 }
1677
getExperienceValue()1678 int Character::getExperienceValue()
1679 {
1680 int xpval = 0;
1681 if (species)
1682 xpval += species->xpval;
1683 if (occ)
1684 xpval += occ->xpval;
1685 return (xpval * lvl);
1686 }
1687
getXpForLevel(int lvl)1688 int Character::getXpForLevel(int lvl)
1689 {
1690 if (lvl == 1)
1691 return 0;
1692 return (int)pow((double)2, (double)lvl+6);
1693 }
1694
addExperience(int amount)1695 void Character::addExperience(int amount)
1696 {
1697 xp += amount;
1698 if (xp >= getXpForLevel(getLevel()+1)) {
1699 lvl++;
1700 log_banner("^c+b%s^c- gains level ^c+g%d^c-!", getName(), lvl);
1701 if (isPlayerControlled()) {
1702 mapFlash(1000);
1703 }
1704 setHp(getMaxHp());
1705 setMana(getMaxMana());
1706 }
1707 }
1708
getMaxHp()1709 int Character::getMaxHp()
1710 {
1711 int base = hp_mod + species->hp_mod;
1712 int mult = hp_mult + species->hp_mult;
1713
1714 if (occ) {
1715 base += occ->hp_mod;
1716 mult += occ->hp_mult;
1717 }
1718
1719 mult = max(0, mult);
1720
1721 return (base + getLevel() * mult);
1722 }
1723
getMaxMana()1724 int Character::getMaxMana()
1725 {
1726 int base = mp_mod + species->mp_mod;
1727 int mult = mp_mult + species->mp_mult;
1728
1729 if (occ) {
1730 base += occ->mp_mod;
1731 mult += occ->mp_mult;
1732 }
1733
1734 mult = max(0, mult);
1735 return base + getLevel() * mult;
1736 }
1737
changeArmourClass(int delta)1738 void Character::changeArmourClass(int delta)
1739 {
1740 ac += delta;
1741 ac = max(0, ac);
1742 }
1743
clone()1744 class Object *Character::clone()
1745 {
1746 char buf[64];
1747 class Character *clone = new Character();
1748 if (!clone)
1749 return NULL;
1750
1751 if (is_clone)
1752 snprintf(buf, sizeof(buf), "%s", getName());
1753 else
1754 snprintf(buf, sizeof(buf), "%s (clone)", getName());
1755
1756 clone->initStock(species, occ, current_sprite, buf, 0);
1757 clone->is_clone = true;
1758
1759 // clone the readied items
1760 int armsIndex = 0;
1761 for (ArmsType *arms = enumerateArms(&armsIndex); arms != NULL; arms = getNextArms(&armsIndex)) {
1762 clone->ready(arms);
1763 }
1764
1765 // NOTE: effects not cloned... that will require a bit of work and
1766 // testing because effects can have gobs (gobs aren't cloned either)
1767
1768 return clone;
1769 }
1770
isVisible()1771 bool Character::isVisible()
1772 {
1773 return ((visible > 0) && species->visible);
1774 }
1775
isShaded()1776 bool Character::isShaded()
1777 {
1778 // Friendly invisible characters are shaded
1779 return ((! isVisible() && isPlayerControlled())
1780 || Object::isShaded());
1781 }
1782
describe()1783 void Character::describe()
1784 {
1785 if (Session->subject) {
1786 const char *diplstr = diplomacy_string(this, Session->subject);
1787 if (isvowel(diplstr[0]))
1788 log_continue("an");
1789 else
1790 log_continue("a");
1791 log_continue(" %s", diplstr);
1792 } else {
1793 log_continue("an");
1794 }
1795 log_continue(" L%d", getLevel());
1796 if (isKnown()) {
1797 log_continue(" %s", getName());
1798 } else {
1799 if (species && species->name) {
1800 log_continue(" %s", species->name);
1801 }
1802 if (occ && occ->name) {
1803 log_continue(" %s", occ->name);
1804 }
1805 }
1806 if (!isVisible())
1807 log_continue(" (invisible)");
1808 if (isSubmerged()) {
1809 log_continue(" (submerged)");
1810 }
1811 }
1812
examine()1813 void Character::examine()
1814 {
1815 int i;
1816 int n = 0;
1817 const char *diplstr = diplomacy_string(this, Session->subject);
1818
1819 log_continue("%s level %d", diplstr, getLevel());
1820
1821 if (isKnown()) {
1822 log_continue(" %s,", getName());
1823 } else {
1824 if (species && species->name) {
1825 log_continue(" %s", species->name);
1826 }
1827 if (occ && occ->name) {
1828 log_continue(" %s", occ->name);
1829 }
1830 }
1831
1832 log_continue(" %s [", getWoundDescription());
1833
1834 for (ArmsType *arms = enumerateArms(&i); arms;
1835 arms = getNextArms(&i)) {
1836 if (n > 0) {
1837 log_continue(", ");
1838 }
1839 log_continue("%s", arms->getName());
1840 n++;
1841 }
1842
1843 if (!n) {
1844 log_continue("no arms");
1845 }
1846
1847 log_continue("]");
1848 }
1849
getDamageSound()1850 sound_t *Character::getDamageSound()
1851 {
1852 if (damage_sound)
1853 return damage_sound;
1854 if (species && species->damage_sound)
1855 return species->damage_sound;
1856 return NULL_SOUND;
1857 }
1858
get_movement_sound()1859 sound_t *Character::get_movement_sound()
1860 {
1861 if (species)
1862 return species->movement_sound;
1863 return NULL_SOUND;
1864 }
1865
isType(int classID)1866 bool Character::isType(int classID) {
1867 if (classID == CHARACTER_ID)
1868 return true;
1869 return Object::isType(classID);
1870 }
1871
getType()1872 int Character::getType() {
1873 return CHARACTER_ID;
1874 }
1875
getHp()1876 int Character::getHp() {
1877 return hp;
1878 }
1879
getOrder()1880 int Character::getOrder() {
1881 return order;
1882 }
1883
getExperience()1884 int Character::getExperience() {
1885 return xp;
1886 }
1887
getStrength()1888 unsigned char Character::getStrength() {
1889 return (species->str + str);
1890 }
1891
getIntelligence()1892 unsigned char Character::getIntelligence() {
1893 return (species->intl + intl);
1894 }
1895
getDexterity()1896 unsigned char Character::getDexterity() {
1897 return (species->dex + dex);
1898 }
1899
getBaseStrength()1900 unsigned char Character::getBaseStrength() {
1901 return (str);
1902 }
1903
getBaseIntelligence()1904 unsigned char Character::getBaseIntelligence() {
1905 return (intl);
1906 }
1907
getBaseDexterity()1908 unsigned char Character::getBaseDexterity() {
1909 return (dex);
1910 }
1911
setStrength(unsigned char newstat)1912 void Character::setStrength(unsigned char newstat) {
1913 str = newstat;
1914 }
1915
setIntelligence(unsigned char newstat)1916 void Character::setIntelligence(unsigned char newstat) {
1917 intl = newstat;
1918 }
1919
setDexterity(unsigned char newstat)1920 void Character::setDexterity(unsigned char newstat) {
1921 dex = newstat;
1922 }
1923
getLevel()1924 int Character::getLevel() {
1925 return lvl;
1926 }
1927
setLevel(int val)1928 void Character::setLevel(int val) {
1929 assert(val>0);
1930 xp = getXpForLevel(val);
1931 lvl = val;
1932 setHp(getMaxHp());
1933 setMana(getMaxMana());
1934 }
1935
isDead()1936 bool Character::isDead() {
1937 return dead;
1938 }
1939
isAsleep()1940 bool Character::isAsleep() {
1941 return sleeping;
1942 }
1943
isIncapacitated()1944 bool Character::isIncapacitated() {
1945 return (!isOnMap() || isDead() || isAsleep());
1946 }
1947
getArmourClass()1948 int Character::getArmourClass() {
1949 return ac;
1950 }
1951
setHp(int val)1952 void Character::setHp(int val)
1953 {
1954 hp = val;
1955 hp = clamp(hp, 0, getMaxHp());
1956 if (hp == 0) {
1957 kill();
1958 }
1959 }
1960
isPlayerControlled()1961 bool Character::isPlayerControlled() {
1962 return playerControlled;
1963 }
1964
setPlayerControlled(bool val)1965 void Character::setPlayerControlled(bool val)
1966 {
1967 playerControlled = val;
1968 if (val) {
1969 ctrl = ctrl_character_ui;
1970 if (isPlayerPartyMember()) {
1971 class Character *oldLeader, *newLeader;
1972 switch (player_party->getPartyControlMode()) {
1973 case PARTY_CONTROL_FOLLOW:
1974 oldLeader = player_party->get_leader();
1975 player_party->chooseNewLeader();
1976 newLeader = player_party->get_leader();
1977 if (oldLeader != newLeader) {
1978 oldLeader->setControlMode(CONTROL_MODE_FOLLOW);
1979 }
1980 if (newLeader != this) {
1981 setControlMode(CONTROL_MODE_FOLLOW);
1982 }
1983 break;
1984 case PARTY_CONTROL_SOLO:
1985 setControlMode(CONTROL_MODE_IDLE);
1986 break;
1987 case PARTY_CONTROL_ROUND_ROBIN:
1988 setControlMode(CONTROL_MODE_PLAYER);
1989 break;
1990 }
1991 }
1992 } else {
1993 ctrl = ctrl_character_ai;
1994 if (isPlayerPartyMember()) {
1995 if (isLeader()) {
1996 player_party->chooseNewLeader();
1997 assert(this != player_party->get_leader());
1998 }
1999 if (isSolo()) {
2000 setSolo(false);
2001 player_party->enableFollowMode();
2002 }
2003 if (engagedInTask()) {
2004 taskAbort();
2005 }
2006 }
2007 setControlMode(CONTROL_MODE_AUTO);
2008 }
2009 }
2010
setAttackTarget(class Character * newtarget)2011 void Character::setAttackTarget(class Character * newtarget)
2012 {
2013 if (target == newtarget)
2014 return;
2015
2016 if (target && target != this)
2017 obj_dec_ref(target);
2018
2019 target = newtarget;
2020
2021 if (target && target != this)
2022 obj_inc_ref(target);
2023 }
2024
isSolo()2025 bool Character::isSolo() {
2026 return solo;
2027 }
2028
getLight()2029 int Character::getLight() {
2030 return light;
2031 }
2032
getVisionRadius()2033 int Character::getVisionRadius() {
2034 return species->vr;
2035 }
2036
getSpeed()2037 int Character::getSpeed() {
2038 // Returns the character-specific number of
2039 // Action Points per round for this character.
2040
2041 int total_AP;
2042 int AP_modifier_from_equipped_items = 0;
2043
2044 if (AP_per_round > 0)
2045 total_AP = AP_per_round;
2046 else
2047 total_AP = species->spd;
2048
2049 int armsIndex = 0;
2050 for (class ArmsType * arms = enumerateArms(&armsIndex); arms != NULL;
2051 arms = getNextArms(&armsIndex)) {
2052 AP_modifier_from_equipped_items += arms->get_AP_mod();
2053 }
2054 total_AP += AP_modifier_from_equipped_items;
2055
2056 if (total_AP < 1)
2057 total_AP = 1; // SAM: perhaps revisit this...
2058
2059 return total_AP;
2060 }
2061
setSpeed(int val)2062 int Character::setSpeed(int val) {
2063 AP_per_round = val;
2064 return AP_per_round;
2065 }
2066
getMana()2067 int Character::getMana() {
2068 return mana;
2069 }
2070
setMana(int val)2071 void Character::setMana(int val) {
2072 mana = val;
2073 }
2074
isFleeing()2075 bool Character::isFleeing() {
2076 return fleeing;
2077 }
2078
setOrder(int order)2079 void Character::setOrder(int order) {
2080 this->order = order;
2081 }
2082
setCombat(bool val)2083 void Character::setCombat(bool val) {
2084 inCombat = val;
2085 }
2086
getDefend()2087 int Character::getDefend()
2088 {
2089 int defend = 0;
2090
2091 if (isAsleep())
2092 return -3; // hack: hard-coded constant
2093
2094 int armsIndex=0;
2095 for (class ArmsType * arms = enumerateArms(&armsIndex); arms != NULL;
2096 arms = getNextArms(&armsIndex)) {
2097 defend += dice_roll(arms->getToDefendDice());
2098 }
2099
2100 defend += defenseBonus;
2101
2102 if (isSubmerged()) {
2103 defend += kern_intvar_get("submerged_def_bonus");
2104 }
2105
2106 return defend;
2107 }
2108
getToHitPenalty()2109 int Character::getToHitPenalty()
2110 {
2111 int penalty = 0;
2112
2113 int armsIndex=0;
2114 for (class ArmsType * arms = enumerateArms(&armsIndex);
2115 arms != NULL; arms = getNextArms(&armsIndex)) {
2116 int roll = dice_roll(arms->getToHitDice());
2117 if (roll < 0)
2118 penalty += roll;
2119 }
2120
2121 return penalty;
2122 }
2123
getBaseAttackBonus(class ArmsType * weapon)2124 int Character::getBaseAttackBonus(class ArmsType * weapon)
2125 {
2126 int strbonus = weapon->modifyStrAttack(session_run_query(Session, str_based_attack_query, "p", this));
2127 int dexbonus = weapon->modifyDexAttack(session_run_query(Session, dex_based_attack_query, "p", this));
2128 int totalbonus = (strbonus + dexbonus) / (100 * 1000);
2129 return (1+ totalbonus);
2130 }
2131
getAttackBonus(class ArmsType * weapon)2132 int Character::getAttackBonus(class ArmsType * weapon)
2133 {
2134 return (rand() % getBaseAttackBonus(weapon));
2135 }
2136
getDamageBonus(class ArmsType * weapon)2137 int Character::getDamageBonus(class ArmsType * weapon)
2138 {
2139 int dambonus = weapon->modifyDamageBonus(session_run_query(Session, damage_bonus_query, "p", this))/(100 * 1000);
2140 return (rand() % (1+ dambonus));
2141 }
2142
getAvoidBonus()2143 int Character::getAvoidBonus()
2144 {
2145 //hack: dont get any bonus here if you're asleep on the job
2146 if (isAsleep())
2147 return 0;
2148
2149 int avoidBonus = session_run_query(Session, defense_bonus_query, "p", this);
2150 float avoidMod = 1;
2151
2152 //roundabout way of getting data in order to preserve info for stderr
2153 int armsIndex=0;
2154 for (class ArmsType * arms = enumerateArms(&armsIndex);
2155 arms != NULL; arms = getNextArms(&armsIndex))
2156 {
2157 avoidMod = arms->modifyAvoidBonus(avoidMod);
2158 }
2159
2160 int totalbonus = (int)(avoidBonus * (avoidMod/1000));
2161 return (rand() % (1+ totalbonus));
2162 }
2163
getArmor()2164 int Character::getArmor()
2165 {
2166 int armor = 0;
2167
2168 int armsIndex=0;
2169 for (class ArmsType * arms = enumerateArms(&armsIndex);
2170 arms != NULL; arms = getNextArms(&armsIndex)) {
2171 armor += dice_roll(arms->getArmorDice());
2172 }
2173
2174 // the obsolescent 'armor class' is still used by the 'protect' spell
2175 // effect
2176 armor += ac;
2177
2178 // Some species have an armor bonus
2179 if (species->armor_dice) {
2180 armor += dice_roll(species->armor_dice);
2181 }
2182
2183 return armor;
2184
2185 }
2186
getParty()2187 class Party *Character::getParty()
2188 {
2189 return party;
2190 }
2191
burn()2192 void Character::burn()
2193 {
2194 damage(DAMAGE_FIRE);
2195 log_msg("%s burning-%s!", getName(), getWoundDescription());
2196 }
2197
sleep()2198 void Character::sleep()
2199 {
2200 if (isAsleep())
2201 return;
2202
2203 changeSleep(true);
2204 log_msg("%s sleeping!", getName());
2205 }
2206
canSee(class Object * obj)2207 bool Character::canSee(class Object *obj)
2208 {
2209 return (obj->getPlace() == getPlace() &&
2210 place_flying_distance(getPlace(), getX(), getY(),
2211 obj->getX(), obj->getY()) <=
2212 getVisionRadius() &&
2213 obj->isVisible() &&
2214 place_in_los(getPlace(), getX(), getY(),
2215 obj->getPlace(), obj->getX(), obj->getY()));
2216 }
2217
atAppointment()2218 bool Character::atAppointment()
2219 {
2220 struct appt *curAppt = &sched->appts[appt];
2221
2222 if (getX() >= curAppt->x
2223 && getX() < (curAppt->x + curAppt->w)
2224 && getY() >= curAppt->y
2225 && getY() < (curAppt->y + curAppt->h)) {
2226 return true;
2227 }
2228 return false;
2229 }
2230
playerIsInMyBed()2231 bool Character::playerIsInMyBed()
2232 {
2233 struct appt *curAppt = &sched->appts[appt];
2234
2235 if (SLEEPING!=curAppt->act)
2236 return false;
2237
2238 class Character *sleeper =
2239 (class Character*)place_get_object(getPlace(),
2240 curAppt->x,
2241 curAppt->y,
2242 being_layer);
2243
2244 return (sleeper
2245 && sleeper->isPlayerControlled()
2246 && sleeper->isResting());
2247 }
2248
2249 //return true if character is next to or on the appointment loc
nextToAppointment()2250 bool Character::nextToAppointment()
2251 {
2252 struct appt *curAppt = &sched->appts[appt];
2253
2254 if (abs(curAppt->x - getX())>1)
2255 {
2256 return false;
2257 }
2258 if (abs(curAppt->y - getY())>1)
2259 {
2260 return false;
2261 }
2262
2263 return true;
2264 }
2265
kickPlayerOutOfMyBed()2266 void Character::kickPlayerOutOfMyBed()
2267 {
2268 struct appt *curAppt = &sched->appts[appt];
2269
2270 log_msg("Kicked out of bed!");
2271 player_party->throw_out_of_bed();
2272
2273 // now switch places with whoever is in bed
2274 class Character *sleeper =
2275 (class Character*)place_get_object(getPlace(),
2276 curAppt->x,
2277 curAppt->y,
2278 being_layer);
2279 assert(sleeper);
2280 switchPlaces(sleeper);
2281
2282 assert(atAppointment());
2283 setActivity(curAppt->act);
2284 }
2285
switchPlaces(class Being * occupant)2286 void Character::switchPlaces(class Being *occupant)
2287 {
2288 int oldx = getX();
2289 int oldy = getY();
2290 int newx = occupant->getX();
2291 int newy = occupant->getY();
2292
2293 // Save these before calling remove because remove()
2294 // automatically resets these to defaults (for sane
2295 // reasons... I think)
2296 class Character *oldTarget = target;
2297 struct place *oldPlace = getPlace();
2298 bool wasSolo = isSolo();
2299
2300 //remove();
2301 occupant->relocate(oldPlace, oldx, oldy);
2302 relocate(oldPlace, newx, newy);
2303 decActionPoints(place_get_diagonal_movement_cost(getPlace(),
2304 oldx, oldy,
2305 newx, newy,
2306 this, 0));
2307 setAttackTarget(oldTarget);
2308 setSolo(wasSolo);
2309 }
2310
commute()2311 bool Character::commute()
2312 {
2313 int tx, ty;
2314
2315 // Note: this could be improved a bit by caching the results of the
2316 // rectangle search below. Since the path is cached I'm not sure how
2317 // big of a gain it would be.
2318
2319 struct appt *curAppt = &sched->appts[appt];
2320
2321 // Check if the commute is over
2322 if (atAppointment()) {
2323 setActivity(curAppt->act);
2324 return true;
2325 }
2326
2327 // Else search for an open place in the appointment rectangle
2328 for (ty = curAppt->y; ty < curAppt->y + curAppt->h; ty++) {
2329 for (tx = curAppt->x; tx < curAppt->x + curAppt->w; tx++) {
2330
2331 if (!place_is_passable(getPlace(), tx, ty, this,
2332 PFLAG_IGNOREMECHS) ||
2333 place_is_hazardous(getPlace(), tx, ty))
2334 continue;
2335
2336 // Try to go there. Adjacent-to-there is not good
2337 // enough; we will get stuck repeatedly trying to
2338 // pathfind if the last step is impassable (Bug
2339 // 1734069).
2340 if (!pathfindTo(getPlace(), tx, ty,
2341 PFLAG_ADJACENTNOTOK|PFLAG_IGNOREMECHS)) {
2342 continue;
2343 }
2344
2345 // Check if the commute is over.
2346 if (atAppointment()) {
2347 setActivity(curAppt->act);
2348 }
2349
2350 return true;
2351 }
2352 }
2353
2354 // Special case: if the appointment is the character's bed, and
2355 // pathfinding failed because the player is sleeping in it, then kick
2356 // the player out of bed
2357 if (playerIsInMyBed())
2358 {
2359 if (nextToAppointment())
2360 {
2361 //evict the player if we are close enough
2362 kickPlayerOutOfMyBed();
2363 }
2364 else
2365 {
2366 //try to reach bed, clambering over other beings as necessary
2367 pathfindTo(getPlace(), curAppt->x, curAppt->y, PFLAG_IGNOREBEINGS | PFLAG_IGNOREMECHS);
2368 }
2369 return true;
2370 }
2371
2372 dbg("%s cannot find path to [%d %d %d %d] while commuting\n",
2373 getName(),
2374 sched->appts[appt].x,
2375 sched->appts[appt].y,
2376 sched->appts[appt].w,
2377 sched->appts[appt].h);
2378
2379 return false;
2380 }
2381
synchronize()2382 void Character::synchronize()
2383 {
2384 struct appt *cur_appt = 0;
2385
2386 if (!sched || sched->n_appts == 0)
2387 return;
2388
2389 if (isDead())
2390 return;
2391
2392 cur_appt = sched_get_appointment(sched, Session->clock.hour,
2393 Session->clock.min);
2394
2395 if (getPlace()
2396 && getPlace() != cur_appt->place) {
2397 introduce();
2398 return;
2399 }
2400
2401 /* Iff the character is not already in their appointment rectangle then
2402 * relocate them to the upper left corner of their appointment. The ULC
2403 * is better than the center because it's more obvious to the schedule
2404 * designer that the ULC needs to be passable terrain. If they are
2405 * already in their appointment then don't relocate them, because it
2406 * looks funny when you reload the game.
2407 */
2408 if ((getX() < cur_appt->x)
2409 || (getX() >= (cur_appt->x + cur_appt->w))
2410 || (getY() < cur_appt->y)
2411 || (getY() >= (cur_appt->y + cur_appt->h))) {
2412 relocate(cur_appt->place, cur_appt->x, cur_appt->y);
2413 }
2414 setActivity(cur_appt->act);
2415 appt = cur_appt->index;
2416 }
2417
introduce()2418 void Character::introduce()
2419 {
2420 assert(sched);
2421
2422 if (isDead())
2423 return;
2424
2425 struct appt *newAppt = sched_get_appointment(sched,
2426 Session->clock.hour,
2427 Session->clock.min);
2428 if (getPlace() != newAppt->place) {
2429
2430 // Since this code just "teleports" the character, make sure
2431 // the player isn't looking.
2432
2433 // Introduce a character into the place as part of its
2434 // schedule. For now, just drop it on its location as in
2435 // synchronize(). DO NOT try to use object::relocate(). It's
2436 // seriously overextended and has a bunch of logic that will
2437 // prevent NPC's from relocating.
2438 obj_inc_ref(this);
2439 if (getPlace()) {
2440 place_remove_object(getPlace(), this);
2441 }
2442 setPlace(newAppt->place);
2443 setX(newAppt->x);
2444 setY(newAppt->y);
2445 place_add_object(newAppt->place, this);
2446 obj_dec_ref(this);
2447 setActivity(newAppt->act);
2448 appt = newAppt->index;
2449 }
2450 }
2451
getAppointment()2452 void Character::getAppointment()
2453 {
2454
2455 int nextAppt = appt + 1;
2456
2457 /* Special case: the last appointment of the day is over when the clock
2458 rolls over at midnight. We can detect clock rollover by checking if
2459 the current time is BEFORE the start of the current appt. */
2460 if (nextAppt == sched->n_appts) {
2461 if (Session->clock.hour < sched->appts[appt].hr) {
2462 appt = 0;
2463 }
2464 }
2465
2466 /* Normal case: check if the clock time exceeds the start time of our
2467 next appt. */
2468 else if (Session->clock.hour >= sched->appts[nextAppt].hr &&
2469 Session->clock.min >= sched->appts[nextAppt].min) {
2470 appt = nextAppt;
2471 }
2472
2473 if (atAppointment()) {
2474 setActivity(sched->appts[appt].act);
2475 } else {
2476 setActivity(COMMUTING);
2477 }
2478
2479 }
2480
exec()2481 void Character::exec()
2482 {
2483 int points_last_loop;
2484 class Character *leader;
2485 bool noHostiles = false;
2486 bool appointmentChecked = false;
2487
2488 //printf("exec %s\n", getName());
2489
2490 startTurn();
2491
2492 if (isDead() || ! isOnMap() ||action_points <= 0) {
2493 endTurn();
2494 return;
2495 }
2496
2497 if (isResting()) {
2498
2499 // -----------------------------------------------------------
2500 // Every hour until the wakeup alarm goes off have the
2501 // character rest a little.
2502 //
2503 // The first character to wakeup to the alarm clock will wake
2504 // up the party.
2505 // -----------------------------------------------------------
2506
2507 assert(isAsleep());
2508
2509 if (clock_alarm_is_expired(&rest_alarm)) {
2510 rest(1);
2511 clock_alarm_set(&rest_alarm, 60);
2512 }
2513
2514 if (clock_alarm_is_expired(&wakeup_alarm)) {
2515
2516 if (! isPlayerPartyMember()) {
2517 endResting();
2518 } else {
2519 log_begin_group();
2520 log_msg("Done resting...");
2521 endResting();
2522
2523 if (player_party->isCamping())
2524 player_party->endCamping();
2525 else if (player_party->isResting())
2526 player_party->endResting();
2527
2528 log_end_group();
2529 }
2530 }
2531
2532 endTurn();
2533 return;
2534 }
2535
2536 else if (isGuarding()) {
2537
2538 // -----------------------------------------------------------
2539 // Every hour have the guard repair the vehicle by some amount.
2540 //
2541 // When guarding is over the guard will wake up the party.
2542 // -----------------------------------------------------------
2543
2544 class Vehicle *vehicle = player_party->getVehicle();
2545 if (clock_alarm_is_expired(&rest_alarm)) {
2546 if (isPlayerControlled() &&
2547 vehicle &&
2548 vehicle->getHp() <
2549 vehicle->getMaxHp()) {
2550 vehicle->heal(
2551 vehicle->getMaxHp() /
2552 10);
2553 foogodRepaint();
2554 log_begin("%s repairs ", getName());
2555 vehicle->describe();
2556 log_end(".");
2557 }
2558 clock_alarm_set(&rest_alarm, 60);
2559 }
2560
2561 if (clock_alarm_is_expired(&wakeup_alarm)) {
2562 endGuarding();
2563
2564 if (isPlayerPartyMember()) {
2565 if (player_party->isCamping())
2566 player_party->endCamping();
2567 else if (player_party->isResting())
2568 player_party->endResting();
2569 }
2570 }
2571 endTurn();
2572 return;
2573 }
2574
2575 else if (isLoitering()) {
2576 if (clock_alarm_is_expired(&wakeup_alarm)) {
2577 if (! isPlayerPartyMember()) {
2578 endLoitering();
2579 } else {
2580 log_begin_group();
2581 log_msg("Done loitering...");
2582 endLoitering();
2583
2584 if (player_party->isLoitering())
2585 player_party->endLoitering();
2586 log_end_group();
2587 }
2588 }
2589 endTurn();
2590 return;
2591 }
2592
2593 // ------------------------------------------------------------------
2594 // Check for cases that prevent the character from taking a turn. Note
2595 // that if the character is sleeping he will still take a turn iff the
2596 // sleep is part of his schedule.
2597 // ------------------------------------------------------------------
2598
2599 if (isAsleep() && getActivity() != SLEEPING) {
2600
2601 if (ambushedWhileCamping &&
2602 ((rand() % 100) < PROB_AWAKEN)) {
2603 endCamping();
2604 } else {
2605 endTurn();
2606 return;
2607 }
2608 }
2609
2610 switch (getControlMode()) {
2611
2612 case CONTROL_MODE_AUTO:
2613
2614 // Loop until the turn is over or the character stops using
2615 // action points.
2616 points_last_loop = 0;
2617 while (! isTurnEnded() &&
2618 getActionPoints() != points_last_loop) {
2619 points_last_loop = action_points;
2620
2621 // Lookup this character's schedule. We only need to do
2622 // it once per turn; the clock won't change in the
2623 // loop. But we want to do it inside the loop so that
2624 // if there's no AP the schedule won't change the
2625 // character's state. This was specifically added so
2626 // that An Zu could wake up sleeping NPC's for a few
2627 // rounds by waking them up and socking them with an AP
2628 // debt to keep them docile for a bit.
2629 if (sched && ! appointmentChecked) {
2630 getAppointment();
2631 appointmentChecked = true;
2632 }
2633
2634 switch (getActivity()) {
2635 case COMMUTING:
2636 // pathfind to next appointment
2637 commute();
2638 break;
2639 case EATING:
2640 case SLEEPING:
2641 // do nothing
2642 break;
2643 case FIGHTING:
2644 default:
2645 // call the AI
2646 ctrl(this);
2647 break;
2648 }
2649 }
2650 break;
2651
2652 case CONTROL_MODE_PLAYER:
2653
2654 /* Highlight the character & prompt the user */
2655 select(true);
2656
2657 // Update name in foogod window
2658 if (PARTY_CONTROL_ROUND_ROBIN == player_party->getPartyControlMode()) {
2659 foogod_set_title("Round Robin: %s", getName());
2660 foogodRepaint();
2661 }
2662
2663 // If the character is out-of-site then change the camera to
2664 // focus on the character.
2665 if (! mapIsInCameraView(getPlace(), getX(), getY())) {
2666 mapCenterCamera(getX(), getY());
2667 mapUpdate(0);
2668 }
2669
2670 /* Hand control over to the player */
2671 ctrl(this);
2672
2673 /* Increment the turn count once per combat round. */
2674 if (isSolo() || isLeader()) {
2675 session_inc_turn_count();
2676 foogodRepaint();
2677 }
2678
2679 if (Session->reloaded)
2680 /* Hack: this object has been destroyed. Leave
2681 * now. Don't touch a thing. */
2682 return;
2683
2684 /* Un-highlight the character */
2685 select(false);
2686
2687 break;
2688
2689
2690 case CONTROL_MODE_FOLLOW:
2691
2692 // -----------------------------------------------------------
2693 // Follow the party leader.
2694 // -----------------------------------------------------------
2695
2696 leader = player_party->get_leader();
2697
2698 assert(leader);
2699 assert(this != leader);
2700
2701 // -----------------------------------------------------------
2702 // Loop until the leader is one tile away, we run out of action
2703 // points, or we stop using action points (this last occurs
2704 // when we can't find a path)
2705 // -----------------------------------------------------------
2706
2707 points_last_loop = 0;
2708
2709 // Since this character is in follow mode it's keystroke hook
2710 // hasn't been run yet. Do it now so that effects like
2711 // paralysis work properly on PC's in follow mode.
2712 runHook(OBJ_HOOK_KEYSTROKE, 0);
2713 if (isTurnEnded()) {
2714 break;
2715 }
2716
2717 noHostiles = ! place_contains_hostiles(getPlace(), this);
2718
2719 while (1 < place_flying_distance(Place, getX(), getY(),
2720 leader->getX(),
2721 leader->getY())
2722 && (noHostiles || ! isTurnEnded())
2723 && getActionPoints() != points_last_loop
2724 ) {
2725
2726 points_last_loop = getActionPoints();
2727
2728 // ----------------------------------------------------
2729 // Take a step toward the leader, recompute
2730 // line-of-sight and repaint to show the action.
2731 // ----------------------------------------------------
2732
2733 pathfindTo(leader->getPlace(),
2734 leader->getX(),
2735 leader->getY(),
2736 PFLAG_IGNOREMECHS
2737 |PFLAG_IGNORECOMPANIONS
2738 |PFLAG_IGNORESTEPTRIG
2739 );
2740 mapCenterView(getView(), getX(), getY());
2741 mapSetDirty();
2742 }
2743
2744 // In follow mode don't accumulate action point depth. This
2745 // leads to annoying laggardliness in player party members.
2746 setActionPoints(0);
2747 break;
2748
2749 case CONTROL_MODE_IDLE:
2750 break;
2751
2752 case CONTROL_MODE_TASK:
2753 assert(taskproc);
2754 if (isPlayerControlled()) {
2755 log_msg("%s continues %s...", getName(), getTaskName());
2756 }
2757 closure_exec(taskproc, "pl", this, taskgob->p);
2758 break;
2759
2760 default:
2761 assert(false);
2762 break;
2763 }
2764
2765 endTurn();
2766 }
2767
setSolo(bool val)2768 void Character::setSolo(bool val)
2769 {
2770 if (solo == val)
2771 return;
2772
2773 if (val) {
2774 assert(isPlayerControlled());
2775 if (engagedInTask()) {
2776 taskAbort();
2777 }
2778 solo = val;
2779 attachCamera(true);
2780 setControlMode(CONTROL_MODE_PLAYER);
2781 log_msg("%s goes solo.", getName());
2782 mapCenterCamera(getX(), getY());
2783 mapSetDirty();
2784 } else {
2785 solo = val;
2786 attachCamera(false);
2787 setControlMode(CONTROL_MODE_IDLE);
2788 }
2789 }
2790
unCharm()2791 void Character::unCharm()
2792 {
2793 // Check for illegal request
2794 if (0 == factionSwitch) {
2795 warn("%s:uncharm:factionSwitch 0", getName());
2796 return;
2797 }
2798
2799 // Decrement the faction switch.
2800 factionSwitch--;
2801
2802 // If the faction switch is still on then there are other charms in
2803 // effect and we don't want to disturb them. NOTE: the last faction
2804 // used with charm will remain in effect until the factionSwitch falls
2805 // to zero. This will match most expected behavior related to multiple
2806 // charm effects.
2807 if (factionSwitch)
2808 return;
2809
2810 // Is this an NPC or a party member?
2811 if (! isPlayerPartyMember()) {
2812
2813 // Revert the NPC to AI-control
2814 setControlMode(CONTROL_MODE_AUTO);
2815
2816 // Remove the NPC's map view, if any
2817 if (NULL != getView()) {
2818 rmView();
2819 mapDestroyView(getView());
2820 setView(NULL);
2821 }
2822
2823 // Switch the controller back to the AI
2824 //ctrl = ctrl_character_ai;
2825
2826 } else {
2827
2828 // Set the party member's control mode based on the party's
2829 // current control mode.
2830 switch (player_party->getPartyControlMode()) {
2831 case PARTY_CONTROL_ROUND_ROBIN:
2832 setControlMode(CONTROL_MODE_PLAYER);
2833 break;
2834 case PARTY_CONTROL_SOLO:
2835 setControlMode(CONTROL_MODE_IDLE);
2836 break;
2837 case PARTY_CONTROL_FOLLOW:
2838 if (isLeader()) {
2839 setControlMode(CONTROL_MODE_PLAYER);
2840 } else {
2841 setControlMode(CONTROL_MODE_FOLLOW);
2842 }
2843 break;
2844 }
2845 }
2846 }
2847
charm(int newFaction)2848 void Character::charm(int newFaction)
2849 {
2850
2851 if (isDead())
2852 return;
2853
2854 // Set the temporary faction (possibly clobbering the previous one).
2855 tmpFaction = newFaction;
2856
2857 if (isPlayerPartyMember())
2858 // Switch the party member to be AI-controlled.
2859 setControlMode(CONTROL_MODE_AUTO);
2860 else {
2861 // Add a map view for the non-party member
2862 setView(mapCreateView());
2863 addView();
2864
2865 // Switch the non-party member to be player-controlled
2866 setControlMode(CONTROL_MODE_PLAYER);
2867
2868 // Switch the controller over to the player
2869 // NOTE: this needs a bit more work to be nicely done
2870 // ctrl = ctrl_character_ui;
2871 }
2872
2873 // Increment the faction switch (must do this AFTER calling
2874 // setControlMode())
2875 factionSwitch++;
2876
2877 }
2878
isCharmed()2879 bool Character::isCharmed()
2880 {
2881 return getCurrentFaction() != getBaseFaction();
2882 }
2883
isPlayerPartyMember()2884 bool Character::isPlayerPartyMember()
2885 {
2886 // If player party has not been created yet it's safe to assume that
2887 // for now this is not a party member.
2888 if (!Session || ! player_party) {
2889 return false;
2890 }
2891 return (class Object*)party == (class Object*)player_party;
2892 }
2893
setControlMode(enum control_mode mode)2894 void Character::setControlMode(enum control_mode mode)
2895 {
2896 // -------------------------------------------------------------------
2897 // Player party calls in here to switch between follow, round-robin and
2898 // solo modes. Don't want to change the control mode of charmed
2899 // member's in this case. Always need to uncharm before switching
2900 // control modes.
2901 // -------------------------------------------------------------------
2902
2903 if (isCharmed())
2904 return;
2905
2906 control_mode = mode;
2907
2908 switch (mode) {
2909 case CONTROL_MODE_AUTO:
2910 ctrl = ctrl_character_ai;
2911 break;
2912 case CONTROL_MODE_PLAYER:
2913 case CONTROL_MODE_IDLE:
2914 case CONTROL_MODE_FOLLOW:
2915 ctrl = ctrl_character_ui;
2916 break;
2917 case CONTROL_MODE_TASK:
2918 break;
2919 }
2920 }
2921
2922
add(ObjectType * type,int amount)2923 bool Character::add(ObjectType *type, int amount)
2924 {
2925 if (isPlayerPartyMember()) {
2926 return player_party->add(type, amount);
2927 } else if (container) {
2928 container->add(type, amount);
2929 return true;
2930 }
2931
2932 return false;
2933 }
2934
2935
takeOut(ObjectType * type,int amount)2936 bool Character::takeOut(ObjectType *type, int amount)
2937 {
2938 if (isPlayerPartyMember()) {
2939 return player_party->takeOut(type, amount);
2940 } else if(container) {
2941 return container->takeOut(type, amount);
2942 }
2943 return false;
2944
2945 }
addFood(int quantity)2946 bool Character::addFood(int quantity)
2947 {
2948 if (isPlayerPartyMember()) {
2949 player_party->addFood(quantity);
2950 return true;
2951 }
2952 return false;
2953 }
2954
addGold(int quantity)2955 bool Character::addGold(int quantity)
2956 {
2957 if (isPlayerPartyMember()) {
2958 player_party->addGold(quantity);
2959 return true;
2960 }
2961 return false;
2962 }
2963
beginResting(int hours)2964 void Character::beginResting(int hours)
2965 {
2966 assert(hours > 0);
2967
2968 if (isDead())
2969 return;
2970
2971 clock_alarm_set(&wakeup_alarm, hours * 60);
2972 clock_alarm_set(&rest_alarm, 60);
2973 resting = true;
2974 changeSleep(true);
2975 }
2976
endResting()2977 void Character::endResting()
2978 {
2979 resting = false;
2980 setControlMode(CONTROL_MODE_PLAYER);
2981 awaken();
2982 }
2983
isResting()2984 bool Character::isResting()
2985 {
2986 return resting;
2987 }
2988
beginLoitering(int hours)2989 void Character::beginLoitering(int hours)
2990 {
2991 assert(hours > 0);
2992
2993 if (isDead())
2994 return;
2995
2996 clock_alarm_set(&wakeup_alarm, hours * 60);
2997 loitering = true;
2998 }
2999
endLoitering()3000 void Character::endLoitering()
3001 {
3002 loitering = false;
3003 if (! engagedInTask()) {
3004 setControlMode(CONTROL_MODE_PLAYER);
3005 }
3006 }
3007
isLoitering()3008 bool Character::isLoitering()
3009 {
3010 return loitering;
3011 }
3012
beginCamping(int hours)3013 void Character::beginCamping(int hours)
3014 {
3015 ambushedWhileCamping = false;
3016 beginResting(hours);
3017 }
3018
endCamping()3019 void Character::endCamping()
3020 {
3021 ambushedWhileCamping = false;
3022 endResting();
3023 }
3024
isCamping()3025 bool Character::isCamping()
3026 {
3027 return isResting();
3028 }
3029
ambushWhileCamping()3030 void Character::ambushWhileCamping()
3031 {
3032 resting = false;
3033 ambushedWhileCamping = true;
3034
3035 if (!isAsleep())
3036 return;
3037
3038 if ((rand() % 100) < PROB_AWAKEN) {
3039 endCamping();
3040 }
3041
3042 }
3043
beginGuarding(int hours)3044 void Character::beginGuarding(int hours)
3045 {
3046 assert(hours > 0);
3047 assert(!isAsleep());
3048 assert(!isDead());
3049
3050 clock_alarm_set(&wakeup_alarm, hours * 60);
3051 clock_alarm_set(&rest_alarm, 60);
3052 setControlMode(CONTROL_MODE_IDLE);
3053 guarding = true;
3054 }
3055
endGuarding()3056 void Character::endGuarding()
3057 {
3058 guarding = false;
3059 setControlMode(CONTROL_MODE_PLAYER);
3060 }
3061
isGuarding()3062 bool Character::isGuarding()
3063 {
3064 return guarding;
3065 }
3066
heal(int amount)3067 void Character::heal(int amount)
3068 {
3069 amount = min(amount, getMaxHp() - hp);
3070 hp += amount;
3071 if (isPlayerControlled() && amount)
3072 statusFlash(getOrder(), Blue);
3073 /* fixme: if sufficiently healed should stop fleeing */
3074 }
3075
setLeader(bool val)3076 void Character::setLeader(bool val)
3077 {
3078 if (is_leader == val)
3079 return;
3080
3081 if (is_leader) {
3082 attachCamera(false);
3083 if (isPlayerControlled()) {
3084 setControlMode(CONTROL_MODE_IDLE);
3085 }
3086 } else {
3087 attachCamera(true);
3088 setControlMode(CONTROL_MODE_PLAYER);
3089 //consolePrint("%s is now party leader.\n", getName());
3090 mapSetPlace(getPlace());
3091 mapCenterCamera(getX(), getY());
3092 mapSetDirty();
3093 }
3094
3095 is_leader = val;
3096 }
3097
canBeLeader()3098 bool Character::canBeLeader()
3099 {
3100 return (! isDead() && isOnMap() && ! isAsleep() && ! isCharmed() && isPlayerControlled()
3101 && (getActionPoints() > -(2*getActionPointsPerTurn()))
3102 && ! engagedInTask()
3103 );
3104 }
3105
isLeader()3106 bool Character::isLeader()
3107 {
3108 return is_leader;
3109 }
3110
isCompanionOf(class Object * other)3111 bool Character::isCompanionOf(class Object *other)
3112 {
3113 // ------------------------------------------------------------------
3114 // Do the simple thing for now. This is only used in
3115 // the context of player party follow mode.
3116 // ------------------------------------------------------------------
3117
3118 return isPlayerPartyMember() && other->isPlayerPartyMember();
3119 }
3120
joinPlayer(void)3121 bool Character::joinPlayer(void)
3122 {
3123 class Party *old_party = party;
3124
3125 if (old_party == player_party)
3126 return false;
3127
3128 if (NULL != old_party) {
3129 old_party->removeMember(this);
3130 }
3131
3132 if (player_party->addMember(this)) {
3133 addView();
3134 // Turn off any prior AI.
3135 setAI(NULL);
3136 return true;
3137 }
3138
3139 if (NULL != old_party) {
3140 old_party->addMember(this);
3141 }
3142
3143 setSchedule(NULL);
3144
3145 return false;
3146 }
3147
leavePlayer(void)3148 void Character::leavePlayer(void)
3149 {
3150 if (!isPlayerPartyMember())
3151 return;
3152 if (isSolo()) {
3153 assert(isPlayerControlled());
3154 player_party->enableRoundRobinMode();
3155 }
3156
3157 bool wasLeader = isLeader();
3158
3159 unreadyAll();
3160
3161 player_party->removeMember(this);
3162 log_msg("%s leaves the party", getName());
3163 if (wasLeader) {
3164 player_party->enableFollowMode();
3165 }
3166 }
3167
getActivity()3168 int Character::getActivity()
3169 {
3170 if (place_contains_hostiles(getPlace(), this))
3171 return FIGHTING;
3172
3173 if (! sched)
3174 return NONE;
3175 getAppointment();
3176 if (atAppointment())
3177 return sched->appts[appt].act;
3178 return COMMUTING;
3179 }
3180
setActivity(int val)3181 void Character::setActivity(int val)
3182 {
3183 activity = val;
3184 changeSleep(activity == SLEEPING);
3185 }
3186
canWanderTo(int newx,int newy)3187 bool Character::canWanderTo(int newx, int newy)
3188 {
3189 // If this party is on a schedule then limit wandering to the
3190 // area specied in the current appt.
3191 if (sched) {
3192 if (newx < sched->appts[appt].x ||
3193 newx > (sched->appts[appt].x + sched->appts[appt].w - 1) ||
3194 newy < sched->appts[appt].y ||
3195 newy > (sched->appts[appt].y + sched->appts[appt].h) - 1)
3196 return false;
3197 }
3198
3199 return true;
3200 }
3201
char_dtor(void * val)3202 void char_dtor(void *val)
3203 {
3204 delete (class Character*)val;
3205 }
3206
char_save(save_t * save,void * val)3207 void char_save(save_t *save, void *val)
3208 {
3209 ((class Character*)val)->save(save);
3210 }
3211
save(struct save * save)3212 void Character::save(struct save *save)
3213 {
3214 class ArmsType *arms;
3215
3216 if (saved == save->session_id) {
3217 save->write(save, "%s\n", tag);
3218 return;
3219 }
3220
3221 saved = save->session_id;
3222
3223 // Create it within a 'let' block
3224 save->enter(save, "(let ((kchar ");
3225
3226 if (getGob()) {
3227 // wrap the declaration in a call to bind the object to the
3228 // gob
3229 save->enter(save, "(bind\n");
3230
3231 if (getForceContainerDrop()) {
3232 save->enter(save, "(kern-char-force-drop");
3233 }
3234 }
3235
3236 // Create the object within a 'let' block
3237 save->enter(save, "(let ((kchar ");
3238 save->enter(save, "(kern-mk-char\n");
3239 if (this->tag) {
3240 save->write(save, "\'%s ; tag\n", this->tag );
3241 } else {
3242 save->write(save, "nil ; tag\n");
3243 }
3244 save->write(save, "\"%s\" ; name\n", this->getName());
3245 save->write(save, "%s ; species\n", this->species->tag);
3246 save->write(save, "%s ; occ\n", this->occ ? this->occ->tag : "nil");
3247 sprite_save(current_sprite, save);
3248 /*save->write(save, "%s\n", sprite_get_tag(this->sprite));*/
3249 save->write(save, "%d ; BaseFaction\n", getBaseFaction());
3250 save->write(save, "%d %d %d ; str, int, dex\n", str, intl, dex);
3251 save->write(save, "%d %d ; hp_mod, hp_mult\n", this->hp_mod, this->hp_mult);
3252 save->write(save, "%d %d ; mp_mod, mp_mult\n", this->mp_mod, this->mp_mult);
3253 save->write(save, "%d %d ; HP, XP\n", this->getHp(), this->getExperience());
3254 save->write(save, "%d ; mana\n", this->getMana() );
3255 save->write(save, "%d ; AP_per_round\n", this->AP_per_round );
3256 save->write(save, "%d ; level\n", this->getLevel() );
3257 save->write(save, "#%c ; dead?\n", isDead() ? 't' : 'f');
3258
3259 if (conv != NULL) {
3260 conv_save(conv, save);
3261 } else {
3262 save->write(save, "nil ; conv\n");
3263 }
3264
3265 save->write(save, "%s ; sched\n", sched? sched->tag : "nil");
3266
3267 if (ai != NULL) {
3268 closure_save(ai, save);
3269 } else
3270 save->write(save, "nil ; ai\n");
3271
3272 // Items in personal inventory.
3273 if (!container) {
3274 save->write(save, "nil ; inventory\n");
3275 } else {
3276 container->save(save);
3277 }
3278
3279 // Readied items. Subtle: if this character has a container then these
3280 // items have already been saved.
3281 int armsIndex=0;
3282 arms = this->enumerateArms(&armsIndex);
3283 if (! arms) {
3284 save->write(save, "nil ; readied arms\n");
3285 } else {
3286 save->enter(save, "(list\n");
3287 while (arms != NULL) {
3288 save->write(save, "%s\n", arms->getTag());
3289 arms = this->getNextArms(&armsIndex);
3290 }
3291 save->exit(save, ") ; readied arms\n\n");
3292 }
3293
3294 // Hooks
3295 Object::saveHooks(save);
3296
3297 // Close the <var-list> part of the 'let' block
3298 save->exit(save, "))) ;; end ((kchar ...)\n");
3299
3300 if (isSubmerged()) {
3301 save->write(save, "(kern-obj-set-submerged kchar #t)\n");
3302 }
3303
3304 // Close the 'let' block
3305 save->exit(save, "kchar) ;; end (let ...)\n");
3306
3307 if (getForceContainerDrop()) {
3308 save->exit(save, "#t) ;; kern-char-force-drop\n");
3309 }
3310
3311 if (getGob()) {
3312
3313 // save the gob list
3314 gob_save(getGob(), save);
3315
3316 // end the bind call
3317 save->exit(save, ") ;; bind\n");
3318 }
3319
3320 // close the args assignment section of the let block
3321 save->write(save, ")) ");
3322
3323 if (isKnown()) {
3324 save->write(save, "(kern-char-set-known kchar #t)\n");
3325 }
3326
3327 // Only save AP debt, else when the game reloads and the character
3328 // restarts its turn it will carry over what is saved as an unwarranted
3329 // bonus.
3330 if (getActionPoints() < 0) {
3331 save->write(save, "(kern-obj-set-ap kchar %d)\n", getActionPoints());
3332 }
3333
3334 // save the task, if any
3335 if (taskproc) {
3336 save->write(save, "(kern-char-task-continue kchar \"%s\" ", getTaskName());
3337 closure_save(taskproc, save);
3338 if (taskgob) {
3339 gob_save(taskgob, save);
3340 } else {
3341 save->write(save, " nil");
3342 }
3343 save->write(save, ")\n");
3344 }
3345
3346 // close the 'let' block
3347 save->exit(save, "kchar)\n");
3348 }
3349
setSchedule(struct sched * val)3350 void Character::setSchedule(struct sched *val)
3351 {
3352
3353 sched = val;
3354
3355 if (sched) {
3356 sched_chars_node = session_add_sched_char(Session, this);
3357 } else {
3358 // Bugfix: if a character is eating and the schedule is set to
3359 // NULL then the character will be stuck eating (and doing
3360 // nothing else!).
3361 setActivity(NONE);
3362 }
3363
3364 // Bugfix: if an npc with a schedule is added to the player party
3365 // either during the game or on reload, and it has a schedule, then the
3366 // next time the player enters a town the scheduling code will mess
3367 // with the party member.
3368 if (! sched && sched_chars_node) {
3369 session_rm_sched_char(sched_chars_node);
3370 sched_chars_node = 0;
3371 }
3372 }
3373
tryToRelocateToNewPlace(struct place * newplace,int newx,int newy,struct closure * closure)3374 bool Character::tryToRelocateToNewPlace(struct place *newplace,
3375 int newx, int newy,
3376 struct closure *closure)
3377 {
3378 // NPCs and charmed PCs are not allowed to change places because I
3379 // don't want them dissappearing into the ether.
3380 //
3381 // Addendum: this needs to work for characters with multi-place
3382 // schedules. If I only forbid entry to the wilderness this should work
3383 // as originally intended.
3384 if (place_is_wilderness(newplace)
3385 && (! isPlayerControlled())) {
3386 return false;
3387 }
3388
3389 // -----------------------------------------------------------------
3390 // At this point I know the character is player-controlled, so I can
3391 // print informative messages.
3392 // -----------------------------------------------------------------
3393
3394 if (player_party->get_num_living_members() == 1) {
3395 // Force to follow mode to avoid the annoying case
3396 // where only one member is in the party and the player
3397 // wants to leave a combat map.
3398 player_party->enableFollowMode();
3399 }
3400
3401 if (player_party->getPartyControlMode() != PARTY_CONTROL_FOLLOW) {
3402 log_msg("Exit - must be in follow mode!");
3403 return false;
3404 }
3405
3406 if (player_party->get_leader() != this)
3407 return false;
3408
3409 if (!player_party->rendezvous(getPlace(), getX(), getY())) {
3410 log_msg("Exit - party can't rendezvous!");
3411 return false;
3412 }
3413
3414 groupExitTo(newplace, newx, newy, closure);
3415
3416 return true;
3417
3418 }
3419
addDefense(int val)3420 void Character::addDefense(int val)
3421 {
3422 defenseBonus += val;
3423 }
3424
getMovementMode()3425 struct mmode *Character::getMovementMode()
3426 {
3427 if (currentMmode) {
3428 return currentMmode;
3429 } else {
3430 return species->mmode;
3431 }
3432 }
3433
setMovementMode(struct mmode * mmode)3434 void Character::setMovementMode(struct mmode *mmode)
3435 {
3436 currentMmode = mmode;
3437 }
3438
setCurrentFaction(int faction)3439 void Character::setCurrentFaction(int faction)
3440 {
3441 #if 1
3442 Being::setCurrentFaction(faction);
3443
3444 // We need to implement charm differently. This is the wrong place to do
3445 // it.
3446 #else
3447 if (! isPlayerPartyMember()) {
3448 Being::setCurrentFaction(faction);
3449 } else {
3450 if (faction != player_party->getBaseFaction()) {
3451 // player party member charmed
3452
3453 // Fix for [ 1425039 ] Solo mode + possession = lockup:
3454 // if in solo mode, switch party to follow mode
3455 if (isSolo()) {
3456 setSolo(false);
3457 player_party->enableFollowMode();
3458 }
3459
3460 setControlMode(CONTROL_MODE_AUTO);
3461
3462 // subtle: in this case, do this AFTER calling
3463 // setControlMode() (charmed character's ignore control
3464 // mode changes)
3465 Being::setCurrentFaction(faction);
3466
3467 } else {
3468 // subtle: in this case, do this BEFORE calling
3469 // setControlMode() (opposite of above)
3470 Being::setCurrentFaction(faction);
3471
3472 // player party member uncharmed
3473 switch (player_party->getPartyControlMode()) {
3474 case PARTY_CONTROL_ROUND_ROBIN:
3475 setControlMode(CONTROL_MODE_PLAYER);
3476 break;
3477 case PARTY_CONTROL_SOLO:
3478 setControlMode(CONTROL_MODE_IDLE);
3479 break;
3480 case PARTY_CONTROL_FOLLOW:
3481 if (isLeader()) {
3482 setControlMode(CONTROL_MODE_PLAYER);
3483 } else {
3484 setControlMode(CONTROL_MODE_FOLLOW);
3485 }
3486 break;
3487 }
3488 }
3489 }
3490 #endif
3491 }
3492
getInventoryContainer()3493 class Container* Character::getInventoryContainer()
3494 {
3495 /* for player-controlled maybe return party inventory? */
3496 if (isPlayerControlled())
3497 return player_party->inventory;
3498 return container;
3499 }
3500
setInventoryContainer(class Container * val)3501 void Character::setInventoryContainer(class Container *val)
3502 {
3503 // Blow away the old one
3504 if (container) {
3505 assert(container->isEmpty());
3506 obj_dec_ref(container);
3507 }
3508
3509 container = val;
3510 if (container) {
3511 obj_inc_ref(container);
3512 }
3513 }
3514
setAI(struct closure * val)3515 void Character::setAI(struct closure *val)
3516 {
3517 // out with the old
3518 if (ai) {
3519 closure_unref(ai);
3520 ai = NULL;
3521 }
3522
3523 // in with the new
3524 if (val) {
3525 closure_ref(val);
3526 ai = val;
3527 }
3528 }
3529
getAI()3530 struct closure *Character::getAI()
3531 {
3532 return ai;
3533 }
3534
setDead(bool val)3535 void Character::setDead(bool val)
3536 {
3537 dead = val;
3538 }
3539
getForceContainerDrop()3540 bool Character::getForceContainerDrop()
3541 {
3542 return forceContainerDrop;
3543 }
3544
setForceContainerDrop(bool val)3545 void Character::setForceContainerDrop(bool val)
3546 {
3547 forceContainerDrop = val;
3548 }
3549
isStationary()3550 bool Character::isStationary()
3551 {
3552 return species->stationary;
3553 }
3554
isKnown()3555 bool Character::isKnown()
3556 {
3557 return known;
3558 }
3559
setKnown(bool val)3560 void Character::setKnown(bool val)
3561 {
3562 known = val;
3563 }
3564
getTaskName()3565 const char * Character::getTaskName()
3566 {
3567 return taskname ? taskname : "a nameless task";
3568 }
3569
taskCleanup()3570 void Character::taskCleanup()
3571 {
3572 if (taskname) {
3573 free(taskname);
3574 taskname = NULL;
3575 }
3576
3577 if (taskproc) {
3578 closure_unref(taskproc);
3579 taskproc = NULL;
3580 }
3581
3582 if (taskgob) {
3583 gob_unref(taskgob);
3584 taskgob = NULL;
3585 }
3586
3587 if (isPlayerPartyMember()) {
3588 class Character *oldLeader, *newLeader;
3589 switch (player_party->getPartyControlMode()) {
3590 case PARTY_CONTROL_FOLLOW:
3591 oldLeader = player_party->get_leader();
3592 player_party->chooseNewLeader();
3593 newLeader = player_party->get_leader();
3594 if (oldLeader != newLeader) {
3595 oldLeader->setControlMode(CONTROL_MODE_FOLLOW);
3596 }
3597 if (newLeader != this) {
3598 setControlMode(CONTROL_MODE_FOLLOW);
3599 }
3600 break;
3601 case PARTY_CONTROL_SOLO:
3602 setControlMode(CONTROL_MODE_IDLE);
3603 break;
3604 case PARTY_CONTROL_ROUND_ROBIN:
3605 setControlMode(CONTROL_MODE_PLAYER);
3606 break;
3607 }
3608 }
3609 }
3610
taskAbort()3611 void Character::taskAbort()
3612 {
3613 log_msg("%s aborts %s!", getName(), getTaskName());
3614 taskCleanup();
3615 }
3616
taskSetup(char * name_arg,struct closure * proc_arg,struct gob * gob_arg)3617 void Character::taskSetup(char *name_arg, struct closure *proc_arg, struct gob *gob_arg)
3618 {
3619 assert(! engagedInTask());
3620
3621 if (name_arg) {
3622 taskname = strdup(name_arg);
3623 }
3624
3625 assert(proc_arg);
3626 taskproc = proc_arg;
3627 closure_ref(taskproc);
3628
3629 if (gob_arg) {
3630 taskgob = gob_arg;
3631 gob_ref(gob_arg);
3632 }
3633
3634 if (isPlayerPartyMember()) {
3635 switch (player_party->getPartyControlMode()) {
3636 case PARTY_CONTROL_FOLLOW:
3637 if (isLeader()) {
3638 player_party->chooseNewLeader();
3639 assert(this != player_party->get_leader());
3640 }
3641 break;
3642 case PARTY_CONTROL_SOLO:
3643 if (isSolo()) {
3644 setSolo(false); // sets CONTROL_MODE_IDLE
3645 player_party->enableFollowMode();
3646 }
3647 break;
3648 case PARTY_CONTROL_ROUND_ROBIN:
3649 break;
3650 }
3651 }
3652
3653 setControlMode(CONTROL_MODE_TASK);
3654 taskInterruptOnDamage = true;
3655 }
3656
taskBegin(char * name_arg,struct closure * proc_arg,struct gob * gob_arg)3657 void Character::taskBegin(char *name_arg, struct closure *proc_arg, struct gob *gob_arg)
3658 {
3659 taskSetup(name_arg, proc_arg, gob_arg);
3660 log_msg("%s begins %s.", getName(), getTaskName());
3661 endTurn();
3662 }
3663
taskContinue(char * name_arg,struct closure * proc_arg,struct gob * gob_arg)3664 void Character::taskContinue(char *name_arg, struct closure *proc_arg, struct gob *gob_arg)
3665 {
3666 if (isPlayerControlled()) {
3667 log_msg("%s continues %s.", getName(), getTaskName());
3668 }
3669
3670 taskSetup(name_arg, proc_arg, gob_arg);
3671 }
3672
taskEnd()3673 void Character::taskEnd()
3674 {
3675 log_msg("%s completes %s!", getName(), getTaskName());
3676 taskCleanup();
3677 }
3678
engagedInTask()3679 bool Character::engagedInTask()
3680 {
3681 return taskproc != NULL;
3682 }
3683