1
0
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:
Zeno Rogue
2025-12-23 23:15:51 +01:00
parent db0d8676a0
commit 9521e314cd
6 changed files with 131 additions and 82 deletions

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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] };

View File

@@ -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);

View File

@@ -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;
}