1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-23 21:07:17 +00:00

implemented curses

This commit is contained in:
Zeno Rogue 2021-05-02 15:16:29 +02:00
parent 46683b06fe
commit 61891c4eb8
13 changed files with 193 additions and 6 deletions

View File

@ -89,7 +89,9 @@ EX bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags)
if(!m2) return false; if(!m2) return false;
if(m2 == moPlayer && peace::on) return false; if(m2 == moPlayer && peace::on) return false;
if((flags & AF_WEAK) && isIvy(c2)) return false;
if((flags & AF_MUSTKILL) && attackJustStuns(c2, flags, m1)) if((flags & AF_MUSTKILL) && attackJustStuns(c2, flags, m1))
return false; return false;
@ -353,6 +355,7 @@ EX void stunMonster(cell *c2, eMonster killer, flagtype flags) {
c2->monst == moFlailer ? 1 : c2->monst == moFlailer ? 1 :
c2->monst == moSalamander ? 6 : c2->monst == moSalamander ? 6 :
c2->monst == moBrownBug ? 3 : c2->monst == moBrownBug ? 3 :
((flags & AF_WEAK) && !attackJustStuns(c2, flags &~ AF_WEAK, killer)) ? min(5+c2->stuntime, 15) :
3); 3);
if(killer == moArrowTrap) newtime = min(newtime + 3, 7); if(killer == moArrowTrap) newtime = min(newtime + 3, 7);
if(!isMetalBeast(c2->monst) && !among(c2->monst, moSkeleton, moReptile, moSalamander, moTortoise, moWorldTurtle, moBrownBug)) { if(!isMetalBeast(c2->monst) && !among(c2->monst, moSkeleton, moReptile, moSalamander, moTortoise, moWorldTurtle, moBrownBug)) {
@ -366,6 +369,8 @@ EX void stunMonster(cell *c2, eMonster killer, flagtype flags) {
} }
EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) { EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) {
if(f & AF_WEAK)
return true;
if(f & AF_HORNS) if(f & AF_HORNS)
return hornStuns(c2); return hornStuns(c2);
else if(attacker == moArrowTrap && arrow_stuns(c2->monst)) else if(attacker == moArrowTrap && arrow_stuns(c2->monst))
@ -1040,6 +1045,8 @@ EX void killFriendlyIvy() {
} }
EX bool monsterPushable(cell *c2) { EX bool monsterPushable(cell *c2) {
if(markOrb(itCurseWeakness) && attackJustStuns(c2, 0, moPlayer))
return false;
return (c2->monst != moFatGuard && !(isMetalBeast(c2->monst) && c2->stuntime < 2) && c2->monst != moTortoise && c2->monst != moTerraWarrior && c2->monst != moVizier && c2->monst != moWorldTurtle); return (c2->monst != moFatGuard && !(isMetalBeast(c2->monst) && c2->stuntime < 2) && c2->monst != moTortoise && c2->monst != moTerraWarrior && c2->monst != moVizier && c2->monst != moWorldTurtle);
} }

View File

@ -545,6 +545,7 @@ static const flagtype IF_EMPATHY = Flag(3);
static const flagtype IF_RANGED = Flag(4); static const flagtype IF_RANGED = Flag(4);
static const flagtype IF_SHMUPLIFE = Flag(5); static const flagtype IF_SHMUPLIFE = Flag(5);
static const flagtype IF_REVIVAL = Flag(6); static const flagtype IF_REVIVAL = Flag(6);
static const flagtype IF_CURSE = Flag(7);
// 0 = basic treasure, 1 = other item, 2 = power orb, 3 = not an item // 0 = basic treasure, 1 = other item, 2 = power orb, 3 = not an item
#define IC_TREASURE 0 #define IC_TREASURE 0

View File

@ -1645,6 +1645,43 @@ WALL( '$', 0x40FD40, "Crate on Target", waCrateOnTarget, WF_WALL | WF_PUSHABLE,
"A crate already on a target." "A crate already on a target."
) )
ITEM( 'o', 0xF0F0FF, "Orb of Purity", itOrbPurity, IC_ORB, ZERO, RESERVED, osProtective,
"Reverses all the curses."
)
ITEM('c', 0x202020, "Curse of Weakness", itCurseWeakness, IC_ORB, IF_CURSE, RESERVED, osOffensive,
"Makes you weak."
)
ITEM('c', 0x6060FF, "Curse of Draining", itCurseDraining, IC_ORB, IF_CURSE, RESERVED, osPowerUtility,
"Drains your power."
)
ITEM('c', 0x000060, "Curse of Water", itCurseWater, IC_ORB, IF_CURSE, RESERVED, osTerraform,
"Makes you fear water."
)
ITEM('c', 0xFF6060, "Curse of Fatigue", itCurseFatigue, IC_ORB, IF_CURSE, RESERVED, osMovement,
"Cannot move too quickly."
)
ITEM('c', 0xFFFF80, "Curse of Repulsion", itCurseRepulsion, IC_ORB, IF_CURSE, RESERVED, osUtility,
"All items are repelled."
)
ITEM('c', 0xD08080, "Curse of Gluttony", itCurseGluttony, IC_ORB, IF_CURSE, RESERVED, osNone,
"The first item you pick up is consumed."
)
ITEM('>', 0xFF6060, "fatigue", itFatigue, IC_NAI, ZERO, RESERVED, osNone,
"In the Windy Plains, you can let the wind carry you, "
"causing you to move two cells with the wind in a single turn. "
"This cannot be done if you are standing at distance at most 2 "
"from the Air Elemental, or if any of the three cells on the way "
"has two wind directions.\n\n"
"Press 't' or click the destination to activate."
)
//shmupspecials //shmupspecials
MONSTER( '@', 0xC0C0C0, "Rogue", moPlayer, CF_FACE_UP | CF_PLAYER, RESERVED, moNone, "In the Shoot'em Up mode, you are armed with thrown Knives.") MONSTER( '@', 0xC0C0C0, "Rogue", moPlayer, CF_FACE_UP | CF_PLAYER, RESERVED, moNone, "In the Shoot'em Up mode, you are armed with thrown Knives.")
MONSTER( '*', 0xC0C0C0, "Knife", moBullet, ZERO | CF_BULLET, RESERVED, moNone, "A simple, but effective, missile, used by rogues.") MONSTER( '*', 0xC0C0C0, "Knife", moBullet, ZERO | CF_BULLET, RESERVED, moNone, "A simple, but effective, missile, used by rogues.")

View File

@ -296,6 +296,7 @@ EX void placeGolem(cell *on, cell *moveto, eMonster m) {
EX bool multiRevival(cell *on, cell *moveto) { EX bool multiRevival(cell *on, cell *moveto) {
int fl = 0; int fl = 0;
if(items[itOrbAether]) fl |= P_AETHER; if(items[itOrbAether]) fl |= P_AETHER;
if(items[itCurseWater]) fl |= P_WATERCURSE;
if(items[itOrbFish]) fl |= P_FISH; if(items[itOrbFish]) fl |= P_FISH;
if(items[itOrbWinter]) fl |= P_WINTER; if(items[itOrbWinter]) fl |= P_WINTER;
if(passable(on, moveto, fl)) { if(passable(on, moveto, fl)) {

View File

@ -819,6 +819,10 @@ EX bool drawItemType(eItem it, cell *c, const shiftmatrix& V, color_t icol, int
queuepoly(Vit * spinptick(750, 0), cgi.shFan, darkena(icol, 0, 255)); queuepoly(Vit * spinptick(750, 0), cgi.shFan, darkena(icol, 0, 255));
} }
else if(it == itFatigue) {
queuepoly(Vit * spinptick(750, 0), cgi.shFan, darkena(icol, 0, 255));
}
else if(it == itWarning) { else if(it == itWarning) {
queuepoly(Vit * spinptick(750, 0), cgi.shTriangle, darkena(icol, 0, 255)); queuepoly(Vit * spinptick(750, 0), cgi.shTriangle, darkena(icol, 0, 255));
} }
@ -958,7 +962,7 @@ EX bool drawItemType(eItem it, cell *c, const shiftmatrix& V, color_t icol, int
} }
} }
else if(xch == 'o' || it == itInventory) { else if(xch == 'o' || xch == 'c' || it == itInventory) {
if(it == itOrbFire) icol = firecolor(100); if(it == itOrbFire) icol = firecolor(100);
PPR prio = PPR::ITEM; PPR prio = PPR::ITEM;
bool inice = c && c->wall == waIcewall; bool inice = c && c->wall == waIcewall;
@ -970,8 +974,11 @@ EX bool drawItemType(eItem it, cell *c, const shiftmatrix& V, color_t icol, int
if(it == itOrbFish) if(it == itOrbFish)
queuepolyat(Vit * spinptick(1500, 0), cgi.shFishTail, col, PPR::ITEM_BELOW); queuepolyat(Vit * spinptick(1500, 0), cgi.shFishTail, col, PPR::ITEM_BELOW);
queuepolyat(Vit, cgi.shDisk, darkena(icol1, 0, inice ? 0x80 : hidden ? 0x20 : 0xC0), prio); if(xch == 'c')
queuepolyat(Vit * spinptick(500, 0), cgi.shMoonDisk, darkena(0x801080, 0, hidden ? 0x20 : 0xC0), prio);
else
queuepolyat(Vit, cgi.shDisk, darkena(icol1, 0, inice ? 0x80 : hidden ? 0x20 : 0xC0), prio);
queuepolyat(Vit * spinptick(1500, 0), orbshape(iinf[it].orbshape), col, prio); queuepolyat(Vit * spinptick(1500, 0), orbshape(iinf[it].orbshape), col, prio);
} }

View File

@ -141,7 +141,7 @@ int glyphflags(int gid) {
int f = 0; int f = 0;
if(gid < ittypes) { if(gid < ittypes) {
eItem i = eItem(gid); eItem i = eItem(gid);
if(itemclass(i) == IC_NAI) f |= GLYPH_NONUMBER; if(itemclass(i) == IC_NAI && i != itFatigue) f |= GLYPH_NONUMBER;
if(isElementalShard(i)) { if(isElementalShard(i)) {
f |= GLYPH_LOCAL | GLYPH_INSQUARE; f |= GLYPH_LOCAL | GLYPH_INSQUARE;
if(i == localshardof(cwt.at->land)) f |= GLYPH_LOCAL2; if(i == localshardof(cwt.at->land)) f |= GLYPH_LOCAL2;

View File

@ -615,6 +615,7 @@ typedef function<int(struct cell*)> cellfunction;
#define AF_CRUSH Flag(31) // Crusher's delayed attack #define AF_CRUSH Flag(31) // Crusher's delayed attack
#define AF_PLAGUE Flag(32) // Orb of Plague (do not check adjacency) #define AF_PLAGUE Flag(32) // Orb of Plague (do not check adjacency)
#define AF_PSI Flag(33) // Orb of the Mind #define AF_PSI Flag(33) // Orb of the Mind
#define AF_WEAK Flag(34) // Curse of Weakness
#if CAP_SDL #if CAP_SDL

View File

@ -29,6 +29,21 @@ EX bool canPickupItemWithMagnetism(cell *c, cell *from) {
} }
EX bool doPickupItemsWithMagnetism(cell *c) { EX bool doPickupItemsWithMagnetism(cell *c) {
/* repulsion works first -- Magnetism will collect the item if not repulsed */
if(items[itCurseRepulsion])
forCellIdEx(c3, i, c) if(c3->item) {
cellwalker cw(c, i);
cw += wstep;
for(int j=1; j<c3->type; j++) {
cell *c4 = (cw+j).peek();
if(!isNeighbor(c, c4) && !c4->item && passable(c4, c3, ZERO)) {
changes.ccell(c3);
changes.ccell(c4);
c4->item = c3->item;
c3->item = itNone;
}
}
}
cell *csaf = NULL; cell *csaf = NULL;
if(items[itOrbMagnetism]) if(items[itOrbMagnetism])
forCellEx(c3, c) if(canPickupItemWithMagnetism(c3, c)) { forCellEx(c3, c) if(canPickupItemWithMagnetism(c3, c)) {
@ -72,6 +87,14 @@ EX bool collectItem(cell *c2, bool telekinesis IS(false)) {
if(cannotPickupItem(c2, telekinesis)) if(cannotPickupItem(c2, telekinesis))
return false; return false;
if(items[itCurseGluttony] && c2->item) {
addMessage(XLAT("%The1 is consumed!", c2->item));
playSound(c2, "apple");
items[itCurseGluttony] = 0;
c2->item = itNone;
return false;
}
/* if(c2->item == itHolyGrail && telekinesis) /* if(c2->item == itHolyGrail && telekinesis)
return false; */ return false; */

View File

@ -420,6 +420,50 @@ EX bool makeflame(cell *c, int timeout, bool checkonly) {
return true; return true;
} }
EX bool makeshallow(cell *c, int timeout, bool checkonly) {
changes.ccell(c);
if(!checkonly) destroyTrapsOn(c);
if(c->land == laBrownian) {
if(checkonly) return true;
brownian::dissolve(c, 1);
}
if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3 ||
c->wall == waTower)
return false;
else if(c->wall == waNone && c->land == laCocytus) {
if(checkonly) return true;
c->wall = waLake;
}
else if(c->wall == waFireTrap) {
if(checkonly) return true;
c->wall = waShallow;
}
else if(c->wall == waFrozenLake) {
if(checkonly) return true;
drawParticles(c, MELTCOLOR, 8, 8);
c->wall = waLake;
}
else if(isFire(c)) {
if(checkonly) return true;
c->wall = waNone;
}
else if(c->wall == waMineMine) {
if(checkonly) return true;
c->wall = waShallow;
}
else if(among(c->wall, waNone, waRubble, waDeadfloor2, waCavefloor, waDeadfloor, waFloorA, waFloorB) && !cellUnstable(c) && !isGravityLand(c)) {
if(checkonly) return true;
c->wall = waShallow;
}
else if(c->wall == waDock) {
if(checkonly) return true;
c->wall = waSea;
c->wparam = 3;
return false;
}
return true;
}
EX void explosion(cell *c, int power, int central) { EX void explosion(cell *c, int power, int central) {
changes.ccell(c); changes.ccell(c);
playSound(c, "explosion"); playSound(c, "explosion");

View File

@ -59,6 +59,11 @@ EX void empathyMove(const movei& mi) {
if(makeflame(mi.s, 10, false)) markEmpathy(itOrbFire); if(makeflame(mi.s, 10, false)) markEmpathy(itOrbFire);
} }
if(items[itCurseWater]) {
invismove = false;
if(makeshallow(mi.s, 10, false)) markEmpathy(itCurseWater);
}
if(items[itOrbDigging]) { if(items[itOrbDigging]) {
if(mi.proper() && earthMove(mi)) if(mi.proper() && earthMove(mi))
markEmpathy(itOrbDigging), invismove = false; markEmpathy(itOrbDigging), invismove = false;
@ -76,6 +81,12 @@ EX int intensify(int val) {
} }
EX bool reduceOrbPower(eItem it, int cap) { EX bool reduceOrbPower(eItem it, int cap) {
if(items[it] && markOrb(itCurseDraining)) {
items[it] -= (markOrb(itOrbEnergy) ? 1 : 2) * multi::activePlayers();
if(items[it] < 0) items[it] = 0;
if(items[it] == 0 && it == itOrbLove)
princess::bringBack();
}
if(items[it] && (lastorbused[it] || (it == itOrbShield && items[it]>3) || !markOrb(itOrbTime))) { if(items[it] && (lastorbused[it] || (it == itOrbShield && items[it]>3) || !markOrb(itOrbTime))) {
items[it] -= multi::activePlayers(); items[it] -= multi::activePlayers();
if(isHaunted(cwt.at->land)) if(isHaunted(cwt.at->land))
@ -101,7 +112,22 @@ EX void reduceOrbPowerAlways(eItem it) {
} }
} }
EX void reverse_curse(eItem curse, eItem orb) {
if(items[curse] && markOrb(itOrbPurity)) {
items[orb] += items[curse];
items[curse] = 0;
}
}
EX void reduceOrbPowers() { EX void reduceOrbPowers() {
reverse_curse(itCurseWeakness, itOrbSlaying);
reverse_curse(itCurseFatigue, itOrbSpeed); // OK
reverse_curse(itCurseRepulsion, itOrbMagnetism); // OK
reverse_curse(itCurseWater, itOrbFire); // OK
reverse_curse(itCurseDraining, itOrbTime); // OK
reverse_curse(itCurseGluttony, itOrbChoice); // OK
if(haveMount()) markOrb(itOrbDomination); if(haveMount()) markOrb(itOrbDomination);
for(int i=0; i<ittypes; i++) for(int i=0; i<ittypes; i++)
lastorbused[i] = orbused[i], orbused[i] = false; lastorbused[i] = orbused[i], orbused[i] = false;

View File

@ -74,6 +74,7 @@ EX bool checkflags(flagtype flags, flagtype x) {
if((x & P_FISH) && markOrb(itOrbFish)) return true; if((x & P_FISH) && markOrb(itOrbFish)) return true;
if((x & P_MARKWATER) && markOrb(itOrbWater)) return true; if((x & P_MARKWATER) && markOrb(itOrbWater)) return true;
if((x & P_AETHER) && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true; if((x & P_AETHER) && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true;
if((x & P_WATERCURSE)&& markOrb2(itCurseWater)) return true;
} }
if(flags & P_ISFRIEND) if(items[itOrbEmpathy]) if(flags & P_ISFRIEND) if(items[itOrbEmpathy])
if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy)) if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy))
@ -129,6 +130,7 @@ EX bool anti_alchemy(cell *w, cell *from) {
#define P_VOID Flag(32) // void beast #define P_VOID Flag(32) // void beast
#define P_PHASE Flag(33) // phasing movement #define P_PHASE Flag(33) // phasing movement
#define P_PULLMAGNET Flag(34) // pull the other part of the magnet #define P_PULLMAGNET Flag(34) // pull the other part of the magnet
#define P_WATERCURSE Flag(35) // Curse of Water
#endif #endif
EX bool passable(cell *w, cell *from, flagtype flags) { EX bool passable(cell *w, cell *from, flagtype flags) {
@ -137,6 +139,9 @@ EX bool passable(cell *w, cell *from, flagtype flags) {
if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false; if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false;
if((isWateryOrBoat(w) || w->wall == waShallow) && F(P_WATERCURSE))
return false;
for(cell *pp: player_positions()) { for(cell *pp: player_positions()) {
if(w == pp && F(P_ONPLAYER)) return true; if(w == pp && F(P_ONPLAYER)) return true;
if(from == pp && F(P_ONPLAYER) && F(P_REVDIR)) return true; if(from == pp && F(P_ONPLAYER) && F(P_REVDIR)) return true;

View File

@ -778,7 +778,7 @@ bool pcmove::after_escape() {
bool dont_attack = items[itOrbFlash] || items[itOrbLightning]; bool dont_attack = items[itOrbFlash] || items[itOrbLightning];
if(attackable && fmsAttack && !dont_attack) { if(attackable && fmsAttack && !dont_attack && !items[itCurseWeakness]) {
if(checkNeedMove(checkonly, true)) return false; if(checkNeedMove(checkonly, true)) return false;
nextmovetype = nm ? lmAttack : lmSkip; nextmovetype = nm ? lmAttack : lmSkip;
if(c2->wall == waSmallTree) { if(c2->wall == waSmallTree) {
@ -831,6 +831,11 @@ bool pcmove::after_escape() {
if(vmsg()) tell_why_impassable(); if(vmsg()) tell_why_impassable();
return false; return false;
} }
else if(items[itFatigue] + fatigue_cost(mi) > 10) {
if(vmsg())
addMessage(XLAT("You are too fatigued!"));
return false;
}
else if(fmsMove) else if(fmsMove)
return move_if_okay(); return move_if_okay();
@ -898,6 +903,7 @@ bool pcmove::attack() {
attackflags = AF_NORMAL; attackflags = AF_NORMAL;
if(items[itOrbSpeed]&1) attackflags |= AF_FAST; if(items[itOrbSpeed]&1) attackflags |= AF_FAST;
if(items[itOrbSlaying]) attackflags |= AF_CRUSH; if(items[itOrbSlaying]) attackflags |= AF_CRUSH;
if(items[itCurseWeakness]) attackflags |= AF_WEAK;
bool ca =canAttack(cwt.at, moPlayer, c2, c2->monst, attackflags); bool ca =canAttack(cwt.at, moPlayer, c2, c2->monst, attackflags);
@ -999,6 +1005,13 @@ EX bool chaos_forbidden(cell *c) {
return do_not_touch_this_wall(c) || isMultitile(c->monst); return do_not_touch_this_wall(c) || isMultitile(c->monst);
} }
EX int fatigue_cost(const movei& mi) {
return
gravityLevelDiff(mi.t, mi.s) +
(snakelevel(mi.t) - snakelevel(mi.s)) +
(againstWind(mi.s, mi.t) ? 0 : 1);
}
bool pcmove::perform_actual_move() { bool pcmove::perform_actual_move() {
cell*& c2 = mi.t; cell*& c2 = mi.t;
changes.at_commit([&] { changes.at_commit([&] {
@ -1026,6 +1039,14 @@ bool pcmove::perform_actual_move() {
if(makeflame(cwt.at, 10, false)) markOrb(itOrbFire); if(makeflame(cwt.at, 10, false)) markOrb(itOrbFire);
} }
if(items[itCurseWater]) {
invismove = false;
if(makeshallow(mi.s, 10, false)) markOrb(itCurseWater);
}
if(markOrb(itCurseFatigue) && !markOrb(itOrbAether))
items[itFatigue] += fatigue_cost(mi);
handle_friendly_ivy(); handle_friendly_ivy();
if(items[itOrbDigging]) { if(items[itOrbDigging]) {
@ -1145,6 +1166,10 @@ bool pcmove::stay() {
if(d == -2) if(d == -2)
dropGreenStone(cwt.at); dropGreenStone(cwt.at);
items[itFatigue] -= 5;
if(items[itFatigue] < 0)
items[itFatigue] = 0;
if(monstersnear_add_pmi(mi)) { if(monstersnear_add_pmi(mi)) {
if(vmsg()) wouldkill("%The1 would get you!"); if(vmsg()) wouldkill("%The1 would get you!");
return false; return false;

View File

@ -503,6 +503,16 @@ void geometry_information::procedural_shapes() {
for(int i=0; i<=S84; i+=SD3) for(int i=0; i<=S84; i+=SD3)
hpcpush(ddi(i, orbsize * .2) * C0); hpcpush(ddi(i, orbsize * .2) * C0);
bshape(shMoonDisk, PPR::ITEM);
for(int i=0; i<=S84; i+=SD3)
if(i <= S84 * 2 / 3)
hpcpush(ddi(i, orbsize * .2) * C0);
else {
hyperpoint h1 = ddi(i, orbsize * .2) * C0;
hyperpoint h2 = ddi(S84-i*2, orbsize * .2) * C0;
hpcpush(mid(mid(h1,h2), h2));
}
bshape(shHugeDisk, PPR::ITEM); bshape(shHugeDisk, PPR::ITEM);
for(int i=0; i<=S84; i+=SD3) for(int i=0; i<=S84; i+=SD3)
hpcpush(ddi(i, orbsize * .4) * C0); hpcpush(ddi(i, orbsize * .4) * C0);