summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/godcompanions.cc
blob: ae118d9e6a81e55fe160efc70402501650219a59 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/**
 * @file
 * @brief Tracking permallies granted by Yred and Beogh.
**/

#include "AppHdr.h"

#include <algorithm>

#include "godcompanions.h"

#include "actor.h"
#include "mon-message.h"
#include "mon-util.h"
#include "religion.h"
#include "strings.h"
#include "spl-other.h"
#include "branch.h"

map<mid_t, companion> companion_list;

companion::companion(const monster& m)
{
    mons = follower(m);
    level = level_id::current();
    timestamp = you.elapsed_time;
}

void init_companions()
{
    companion_list.clear();
}

void add_companion(monster* mons)
{
    ASSERT(mons->alive());
    companion_list[mons->mid] = companion(*mons);
}

void remove_companion(monster* mons)
{
    companion_list.erase(mons->mid);
}

void remove_enslaved_soul_companion()
{
    for (map<mid_t, companion>::iterator i = companion_list.begin();
         i != companion_list.end(); ++i)
    {
        monster* mons = monster_by_mid(i->first);
        if (!mons)
            mons = &i->second.mons.mons;
        if (mons_enslaved_soul(mons))
        {
            remove_companion(mons);
            return;
        }
    }
}

void remove_all_companions(god_type god)
{
    for (map<mid_t, companion>::iterator i = companion_list.begin();
         i != companion_list.end();)
    {
        monster* mons = monster_by_mid(i->first);
        if (!mons)
            mons = &i->second.mons.mons;
        if (mons_is_god_gift(mons, god))
            companion_list.erase(i++);
        else
            ++i;
    }
}

void move_companion_to(const monster* mons, const level_id lid)
{
    // If it's taking stairs, that means the player is heading ahead of it,
    // so we shouldn't relocate the monster until it actually arrives
    // (or we can clone things on the other end)
    if (!(mons->flags & MF_TAKING_STAIRS))
    {
        companion_list[mons->mid].level = lid;
        companion_list[mons->mid].mons = follower(*mons);
        companion_list[mons->mid].timestamp = you.elapsed_time;
    }
}

void update_companions()
{
    for (map<mid_t, companion>::iterator i = companion_list.begin();
         i != companion_list.end(); ++i)
    {
        monster* mons = monster_by_mid(i->first);
        if (mons)
        {
            if (mons->is_divine_companion())
            {
                ASSERT(mons->alive());
                i->second.mons = follower(*mons);
                i->second.timestamp = you.elapsed_time;
            }
        }
    }
}

void populate_offlevel_recall_list(vector<pair<mid_t, int> > &recall_list)
{
    for (map<mid_t, companion>::iterator i = companion_list.begin();
         i != companion_list.end(); ++i)
    {
        int mid = i->first;
        companion* comp = &i->second;
        if (companion_is_elsewhere(mid, true))
        {
            // Recall can't pull monsters out of the Abyss
            if (comp->level.branch == BRANCH_ABYSS)
                continue;

            pair<mid_t, int> p = make_pair(mid,
                                           comp->mons.mons.get_experience_level());
            recall_list.push_back(p);
        }
    }
}

/**
 * Attempt to recall an ally from offlevel.
 *
 * @param mid   The ID of the monster to be recalled.
 * @return      Whether the monster was successfully recalled onto the level.
 * Note that the monster may not still be alive or onlevel, due to shafts, etc,
 * but they were here at least briefly!
 */
bool recall_offlevel_ally(mid_t mid)
{
    if (!companion_is_elsewhere(mid, true))
        return false;

    companion* comp = &companion_list[mid];
    if (!comp->mons.place(true))
        return false;

    monster* mons = monster_by_mid(mid);

    // The monster is now on this level
    remove_monster_from_transit(comp->level, mid);
    comp->level = level_id::current();
    simple_monster_message(mons, " is recalled.");

    // Now that the monster is onlevel, we can safely apply traps to it.
    // old location isn't very meaningful, so use current loc
    mons->apply_location_effects(mons->pos());
    // check if it was killed/shafted by a trap...
    if (!mons->alive())
        return true; // still successfully recalled!

    // Catch up time for off-level monsters
    // (We move the player away so that we don't get expiry
    // messages for things that supposed wore off ages ago)
    const coord_def old_pos = you.pos();
    you.moveto(coord_def(0, 0));

    int turns = you.elapsed_time - comp->timestamp;
    // Note: these are auts, not turns, thus healing is 10 times as fast as
    // for other monsters, confusion goes away after a single turn, etc.

    mons->heal(div_rand_round(turns * mons->off_level_regen_rate(), 100));

    if (turns >= 10 && mons->alive())
    {
        // Remove confusion manually (so that the monster
        // doesn't blink after being recalled)
        mons->del_ench(ENCH_CONFUSION, true);
        mons->timeout_enchantments(turns / 10);
    }
    you.moveto(old_pos);
    // Do this after returning the player to the proper position
    // because it uses player position
    recall_orders(mons);

    return true;
}

bool companion_is_elsewhere(mid_t mid, bool must_exist)
{
    if (companion_list.count(mid))
    {
        return companion_list[mid].level != level_id::current()
               || (player_in_branch(BRANCH_PANDEMONIUM)
                   && companion_list[mid].level.branch == BRANCH_PANDEMONIUM
                   && !monster_by_mid(mid));
    }

    return !must_exist;
}

void wizard_list_companions()
{
    if (companion_list.size() == 0)
    {
        mpr("You have no companions.");
        return;
    }

    for (map<mid_t, companion>::iterator i = companion_list.begin();
        i != companion_list.end(); ++i)
    {
        companion* comp = &i->second;
        monster* mon = &comp->mons.mons;
        mprf("%s (%d)(%s:%d)", mon->name(DESC_PLAIN, true).c_str(),
                mon->mid, branches[comp->level.branch].abbrevname, comp->level.depth);
    }
}

#if TAG_MAJOR_VERSION == 34
// A temporary routine to clean up some references to invalid companions and
// prevent crashes on load. Should be unnecessary once the cloning bugs that
// allow the creation of these invalid companions are fully mopped up
void fixup_bad_companions()
{
    for (map<mid_t, companion>::iterator i = companion_list.begin();
         i != companion_list.end();)
    {
        if (invalid_monster_type(i->second.mons.mons.type))
            companion_list.erase(i++);
        else
            ++i;
    }
}
#endif