diff --git a/attack.cpp b/attack.cpp index 1842323c..9e3ae1cb 100644 --- a/attack.cpp +++ b/attack.cpp @@ -89,7 +89,9 @@ EX bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags) if(!m2) 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)) return false; @@ -353,6 +355,7 @@ EX void stunMonster(cell *c2, eMonster killer, flagtype flags) { c2->monst == moFlailer ? 1 : c2->monst == moSalamander ? 6 : c2->monst == moBrownBug ? 3 : + ((flags & AF_WEAK) && !attackJustStuns(c2, flags &~ AF_WEAK, killer)) ? min(5+c2->stuntime, 15) : 3); if(killer == moArrowTrap) newtime = min(newtime + 3, 7); 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) { + if(f & AF_WEAK) + return true; if(f & AF_HORNS) return hornStuns(c2); else if(attacker == moArrowTrap && arrow_stuns(c2->monst)) @@ -1040,6 +1045,8 @@ EX void killFriendlyIvy() { } 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); } diff --git a/classes.cpp b/classes.cpp index 0131a817..1167b87e 100644 --- a/classes.cpp +++ b/classes.cpp @@ -545,6 +545,7 @@ static const flagtype IF_EMPATHY = Flag(3); static const flagtype IF_RANGED = Flag(4); static const flagtype IF_SHMUPLIFE = Flag(5); 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 #define IC_TREASURE 0 diff --git a/content.cpp b/content.cpp index bda98e9f..ed4bfe90 100644 --- a/content.cpp +++ b/content.cpp @@ -1645,6 +1645,43 @@ WALL( '$', 0x40FD40, "Crate on Target", waCrateOnTarget, WF_WALL | WF_PUSHABLE, "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 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.") diff --git a/game.cpp b/game.cpp index 5074c1dc..c6392b71 100644 --- a/game.cpp +++ b/game.cpp @@ -296,6 +296,7 @@ EX void placeGolem(cell *on, cell *moveto, eMonster m) { EX bool multiRevival(cell *on, cell *moveto) { int fl = 0; if(items[itOrbAether]) fl |= P_AETHER; + if(items[itCurseWater]) fl |= P_WATERCURSE; if(items[itOrbFish]) fl |= P_FISH; if(items[itOrbWinter]) fl |= P_WINTER; if(passable(on, moveto, fl)) { diff --git a/graph.cpp b/graph.cpp index db37a94b..f6a7af1d 100644 --- a/graph.cpp +++ b/graph.cpp @@ -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)); } + else if(it == itFatigue) { + queuepoly(Vit * spinptick(750, 0), cgi.shFan, darkena(icol, 0, 255)); + } + else if(it == itWarning) { 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); PPR prio = PPR::ITEM; 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) 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); } diff --git a/hud.cpp b/hud.cpp index 3ae6b9d2..8622dd64 100644 --- a/hud.cpp +++ b/hud.cpp @@ -141,7 +141,7 @@ int glyphflags(int gid) { int f = 0; if(gid < ittypes) { 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)) { f |= GLYPH_LOCAL | GLYPH_INSQUARE; if(i == localshardof(cwt.at->land)) f |= GLYPH_LOCAL2; diff --git a/hyper.h b/hyper.h index 3e350798..1398f0ca 100644 --- a/hyper.h +++ b/hyper.h @@ -615,6 +615,7 @@ typedef function cellfunction; #define AF_CRUSH Flag(31) // Crusher's delayed attack #define AF_PLAGUE Flag(32) // Orb of Plague (do not check adjacency) #define AF_PSI Flag(33) // Orb of the Mind +#define AF_WEAK Flag(34) // Curse of Weakness #if CAP_SDL diff --git a/items.cpp b/items.cpp index 2f22be83..f46fe80e 100644 --- a/items.cpp +++ b/items.cpp @@ -29,6 +29,21 @@ EX bool canPickupItemWithMagnetism(cell *c, cell *from) { } 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; jtype; 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; if(items[itOrbMagnetism]) forCellEx(c3, c) if(canPickupItemWithMagnetism(c3, c)) { @@ -72,6 +87,14 @@ EX bool collectItem(cell *c2, bool telekinesis IS(false)) { if(cannotPickupItem(c2, telekinesis)) 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) return false; */ diff --git a/mapeffects.cpp b/mapeffects.cpp index 06d8ab69..6c3e992d 100644 --- a/mapeffects.cpp +++ b/mapeffects.cpp @@ -420,6 +420,50 @@ EX bool makeflame(cell *c, int timeout, bool checkonly) { 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) { changes.ccell(c); playSound(c, "explosion"); diff --git a/orbs.cpp b/orbs.cpp index c8f970a7..612a1c15 100644 --- a/orbs.cpp +++ b/orbs.cpp @@ -59,6 +59,11 @@ EX void empathyMove(const movei& mi) { 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(mi.proper() && earthMove(mi)) markEmpathy(itOrbDigging), invismove = false; @@ -76,6 +81,12 @@ EX int intensify(int val) { } 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))) { items[it] -= multi::activePlayers(); 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() { + + 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); for(int i=0; iwall == waShallow) && F(P_WATERCURSE)) + return false; + for(cell *pp: player_positions()) { if(w == pp && F(P_ONPLAYER)) return true; if(from == pp && F(P_ONPLAYER) && F(P_REVDIR)) return true; diff --git a/pcmove.cpp b/pcmove.cpp index 480fe08a..223fa9e4 100644 --- a/pcmove.cpp +++ b/pcmove.cpp @@ -778,7 +778,7 @@ bool pcmove::after_escape() { 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; nextmovetype = nm ? lmAttack : lmSkip; if(c2->wall == waSmallTree) { @@ -831,6 +831,11 @@ bool pcmove::after_escape() { if(vmsg()) tell_why_impassable(); return false; } + else if(items[itFatigue] + fatigue_cost(mi) > 10) { + if(vmsg()) + addMessage(XLAT("You are too fatigued!")); + return false; + } else if(fmsMove) return move_if_okay(); @@ -898,6 +903,7 @@ bool pcmove::attack() { attackflags = AF_NORMAL; if(items[itOrbSpeed]&1) attackflags |= AF_FAST; if(items[itOrbSlaying]) attackflags |= AF_CRUSH; + if(items[itCurseWeakness]) attackflags |= AF_WEAK; 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); } +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() { cell*& c2 = mi.t; changes.at_commit([&] { @@ -1026,6 +1039,14 @@ bool pcmove::perform_actual_move() { 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(); if(items[itOrbDigging]) { @@ -1145,6 +1166,10 @@ bool pcmove::stay() { if(d == -2) dropGreenStone(cwt.at); + items[itFatigue] -= 5; + if(items[itFatigue] < 0) + items[itFatigue] = 0; + if(monstersnear_add_pmi(mi)) { if(vmsg()) wouldkill("%The1 would get you!"); return false; diff --git a/polygons.cpp b/polygons.cpp index 04d021f5..f2a27816 100644 --- a/polygons.cpp +++ b/polygons.cpp @@ -503,6 +503,16 @@ void geometry_information::procedural_shapes() { for(int i=0; i<=S84; i+=SD3) 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); for(int i=0; i<=S84; i+=SD3) hpcpush(ddi(i, orbsize * .4) * C0);