Implement ordering your friends to stay where they are. To do this, I've added a new variable to the monster struct: patrol_point, that is set by the new t sub-command "Wait here!" Once this is set, monsters will spend their time wandering around within the LOS radius centred on the patrol point. If they are attacked, or the player or other friends are attacked, they'll stop wandering to fight, but once the foe is gone, they continue doing so. Currently, the only way to make them stop again is to issue another command, "Follow me!" that is basically the already existing "Come here!" command. I've also added a "Stop fighting!" command that for non-patrolling monsters has the same effect as "Follow me!" - patrolling monsters are supposed to take up their wanderings again.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@5247 c06c8d41-db1a-0410-9941-cceddc491573
P5TRGRH7XMQSPCZKM5IEEO34TY6WMLGHHX7BU6Y453JFRXLUR2VQC
GMCNF7YBSN7WQJNHSF3RV2NTHTVP4TABCNP7S26UEIHPAKNCPWGAC
2Z55MZLCQ4LQVNZSEDQSCDJMUEAHEP7RFUC72ZVOAGNJFABSGO3AC
47RJZCYIM3B7IXJT7FFT6NBZZREY6REK5DZWKZ5E7G66BXEXFN6QC
SZI3RQJBWG24RIA2HC4VKGKNHHLHVVONTSCFLB4BLS6YMXUKJYOQC
6EXYHS7E76E7RARFIB5RKUAUBCN6RKOBKWMEGCUADQRRSSZBEUFQC
32N3KXEP3OVYI7EE3IULLTPBZABDZS7FCTXHXZ2X76QGY7E6IFSAC
PFDWNTN3NEZCP6NKPIM2FK3KRVDBQUCGIAWWOU2DJVS4KWWJLYYAC
GVW4OBPGXY2Q75HB7QHADZIOHKL22FI2BSJ2TM4K5SBJENBFTQKAC
WQIEW3O4MANA2KKYRUWEZP44KHVJ4RRHEZTDXSF4EDELX66LO26QC
NM6UK4SZ3JWD3WWDY355C5NW2MBIFR4N73QHF43AE4CKR5LOLRCQC
2WRXQTGYDBLV46WRNVIUKGNA5QS563XZNNW3N2L6PVOCHIP2YGHQC
O4DT3BQQ3XYPL6PQ72G6VPBAVHXZMEOLONFXNHXFMBXBVOYMB6VQC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
F5PJ6H7W2SS5LTUJ7N6JW4SSWTGTDJRXX7L4JBM2NSD2QC26FFLQC
JGKYRZ34S3I23PMJX6IUBR7EHEFD6I4XXEGXNT7GKT2M2VIRBSMQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
AM7QPHDAWNXHLUEVUHVRHG2RO2DOIEFFU4GV3DCIROW6O5HW7H4AC
7KWDC7XFNMBLSUO2HISIROBINZBX5T67LJEEXTAORXW2YZ7VWFGAC
PSCYVKJ7DGXAL3V5U4O6AJTRV6Q3N3SHQWAZ73VIPRTE4W64F2XAC
LJK4ZQATLSB4MKZG3ARZX5V6RFGTN3NLCN6GTCUGJQKU26SOXMUAC
3BJ2OOF4F524G6UKVGOZVT6W3FSTSHHTKRJADUBZCHDXZWV3KANQC
5UC5TI7B6NWGIGN74QRBZKDBYUMHLM2ZO5ATXEZ6TZOGOLF3JX5QC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
C5U3HSOOQ7BKXKXIDS7MLVXUKDTHAWJ5NXNX6YDXTM3GWY5UWX4QC
TTCA2KKE56DAAKEVUGAP7ZRNF5KDLOWRVO37E3D6KDNMLDBVL7MAC
KAOE5HB3THUKVGFZRO5EZESHEB3Q34WUO5DFMLWIKOBF47LZTIYAC
N5XD5IAOMEDF37AXEBALHFINB4H527T6YNTCHN5KKO6YHXAP5PNQC
UZ6N6HOUPGVSPC5NQROEEDWMEGJA5XUWUY2AKH5QG65AZ25PVXDAC
DH3YTI6VVI727SQXO4CXSDCSBG2UN3UAWLFULBGRLBVH22ACRXIAC
ZGUJWUFJ4NFFJ6PGXLFGQWCWBCZHPWGWI44NJHJEVPRG5L36PADQC
AS2IQQJNNCEQNXXKTGYHLB7RO3ZKCF4F7GK6FJH66BOOKDDRGNIQC
IO5CHPT4QBYSFAPSZGGJAYMN33RDAQKGLVW6OKK25HVIEEQEI4IQC
NUYXKJP5YXHRDUQW5QW7UC3D5U3VPANIOZAOHFCPWMSRYGMA3GCAC
627CM2ZOKVBMPVPBYGWBWWPT2FBMVRRH2VDGPT6Z5XCVJ5R4YQWQC
QS3ZRS3E6KL3YJHPKYEWCWJYRBJSXD5OOYF6Y25HZVECGPJRDB5QC
4UXFU3FZOCBSLDQ4S7MJKAE2H7VUHCNRDQMIY6NJ3PHYXWNGISDQC
SFWCESFCUEVKJ6ZQQX3Y5YTIQD5BC6MCVSLVZFRGRTU46BFLKKWAC
NNG27Y5ZQAZX6UD7F7M4F6KEZBEDFXPEEC3LFUSX4ESKT7K6UJQAC
3V52MSSK7QX7FWLLUW63DTWCBAJEK674EFZLKP45FLZ5KZKVARHAC
CGYTZT5QWIEGYKUOLOK7MFXSLJKLYRZONER5ZCDZO5XYWSLG475QC
ASH5CK6CPBKMLGGIRJ5GKTWMS5W3OBVHTL66RTYZIPFM6KFBYA3QC
OSGS3PH2L5CBTDVZCZS6OCFQNA4A7RMEXBYJQB7DDZBYYJW7QSSAC
NVSFIV2ZKP44XHCSCXG6OZVGL67OIFINC34J2EMKTA4KULCERUEAC
WMHFDQKUDCUGM3R245LLVZ5NNEZSCXFDSTNMVS2O5EFUHHO7HU3AC
PM65H4V4GNPVIJFUQW57DC3VDB7TRUUNXKVZONQKEFZSK3AXX5GQC
GPEJOT73KMACP33IPAKFR5ROGHCOIP22VXZMQNYTGLEA2OSZUM2AC
OWERGKLVPNPGIIS23FZ7ZDOBWUIXCKYAFG3URXU75JAUDX3N5ENAC
UW4XQAAAV3S2ZVBLMSK6VQG6AMYR6DRKXFP64HHBC6Z3QIUWPVXQC
FCZSQBKDNMJZRJS2LWQQWLUFGOXSKXDJZQIHC7L5S7HXCXQPOMMAC
6GT5JAWOIIL4SQ5MWIID6ZVO3KKQFWDQDZNVFHZ6DNK5QCBXJ4UAC
7AMQN7MITMXBNVDAK5VOXTQ4TZIAOD6ZLOFJG7GQMBTY23Y2BKSAC
JQFDRS5YPQ5KKTCNXW2XNUZU2XK2CW4PXRD6BSQFKMC5QFG3Y23AC
X5WLJCJVW55SXZVP7IKP7ADCJIGNKN4PKAXFECVR6TNK7XSMZR7QC
E335DPK7M5WBYKN5C3KG75KY75CVXLN3XTLK55MADVZMHW74AHGQC
WQLOHSNCA3VOMDJF6IINJYKSYVYZEBPJJWBB33QSNE4RP5HEXPMAC
CRUW4EVU3UDWNKXBCPWWHWXXGE7EMEHKK3PLLUD7NWPYY4K2R3YAC
KEANRIMF5CGFVZ2XJYNFPOAKLXOSOJUOVA73IWBWOG576265ERHAC
OP6CTAKWCAU64JXQ3USQYR5E5IFHQHNCACII5UMVRXUTZXJQOAZAC
FU7EQZLXD7YNGUUDHXCBI3VUKL6M2G3EPDY6FB5UA6B6RD4S5UOQC
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
PKHOZG6TIUP2NZZIP6CW5OIPZ3O6PCGWXXW5MH4I6P2WVM24HZEQC
JN4GPMQCXOY5ICTLPLWP6DXBFULN4GMAEK7T4GXTZVIJAUUKBBYAC
IHLGRQOXBGZE3COMNKBKMIDQPJ7HRY4PT74ZUN7HD4ADDPDOX2NQC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
OSRZEPPGBIMSZBWIVBTZTTIMV6TEUGVZRZ5AI2ZJW7CVZZQBUIMQC
ZI7643NG3LOKVSDXHFNV2YU3ZIQUVEY25AOXZCPBXJB5SQAUCE6QC
MJWFTUS66PTCNEYXEJA3CUJFXNWXIKDD6H3V24PW7HK64NSVOFSAC
SBTVKHKZRMVDBYLGQNMZMJXPAYJG43UWBBD7HQJWIPN3BMMHUBJAC
7X3KS2DYACYTMNRE47MZSXC3RYRYUYDC5SOPRVWOIH6MQCHMEU7QC
2HWIWULS64JQ6QEVUUM5FAYNBPHU2FIGCI2HDYFYY447UMOL2AIQC
UTGQ25S6K4R2POPYLVF6A5ZU4PRTN3SIR4DL672HERNAE3RZP7AAC
EWBHI7SZ66QCI2JXIXPDDCVHZ2K45ANOGLR2APG74D63WH4N4UVAC
J43G4UGXBYWSEJRLOP3V3NMJIFUXZHSR7MSZ2MWPCNDVI3QBMVDAC
7VRY3OH6WA7QLMNNOX76RR7C6HA7CLNYLZLWPXJWAP75QRF5B27AC
KNW37MRIU72X4LPXSA4AUPW3VJMOXKWY2XFTV67KW2ZXTCSYMMNAC
ASLW3Z5PAVZSWJEMMMVZT226P44EKSAD47QS72JIFJESAI3RPN3AC
RBAGQ2PB7V5YAM5KSHSZR2E3MLKDSRVM5XYGI2TIXP5QMVBOQHDQC
U3KGUJJQWQORJIIFH3ADVNIEEX5HOX6KEOXO7DJSL7L3Z6GG3PAQC
TOPYNQFDXFXNMNOW2LACAGZQIIBAZFS7NK4CBIA2IF6UUB6Y6ZJAC
UFKLHUYL7WAQ3CI3D42T4C6KBGAUR63DSQAUQTTZG7GJMXSCVJWAC
WW6THKR7JN447YC23YYHYYNH7ABMCFFSECNUFTIJBZX6JHX6W7TAC
3C2VE43SHCSBY4LTRTFYFLIPRWFUN6DXU6D34QVWDQTSNRBUFG7AC
ILN2K6ASDZSMEHOPJ22IZLZJUO6DDGZTKAKXM3YXG6JZZHJNLX4AC
45EMD3KLQPMERNMIKU5G76H6556XOMIW352TSBP7VLWJX2YYGS7AC
B7MSPF6X2RLGWN4M6ZZF3WSOPKGYPTTD7LIJVST7DXN27DG6JHNAC
}
static bool _choose_random_target_grid_in_los(monsters *mon)
{
int pos_x, pos_y;
int count_grids = 0;
for (int j = -LOS_RADIUS; j < LOS_RADIUS; j++)
for (int k = -LOS_RADIUS; k < LOS_RADIUS; k++)
{
if (j == 0 && k == 0)
continue;
pos_x = mon->x + j;
pos_y = mon->y + k;
if (!in_bounds(pos_x, pos_y))
continue;
if (!mon->mon_see_grid(pos_x, pos_y))
continue;
if (!mon->can_pass_through_feat(grd[pos_x][pos_y]))
continue;
if (one_chance_in(++count_grids))
{
mon->target_x = pos_x;
mon->target_y = pos_y;
}
}
return (count_grids);
bool isHurt = (mon->hit_points <= mon->max_hit_points / 4 - 1);
bool isHealthy = (mon->hit_points > mon->max_hit_points / 2);
bool isSmart = (mons_intel(mon->type) > I_ANIMAL);
bool isScared = mon->has_ench(ENCH_FEAR);
bool isMobile = !mons_is_stationary(mon);
bool isHurt = (mon->hit_points <= mon->max_hit_points / 4 - 1);
bool isHealthy = (mon->hit_points > mon->max_hit_points / 2);
bool isSmart = (mons_intel(mon->type) > I_ANIMAL);
bool isScared = mon->has_ench(ENCH_FEAR);
bool isMobile = !mons_is_stationary(mon);
bool patrolling = mon->is_patrolling();
// now, the corollary to that is that sometimes, if a
// player is right next to a monster, they will 'see'
// Now, the corollary to that is that sometimes, if a
// player is right next to a monster, they will 'see'.
mon->target_x = 10 + random2(GXM - 10);
mon->target_y = 10 + random2(GYM - 10);
if (patrolling)
{
if (mon->patrol_point != coord_def(mon->x, mon->y)
|| !_choose_random_target_grid_in_los(mon))
{
mon->target_x = mon->patrol_point.x;
mon->target_y = mon->patrol_point.y;
}
}
else
{
mon->target_x = 10 + random2(GXM - 10);
mon->target_y = 10 + random2(GYM - 10);
}
// during their wanderings, monsters will
// eventually relax their guard (stupid
// ones will do so faster, smart monsters
// have longer memories
// During their wanderings, monsters will eventually relax their
// guard (stupid ones will do so faster, smart monsters have
// longer memories.
// Smart monsters flee until they can
// flee no more... possible to get a
// 'CORNERED' event, at which point
// we can jump back to WANDER if the foe
// isn't present.
// Smart monsters flee until they can flee no more...
// possible to get a 'CORNERED' event, at which point
// we can jump back to WANDER if the foe isn't present.
if (mons_is_confused(monster)
|| monster->type == MONS_AIR_ELEMENTAL
&& monster->has_ench(ENCH_SUBMERGED))
{
std::vector<coord_def> moves;
if (mons_is_confused(monster)
|| monster->type == MONS_AIR_ELEMENTAL
&& monster->has_ench(ENCH_SUBMERGED))
{
std::vector<coord_def> moves;
int pfound = 0;
for (int yi = -1; yi <= 1; ++yi)
for (int xi = -1; xi <= 1; ++xi)
{
coord_def c = monster->pos() + coord_def(xi, yi);
if (in_bounds(c) && monster->can_pass_through(c)
&& one_chance_in(++pfound))
{
mmov_x = xi;
mmov_y = yi;
}
}
int pfound = 0;
for (int yi = -1; yi <= 1; ++yi)
for (int xi = -1; xi <= 1; ++xi)
{
coord_def c = monster->pos() + coord_def(xi, yi);
if (in_bounds(c) && monster->can_pass_through(c)
&& one_chance_in(++pfound))
{
mmov_x = xi;
mmov_y = yi;
}
}
// Bounds check: don't let confused monsters try to run
// off the map.
if (monster->x + mmov_x < 0 || monster->x + mmov_x >= GXM)
mmov_x = 0;
// Bounds check: don't let confused monsters try to run
// off the map.
if (monster->x + mmov_x < 0 || monster->x + mmov_x >= GXM)
mmov_x = 0;
if (!monster->can_pass_through(monster->x + mmov_x,
monster->y + mmov_y))
{
mmov_x = mmov_y = 0;
}
if (!monster->can_pass_through(monster->x + mmov_x,
monster->y + mmov_y))
{
mmov_x = mmov_y = 0;
}
if (mgrd[monster->x + mmov_x][monster->y + mmov_y] != NON_MONSTER
&& !is_sanctuary(monster->x, monster->y)
&& (mmov_x != 0 || mmov_y != 0))
{
monsters_fight(i,
mgrd[monster->x + mmov_x][monster->y + mmov_y]);
int enemy = mgrd[monster->x + mmov_x][monster->y + mmov_y];
if (enemy != NON_MONSTER
&& !is_sanctuary(monster->x, monster->y)
&& (mmov_x != 0 || mmov_y != 0))
{
if (monsters_fight(i, enemy))
{
brkk = true;
mmov_x = 0;
mmov_y = 0;
DEBUG_ENERGY_USE("monsters_fight()");
}
else
{
// FIXME: None of these work!
// Instead run away!
if (monster->add_ench(mon_enchant(ENCH_FEAR)))
{
behaviour_event(monster, ME_SCARE, MHITNOT,
monster->x + mmov_x,
monster->y + mmov_y);
}
break;
/*
if (monster->foe == enemy || mons_friendly(monster)
&& monster->foe == MHITYOU)
{
monster->foe = MHITNOT;
monster->behaviour = BEH_WANDER;
}
brkk = true;
mmov_x = 0;
mmov_y = 0;
DEBUG_ENERGY_USE("monsters_fight()");
}
}
monster->target_x = 10 + random2(GXM - 10);
monster->target_y = 10 + random2(GYM - 10);
*/
}
}
}
// smacking the player is always a good move if we're
// hostile (even if we're heading somewhere else)
// also friendlies want to keep close to the player
// so it's okay as well
// Smacking the player is always a good move if we're
// hostile (even if we're heading somewhere else).
// Also friendlies want to keep close to the player
// so it's okay as well.
target_x(0), target_y(0), inv(NON_ITEM), spells(), attitude(ATT_HOSTILE),
behaviour(BEH_WANDER), foe(MHITYOU), enchantments(), flags(0L),
experience(0), number(0), colour(BLACK), foe_memory(0), shield_blocks(0),
god(GOD_NO_GOD), ghost(), seen_context("")
target_x(0), target_y(0), patrol_point(0, 0), inv(NON_ITEM), spells(),
attitude(ATT_HOSTILE), behaviour(BEH_WANDER), foe(MHITYOU),
enchantments(), flags(0L), experience(0), number(0), colour(BLACK),
foe_memory(0), shield_blocks(0), god(GOD_NO_GOD), ghost(),
seen_context("")
|| (flags & MF_BANISHED)
|| type == MONS_ROYAL_JELLY
|| (you.level_type == LEVEL_DUNGEON && hit_dice > 8 + random2(25)))
|| (flags & MF_BANISHED)
|| type == MONS_ROYAL_JELLY
|| you.level_type == LEVEL_DUNGEON && hit_dice > 8 + random2(25))
hand_half_bonus =
unarmed_ok
&& !can_do_unarmed
&& !shield
&& weapon
&& !item_cursed( *weapon )
&& hands == HANDS_HALF;
hand_half_bonus = (unarmed_ok
&& !can_do_unarmed
&& !shield
&& weapon
&& !item_cursed( *weapon )
&& hands == HANDS_HALF);
attacker_invisible = !attacker_visible && see_grid(attacker->pos());
defender_visible = defender && defender->visible();
defender_invisible = !defender_visible && defender
&& see_grid(defender->pos());
needs_message = attacker_visible || defender_visible;
attacker_invisible = (!attacker_visible && see_grid(attacker->pos()));
defender_visible = (defender && defender->visible());
defender_invisible = (!defender_visible && defender
&& see_grid(defender->pos()));
needs_message = (attacker_visible || defender_visible);
if (attack && (is_sanctuary(you.x_pos, you.y_pos)
|| is_sanctuary(defender->x, defender->y)))
if (!attack)
{
// Attack was cancelled or unsuccessful...
if (attk.cancel_attack)
you.turn_is_over = false;
return (false);
}
if (is_sanctuary(you.x_pos, you.y_pos)
|| is_sanctuary(defender->x, defender->y))
mon->foe = you.pet_target;
mon->foe = (allow_patrol && mon->is_patrolling() ? MHITNOT
: you.pet_target);
}
}
static void _set_allies_patrol_point(bool clear = false)
{
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *mon(&menv[i]);
if (!mon->alive() || !mons_near(mon) || !mons_friendly(mon))
continue;
mon->patrol_point = (clear ? coord_def(0, 0)
: coord_def(mon->x, mon->y));
if (!clear)
mon->behaviour = BEH_WANDER;
case 'f':
case 's':
mons_targd = MHITYOU;
if (keyn == 'f')
{
// Don't reset patrol points for 'Stop fighting!'
_set_allies_patrol_point(true);
mpr("Follow me!");
}
else
mpr("Stop fighting!");
break;
case 'w':
mpr("Wait here!");
mons_targd = MHITNOT;
_set_allies_patrol_point();
break;
case 'p':
if (you.duration[DUR_BERSERKER])
{
canned_msg(MSG_TOO_BERSERK);
return;
}
if (targ_prev)
{
mons_targd = you.prev_targ;
mpr("Attack!");
break;
}
if (mons_targd != MHITNOT)
{
you.pet_target = mons_targd;
set_friendly_foes();
}
you.pet_target = mons_targd;
// Allow patrolling for "Stop fighting!" and "Wait here!"
_set_friendly_foes(keyn == 's' || keyn == 'w');
if (mons_targd != MHITNOT && mons_targd != MHITYOU)
mpr("Attack!");