mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2025-12-31 09:29:03 +00:00
more elegant weapon modding, on-hit triggers, and status string changes; also fixed crash on vampirism
This commit is contained in:
@@ -28,8 +28,6 @@ struct randeff {
|
||||
void hs(struct stater& s);
|
||||
};
|
||||
|
||||
enum class mod { burning, freezing, disarming, vampire };
|
||||
|
||||
struct power {
|
||||
int key;
|
||||
string id;
|
||||
@@ -46,7 +44,7 @@ struct power {
|
||||
int random_value;
|
||||
vector<struct randeff*> randeffs;
|
||||
void init();
|
||||
vector<pair<mod, int>> mods;
|
||||
vector<struct weaponmod> mods;
|
||||
hr::function<void(data&)> act, paused_act, dead_act;
|
||||
hr::function<string()> get_name;
|
||||
hr::function<string()> get_desc;
|
||||
@@ -354,12 +352,22 @@ struct entity {
|
||||
entity *hal();
|
||||
};
|
||||
|
||||
struct weaponmod {
|
||||
power* wpn;
|
||||
hr::function<void(color_t&)> change_color;
|
||||
hr::function<void(string&)> change_name;
|
||||
hr::function<void(entity *e, int dam, int sav)> action;
|
||||
hr::function<void(int x, int y)> map_action;
|
||||
};
|
||||
|
||||
struct statdata {
|
||||
statarray<ld> stats;
|
||||
int jump_control, coyote_time, hallucinating;
|
||||
ld detect_area, detect_cross, rough_detect;
|
||||
void reset();
|
||||
vector<tuple<power*, mod, int>> mods;
|
||||
vector<weaponmod> mods;
|
||||
vector<hr::function<void(int&)>> on_hit;
|
||||
vector<hr::function<void(hstream&)>> status_strings;
|
||||
};
|
||||
|
||||
using boxfun = hr::function<bbox(int)>;
|
||||
@@ -379,8 +387,6 @@ struct man : public entity {
|
||||
entity *morphed = nullptr;
|
||||
vector<effect> effects;
|
||||
|
||||
int vampire, protection, healbubble;
|
||||
|
||||
int last_action;
|
||||
|
||||
int experience;
|
||||
|
||||
@@ -46,6 +46,8 @@ void statdata::reset() {
|
||||
rough_detect = 0;
|
||||
hallucinating = false;
|
||||
mods.clear();
|
||||
on_hit.clear();
|
||||
status_strings.clear();
|
||||
}
|
||||
|
||||
man::man() {
|
||||
@@ -86,9 +88,6 @@ void man::hs(stater& s) {
|
||||
sdata(next, "next.");
|
||||
|
||||
entity::hs(s);
|
||||
s.act("protection", protection, 0);
|
||||
s.act("vampire", vampire, 0);
|
||||
s.act("healbubble", healbubble, 0);
|
||||
}
|
||||
|
||||
void man::act() {
|
||||
@@ -103,7 +102,7 @@ void man::act() {
|
||||
current = next;
|
||||
next.reset();
|
||||
for(auto& po: powers) po.mods.clear();
|
||||
for(auto& [po, type, val]: current.mods) po->mods.emplace_back(type, val);
|
||||
for(auto& md: current.mods) md.wpn->mods.emplace_back(md);
|
||||
if(h != max_hp())
|
||||
hp = randround(1. * hp * max_hp() / h);
|
||||
auto dat = get_dat();
|
||||
@@ -135,28 +134,9 @@ void man::act() {
|
||||
}
|
||||
|
||||
bool man::reduce_hp(int x) {
|
||||
if(protection && gframeid >= invinc_end) {
|
||||
ld fraction = 1 - 100 / (protection + 100);
|
||||
int take = ceil(x * fraction);
|
||||
if(take > protection) take = protection;
|
||||
protection -= take;
|
||||
x -= protection;
|
||||
}
|
||||
if(healbubble && gframeid >= invinc_end) {
|
||||
int take = x;
|
||||
if(take > healbubble) take = healbubble;
|
||||
healbubble -= take;
|
||||
auto d = m.get_dat();
|
||||
auto mi = std::make_unique<healthbubble>();
|
||||
mi->id = "HEALBUBBLE";
|
||||
ld r = (rand() % 360) * degree;
|
||||
mi->hs(fountain_resetter);
|
||||
mi->power = take * 2;
|
||||
mi->where = m.where;
|
||||
mi->vel = { cos(r) * d.modv * 3, sin(r) * d.modv * 3 };
|
||||
mi->invinc_end = gframeid + 300;
|
||||
new_entities.emplace_back(std::move(mi));
|
||||
}
|
||||
if(gframeid >= invinc_end)
|
||||
for(auto& f: m.current.on_hit)
|
||||
f(x);
|
||||
return entity::reduce_hp(x);
|
||||
}
|
||||
|
||||
@@ -210,18 +190,7 @@ void man::launch_attack(power *p, int fac, boxfun f) {
|
||||
int sav = e->invinc_end;
|
||||
int dam = (m.current.stats[stat::str] + 1) * 3 / 2;
|
||||
e->attacked(dam);
|
||||
for(auto& [md, qty]: p->mods) {
|
||||
if(md == mod::burning) { e->invinc_end = sav; e->attacked(qty); }
|
||||
if(md == mod::freezing) { e->invinc_end = sav; e->attacked(qty); }
|
||||
if(md == mod::vampire) {
|
||||
int dam1 = min(dam, m.vampire);
|
||||
hp += dam1; m.vampire -= dam1;
|
||||
}
|
||||
if(md == mod::disarming && e->hidden()) {
|
||||
e->existing = false;
|
||||
addMessage("You have disarmed a "+e->hal()->get_name()+".");
|
||||
}
|
||||
}
|
||||
for(auto& md: p->mods) md.action(&*e, dam, sav);
|
||||
}
|
||||
for(int y=bb.miny; y<bb.maxy; y++)
|
||||
for(int x=bb.minx; x<bb.maxx; x++) {
|
||||
@@ -230,20 +199,7 @@ void man::launch_attack(power *p, int fac, boxfun f) {
|
||||
current_room->replace_block_frev(x, y, wSmashedDoor);
|
||||
addMessage("You smash the door!");
|
||||
}
|
||||
for(auto& [m, qty]: p->mods) {
|
||||
if(m == mod::burning && b == wWoodWall) {
|
||||
current_room->replace_block_frev(x, y, wAir);
|
||||
addMessage("You burn the wall!");
|
||||
}
|
||||
if(m == mod::freezing && b == wWater) {
|
||||
current_room->replace_block_frev(x, y, wFrozen);
|
||||
addMessage("You freeze the water!");
|
||||
}
|
||||
if(m == mod::disarming && b == wRogueWallHidden) {
|
||||
current_room->replace_block_frev(x, y, wRogueWall);
|
||||
addMessage("You open a secret passage!");
|
||||
}
|
||||
}
|
||||
for(auto& md: p->mods) md.map_action(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,22 +28,12 @@ power& power::be_weapon() {
|
||||
picked_up = [this] (int x) { qty_owned += x; qty_filled = max(qty_filled, x); };
|
||||
auto gn = get_name; get_name = [gn, this] {
|
||||
string s = addqof(gn(), this);
|
||||
for(auto& [m, qty]: mods) {
|
||||
if(m == mod::burning) s = "flaming " + s;
|
||||
if(m == mod::freezing) s = "freezing " + s;
|
||||
if(m == mod::disarming) s = "disarming " + s;
|
||||
if(m == mod::vampire) s = "vampiric " + s;
|
||||
}
|
||||
for(auto& md: mods) md.change_name(s);
|
||||
return s;
|
||||
};
|
||||
auto gc = get_color; get_color = [gc, this] {
|
||||
auto col = gc();
|
||||
for(auto& [m, qty]: mods) {
|
||||
if(m == mod::vampire) col = 0x802020FF;
|
||||
if(m == mod::disarming) col = 0x4040C0FF;
|
||||
if(m == mod::burning) col = gradient(0xFFFF00FF, 0xFF0000FF, -1, sin(ticks/100), 1);
|
||||
if(m == mod::freezing) col = 0x8080FFFF;
|
||||
}
|
||||
for(auto& md: mods) md.change_color(col);
|
||||
return col;
|
||||
};
|
||||
return self;
|
||||
|
||||
@@ -74,8 +74,24 @@ randeff trap_snake("Snake Hair", "Lets you create snakes that can be used to dis
|
||||
|
||||
randeff trap_disarm("Disarm traps", "Lets you see all traps on the level for a short time, and to attack them with your [weapon] to destroy them.", "You suddenly feel able to disarm traps with your [weapon]!", [] (data &d) {
|
||||
m.next.rough_detect = 0.1;
|
||||
if(d.mode == rev::active)
|
||||
m.next.mods.emplace_back(d.re->which_weapon, mod::disarming, m.current.stats[stat::wis]);
|
||||
if(d.mode == rev::active) m.next.mods.emplace_back(weaponmod{
|
||||
d.re->which_weapon,
|
||||
[] (color_t& col) { col = 0x4040C0FF; },
|
||||
[] (string& s) { s = "disarming " + s; },
|
||||
[] (entity *e, int dam, int sav) {
|
||||
if(e->hidden()) {
|
||||
e->existing = false;
|
||||
addMessage("You have disarmed a "+e->hal()->get_name()+".");
|
||||
}
|
||||
},
|
||||
[] (int x, int y) {
|
||||
int b = current_room->at(x, y);
|
||||
if(b == wRogueWallHidden) {
|
||||
current_room->replace_block_frev(x, y, wRogueWall);
|
||||
addMessage("You open a secret passage!");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// health powers
|
||||
@@ -103,17 +119,59 @@ randeff health_regen("Regeneration", "Heals you over a period of time.", "You fe
|
||||
}
|
||||
});
|
||||
randeff health_protect("Protection", "Makes you more resistant to damage.", "You feel protected!", [] (data &d) {
|
||||
m.protection += 3 * m.current.stats[stat::wis] + m.hp * m.current.stats[stat::wis] * 2 / 100;
|
||||
int& protection = d.re->a;
|
||||
if(d.mode == rev::start) protection += 3 * m.current.stats[stat::wis] + m.hp * m.current.stats[stat::wis] * 2 / 100;
|
||||
if(d.mode == rev::active) {
|
||||
m.next.on_hit.emplace_back([&] (int& x) {
|
||||
ld fraction = 1 - 100 / (protection + 100);
|
||||
int take = ceil(x * fraction);
|
||||
if(take > protection) take = protection;
|
||||
protection -= take;
|
||||
x -= protection;
|
||||
});
|
||||
m.next.status_strings.emplace_back([&] (hstream& ss) { print(ss, " P", protection); });
|
||||
}
|
||||
});
|
||||
randeff health_vampire("Vampirism", "Attacks with your [weapon] restore your health.", "Your [weapon] wants blood!", [] (data &d) {
|
||||
int& vampire = d.re->a;
|
||||
if(d.mode == rev::start)
|
||||
m.vampire += 4 * m.current.stats[stat::wis] + m.max_hp() * m.current.stats[stat::wis] * 3 / 100;
|
||||
if(d.mode == rev::active && m.vampire)
|
||||
m.next.mods.emplace_back(d.re->which_weapon, mod::vampire, 2 * m.current.stats[stat::wis] + 1e-6);
|
||||
vampire += 4 * m.current.stats[stat::wis] + m.max_hp() * m.current.stats[stat::wis] * 3 / 100;
|
||||
if(d.mode == rev::active) {
|
||||
m.next.mods.emplace_back(weaponmod{
|
||||
d.re->which_weapon,
|
||||
[] (color_t& col) { col = 0x802020FF; },
|
||||
[] (string& s) { s = "vampiric " + s; },
|
||||
[&] (entity *e, int dam, int sav) {
|
||||
int dam1 = min(dam, vampire);
|
||||
m.hp += dam1; vampire -= dam1;
|
||||
if(m.hp > m.max_hp()) { m.hp = m.max_hp(); }
|
||||
},
|
||||
[] (int x, int y) {}
|
||||
});
|
||||
m.next.status_strings.emplace_back([&] (hstream& ss) { print(ss, " V", vampire); });
|
||||
}
|
||||
});
|
||||
randeff health_bubbles("Bubbles", "When you are attacked, you produce red bubbles that you can collect to heal yourself back.", "You feel something bouncy growing inside you!", [] (data &d) {
|
||||
if(d.mode == rev::start)
|
||||
m.healbubble += 4 * m.current.stats[stat::wis] + m.max_hp() * m.current.stats[stat::wis] * 3 / 100;
|
||||
int& healbubble = d.re->a;
|
||||
if(d.mode == rev::start) healbubble += 4 * m.current.stats[stat::wis] + m.max_hp() * m.current.stats[stat::wis] * 3 / 100;
|
||||
if(d.mode == rev::active) {
|
||||
m.next.on_hit.emplace_back([&] (int& x) {
|
||||
int take = x;
|
||||
if(take > healbubble) take = healbubble;
|
||||
healbubble -= take;
|
||||
auto d = m.get_dat();
|
||||
auto mi = std::make_unique<healthbubble>();
|
||||
mi->id = "HEALBUBBLE";
|
||||
ld r = (rand() % 360) * degree;
|
||||
mi->hs(fountain_resetter);
|
||||
mi->power = take * 2;
|
||||
mi->where = m.where;
|
||||
mi->vel = { cos(r) * d.modv * 3, sin(r) * d.modv * 3 };
|
||||
mi->invinc_end = gframeid + 300;
|
||||
new_entities.emplace_back(std::move(mi));
|
||||
});
|
||||
m.next.status_strings.emplace_back([&] (hstream& ss) { print(ss, " B", healbubble); });
|
||||
}
|
||||
});
|
||||
|
||||
// fire powers
|
||||
@@ -132,7 +190,42 @@ randeff fire_spit("Fiery Spit", "Lets you spit fire.", "You feel fire in your mo
|
||||
});
|
||||
randeff fire_weapon("Fiery Weapon", "Attacks with your [weapon] set things on fire.", "Your hands glow, and your [weapon] burst into flame!", [] (data &d) {
|
||||
if(d.mode == rev::active)
|
||||
m.next.mods.emplace_back(d.re->which_weapon, mod::burning, 2 * m.current.stats[stat::wis] + 1e-6);
|
||||
m.next.mods.emplace_back(
|
||||
weaponmod{
|
||||
d.re->which_weapon,
|
||||
[] (color_t& col) { col = gradient(0xFFFF00FF, 0xFF0000FF, -1, sin(ticks/100), 1); },
|
||||
[] (string& s) { s = "burning " + s; },
|
||||
[] (entity *e, int dam, int sav) {
|
||||
e->invinc_end = sav; e->attacked(2 * m.current.stats[stat::wis] + 1e-6);
|
||||
},
|
||||
[] (int x, int y) {
|
||||
int b = current_room->at(x, y);
|
||||
if(b == wWoodWall) {
|
||||
current_room->replace_block_frev(x, y, wAir);
|
||||
addMessage("You burn the wall!");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
randeff ice_weapon("Chill Weapon", "Attacks with your [weapon] freeze things.", "Your hands glow, and your [weapon] turns cold!", [] (data &d) {
|
||||
if(d.mode == rev::active)
|
||||
m.next.mods.emplace_back(
|
||||
weaponmod{
|
||||
d.re->which_weapon,
|
||||
[] (color_t& col) { col = 0x8080FFFF; },
|
||||
[] (string& s) { s = "freezing " + s; },
|
||||
[] (entity *e, int dam, int sav) {
|
||||
e->invinc_end = sav; e->attacked(2 * m.current.stats[stat::wis] + 1e-6);
|
||||
},
|
||||
[] (int x, int y) {
|
||||
int b = current_room->at(x, y);
|
||||
if(b == wWater) {
|
||||
current_room->replace_block_frev(x, y, wFrozen);
|
||||
addMessage("You freeze the water!");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
flavor morph_cat_color;
|
||||
@@ -184,6 +277,8 @@ void assign_potion_powers() {
|
||||
|
||||
fire_weapon.which_weapon = wpn[0];
|
||||
trap_disarm.which_weapon = wpn[1];
|
||||
health_vampire.which_weapon = wpn[2];
|
||||
|
||||
using relist = vector<randeff*>;
|
||||
find_power("health").randeffs = relist{ pick(&health_heal, &health_regen, &health_protect, &health_vampire, &health_bubbles, &health_protect), random_powers[0] };
|
||||
find_power("the thief").randeffs = relist{ pick(&trap_detect, &trap_snake, &trap_disarm, &trap_detect_cross), random_powers[1] };
|
||||
|
||||
@@ -290,9 +290,7 @@ void run() {
|
||||
mouseovers = current_room->roomname;
|
||||
shstream ss;
|
||||
print(ss, "HP ", m.hp, "/", m.max_hp());
|
||||
if(m.vampire) print(ss, " V");
|
||||
if(m.healbubble) print(ss, " B");
|
||||
if(m.protection) print(ss, " P");
|
||||
for(auto& sts: m.current.status_strings) sts(ss);
|
||||
displayfr(vid.fsize, vid.fsize, 2, vid.fsize, ss.s, titlecolor, 0);
|
||||
if(current_target && current_target->existing)
|
||||
displayfr(vid.xres - vid.fsize, vid.fsize, 2, vid.fsize, "HP " + its(current_target->hp) + "/" + its(current_target->max_hp()) + " " + current_target->get_name(), titlecolor, 16);
|
||||
|
||||
@@ -16,6 +16,10 @@ void power::hs(stater& s) {
|
||||
void randeff::hs(stater& s) {
|
||||
string str = which_weapon ? which_weapon->id : "NONE";
|
||||
s.act("wpn", str, "NONE");
|
||||
s.act("a", a, 0);
|
||||
s.act("b", a, 0);
|
||||
s.act("c", a, 0);
|
||||
s.act("d", a, 0);
|
||||
try {
|
||||
which_weapon = (str != "NONE") ? &find_power_by_id(str) : nullptr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user