1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-12-25 01:20:37 +00:00
hyperrogue/items.cpp
2020-08-02 12:55:37 +02:00

671 lines
23 KiB
C++

// Hyperbolic Rogue - items
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file item.cpp
* \brief routines related to items
*/
#include "hyper.h"
namespace hr {
EX int currentLocalTreasure;
/** for treasures, the number collected; for orbs, the number of charges */
EX array<int, ittypes> items;
EX map<modecode_t, array<int, ittypes> > hiitems;
EX bool cannotPickupItem(cell *c, bool telekinesis) {
return itemHidden(c) && !telekinesis && !(isWatery(c) && markOrb(itOrbFish));
}
EX bool canPickupItemWithMagnetism(cell *c, cell *from) {
if(!c->item || c->item == itOrbYendor || isWall(c) || cannotPickupItem(c, false))
return false;
if(c->item == itCompass && from->item)
return false;
return true;
}
EX bool doPickupItemsWithMagnetism(cell *c) {
cell *csaf = NULL;
if(items[itOrbMagnetism])
forCellEx(c3, c) if(canPickupItemWithMagnetism(c3, c)) {
changes.ccell(c3);
changes.ccell(c);
if(c3->item == itCompass) {
if(!c->item)
moveItem(c3, c, false);
}
else if(c3->item == itOrbSafety || c3->item == itBuggy || c3->item == itBuggy2)
csaf = c3;
else if(markOrb(itOrbMagnetism))
collectItem(c3, false);
}
if(csaf)
return collectItem(csaf, false);
return false;
}
EX void pickupMovedItems(cell *c) {
if(!c->item) return;
if(c->item == itOrbSafety) return;
if(isPlayerOn(c)) collectItem(c, true);
if(items[itOrbMagnetism])
forCellEx(c2, c)
if(isPlayerOn(c2) && canPickupItemWithMagnetism(c, c2)) {
changes.ccell(c2);
changes.ccell(c);
collectItem(c, true);
}
}
EX bool collectItem(cell *c2, bool telekinesis IS(false)) {
bool dopickup = true;
bool had_choice = false;
if(cannotPickupItem(c2, telekinesis))
return false;
/* if(c2->item == itHolyGrail && telekinesis)
return false; */
if(c2->item) {
invismove = false;
if(shmup::on) shmup::visibleFor(2000);
string s0 = "";
if(c2->item == itPalace && items[c2->item] == 12)
princess::forceVizier = true;
if(!cantGetGrimoire(c2, false)) collectMessage(c2, c2->item);
if(c2->item == itDodeca && peace::on) peace::simon::extend();
}
#if CAP_COMPLEX2
if(c2->land == laHunting && c2->item && !inv::activating) {
ambush::ambush(c2, ambush::size(c2, c2->item));
}
#endif
if(isRevivalOrb(c2->item) && multi::revive_queue.size()) {
multiRevival(cwt.at, c2);
}
else if(isShmupLifeOrb(c2->item) && shmup::on) {
playSound(c2, "pickup-orb"); // TODO summon
gainLife();
}
else if(orbcharges(c2->item)) {
eItem it = c2->item;
if(it == itOrbRecall && !dual::state) {
cellwalker cw2 = cwt;
cw2++;
cw2.at = c2;
saveRecall(cw2);
}
if(it == itOrbFire) playSound(c2, "fire");
else if(it == itOrbFire) playSound(c2, "fire");
else if(it == itOrbWinter) playSound(c2, "pickup-winter");
else if(it == itOrbSpeed) playSound(c2, "pickup-speed");
else if(it == itRevolver) playSound(c2, "pickup-key");
else playSound(c2, "pickup-orb");
if(items[itOrbChoice]) items[itOrbChoice] = 0, had_choice = true;
int oc = orbcharges(it);
if(dual::state && among(it, itOrbTeleport, itOrbFrog, itOrbPhasing, itOrbDash, itOrbRecall)) {
oc = 10;
it = itOrbSpeed;
}
if(c2->land == laAsteroids) oc = 10;
if(markOrb(itOrbIntensity)) oc = intensify(oc);
if(!items[it]) items[it]++;
items[it] += oc;
}
else if(c2->item == itOrbLife) {
playSound(c2, "pickup-orb"); // TODO summon
placeGolem(cwt.at, c2, moGolem);
}
else if(c2->item == itOrbFriend) {
playSound(c2, "pickup-orb"); // TODO summon
placeGolem(cwt.at, c2, moTameBomberbird);
}
#if CAP_TOUR
else if(tour::on && (c2->item == itOrbSafety || c2->item == itOrbRecall)) {
addMessage(XLAT("This Orb is not compatible with the Tutorial."));
if(changes.on) changes.rollback();
return true;
}
#endif
else if(c2->item == itOrbSafety) {
if(changes.on) {
if(changes.checking) {
changes.rollback();
return true;
}
changes.commit();
}
playSound(c2, "pickup-orb"); // TODO safety
if(!dual::state) items[c2->item] = 7;
if(shmup::on || multi::players > 1) {
shmup::delayed_safety = true;
shmup::delayed_safety_land = c2->land;
}
else
activateSafety(c2->land);
return true;
}
else if(c2->item == itBabyTortoise) {
using namespace tortoise;
int bnew = babymap[c2];
changes.map_value(babymap, c2);
babymap.erase(c2);
int bold = seekbits;
changes.value_set(tortoise::seekbits, bnew);
changes.value_set(tortoise::last, seekbits);
if(seek()) {
cell *c = passable(cwt.at, NULL, 0) ? cwt.at : c2;
changes.ccell(c);
c->item = itBabyTortoise;
if(c == c2) dopickup = false;
changes.map_value(babymap, c);
babymap[c] = bold;
}
else items[itBabyTortoise]++;
}
else if(c2->item == itOrbYendor && peace::on) {
if(!items[itDodeca]) {
addMessage(XLAT("Collect as many Dodecahedra as you can, then return here!"));
}
else {
addMessage(XLAT("Your score: %1", its(items[itDodeca])));
peace::simon::restore();
}
dopickup = false;
}
else if(c2->item == itOrbYendor && yendor::state(c2) != yendor::ysUnlocked) {
dopickup = false;
}
else if(c2->item == itOrbYendor)
yendor::collected(c2);
else if(c2->item == itHolyGrail) {
playSound(c2, "tada");
int v = newRoundTableRadius() + 12;
items[itOrbTeleport] += v;
items[itOrbSpeed] += v;
items[itHolyGrail]++;
addMessage(XLAT("Congratulations! You have found the Holy Grail!"));
if(!eubinary) changes.value_keep(c2->master->alt->emeraldval);
if(!eubinary) c2->master->alt->emeraldval |= GRAIL_FOUND;
achievement_collection(c2->item);
}
else if(c2->item == itKey) {
playSound(c2, "pickup-key");
for(int i=0; i<isize(yendor::yi); i++) if(yendor::yi[i].actual_key() == c2)
yendor::yi[i].found = true;
items[itKey]++;
}
else if(!telekinesis && cantGetGrimoire(c2)) {
// telekinesis checks the condition earlier
dopickup = false;
}
else if(c2->item == itCompass) {
dopickup = false;
}
else if(c2->item == itBuggy || c2->item == itBuggy2) {
if(changes.on) {
if(changes.checking) {
changes.rollback();
return true;
}
changes.commit();
}
items[itOrbSafety] += 7;
if(shmup::on)
shmup::delayed_safety = true;
else {
buggyGeneration = false;
activateSafety(laCrossroads);
}
return true;
}
else if(c2->item == itTreat) {
playSound(c2, "pickup-scroll");
halloween::getTreat(c2);
}
else {
if(c2->item == itBarrow)
for(int i=0; i<c2->landparam; i++) gainItem(c2->item);
else if(c2->item) gainItem(c2->item);
if(c2->item && (vid.bubbles_all || (among(items[c2->item], 5, 10, 25, 50, 100, 250, 500) && vid.bubbles_threshold))) {
drawBubble(c2, iinf[c2->item].color, its(items[c2->item]), 0.5);
}
if(c2->item) {
char ch = iinf[c2->item].glyph;
if(ch == '*') playSound(c2, "pickup-gem");
else if(ch == '$' || ch == 'x') playSound(c2, "pickup-gold");
else if(ch == '%' || ch == ';') playSound(c2, "pickup-potion");
else if(c2->item == itApple) playSound(c2, "apple");
else playSound(c2, "pickup-scroll");
}
}
if(dopickup && c2->item) {
#if CAP_HISTORY
// temporary variable to avoid the "cannot bind bitfield" problem in C++11
eItem dummy = c2->item;
history::findhistory.emplace_back(c2, dummy);
#endif
if(c2->item == itBombEgg && c2->land == laMinefield) {
c2->landparam |= 2;
c2->landparam &= ~1;
}
if(!had_choice)
c2->item = itNone;
}
// if(c2->land == laHive)
// c2->heat = 1;
int numOrb = 0;
for(int i=0; i<ittypes; i++)
if(itemclass(eItem(i)) == IC_ORB && items[i])
numOrb++;
if(numOrb) achievement_count("ORB", numOrb, 0);
if(princess::reviveAt && gold(NO_LOVE) >= princess::reviveAt && !inv::on) {
changes.value_set(princess::reviveAt, 0);
items[itSavedPrincess] = 1;
addMessage("You have enough treasure now to revive the Princess!");
}
return false;
}
EX void glance_message() {
if(gold() >= 300)
addMessage(XLAT("You feel great, like a true treasure hunter."));
else if(gold() >= 200)
addMessage(XLAT("Your eyes shine like gems."));
else if(gold() >= 100)
addMessage(XLAT("Your eyes shine as you glance at your precious treasures."));
else if(gold() >= 50)
addMessage(XLAT("You glance at your great treasures."));
else if(gold() >= 10)
addMessage(XLAT("You glance at your precious treasures."));
else if(gold() > 0)
addMessage(XLAT("You glance at your precious treasure."));
else
addMessage(XLAT("Your inventory is empty."));
}
EX void dropGreenStone(cell *c) {
if(items[itGreenStone] && !passable(c, NULL, P_MONSTER)) {
// NOTE: PL/CZ translations assume that itGreenStone is dropped to avoid extra forms!
addMessage(XLAT("Cannot drop %the1 here!", itGreenStone));
return;
}
if(items[itGreenStone] && c->item == itNone) {
items[itGreenStone]--;
if(false) {
c->item = itNone;
spill(c, eWall(c->wall ^ waFloorA ^ waFloorB), 3);
addMessage(XLAT("The slime reacts with %the1!", itGreenStone));
}
else {
c->item = itGreenStone;
addMessage(XLAT("You drop %the1.", itGreenStone));
if(isHaunted(cwt.at->land))
fail_survivalist();
}
}
else {
if(items[itGreenStone] && c->item == itGreenStone)
addMessage(XLAT("You juggle the Dead Orbs."));
else if(items[itGreenStone] && c->item)
addMessage(XLAT("You give %the1 a grim look.", c->item));
else if(items[itGreenStone]) {
addMessage(XLAT("Cannot drop %the1 here!", itGreenStone));
return;
}
else glance_message();
}
}
EX void moveItem1(cell *from, cell *to, bool activateYendor) {
if(from->item == itOrbYendor) {
bool xnew = true;
for(int i=0; i<isize(yendor::yi); i++)
if(yendor::yi[i].path[0] == from) xnew = false;
if(xnew && activateYendor) yendor::check(from);
for(int i=0; i<isize(yendor::yi); i++)
if(yendor::yi[i].path[0] == from)
yendor::yi[i].path[0] = to;
}
if(from->item == itKey) {
for(int i=0; i<isize(yendor::yi); i++) if(yendor::yi[i].path[YDIST-1] == from)
yendor::yi[i].path[YDIST-1] = to;
for(int i=0; i<isize(yendor::yi); i++) if(yendor::yi[i].actualKey == from)
yendor::yi[i].actualKey = to;
}
if(from->item == itBabyTortoise || to->item == itBabyTortoise) {
tortoise::move_baby(from, to);
}
eItem i = to->item;
to->item = from->item;
from->item = i;
}
EX void moveItem (cell *from, cell *to, bool activateYendor) {
static cell dummy;
dummy.item = itNone;
moveItem1(from, &dummy, activateYendor);
moveItem1(to, from, activateYendor);
moveItem1(&dummy, to, activateYendor);
}
EX bool itemHidden(cell *c) {
return isWatery(c) && !(shmup::on && shmup::boatAt(c));
}
EX eItem localTreasureType() {
lastland = singlepos()->land;
return treasureType(lastland);
}
EX void countLocalTreasure() {
eItem i = localTreasureType();
currentLocalTreasure = i ? items[i] : 0;
if(i != itHyperstone) for(int i=0; i<isize(dcal); i++) {
cell *c2 = dcal[i];
if(c2->cpdist > 3) break;
eItem i2 = treasureType(c2->land);
if(i2 && items[i2] < currentLocalTreasure)
currentLocalTreasure = items[i2];
}
}
#if HDR
static const int NO_TREASURE = 1;
static const int NO_YENDOR = 2;
static const int NO_GRAIL = 4;
static const int NO_LOVE = 8;
#endif
EX int gold(int no IS(0)) {
int i = 0;
if(!(no & NO_YENDOR)) i += items[itOrbYendor] * 50;
if(!(no & NO_GRAIL)) i += items[itHolyGrail] * 10;
if(!(no & NO_LOVE)) {
bool love = items[itOrbLove];
#if CAP_INV
if(inv::on && inv::remaining[itOrbLove])
love = true;
#endif
#if CAP_DAILY
if(daily::on) love = false;
#endif
if(love) i += 30;
}
if(!(no & NO_TREASURE))
for(int t=0; t<ittypes; t++)
if(itemclass(eItem(t)) == IC_TREASURE)
i += items[t];
return i;
}
EX int maxgold() {
int mg = 0;
for(int i=0; i<ittypes; i++)
if(itemclass(eItem(i)) == IC_TREASURE && items[i] > mg)
mg = items[i];
return mg;
}
EX void updateHi_for_code(eItem it, int v, modecode_t xcode) {
if(!yendor::on)
if(v > hiitems[xcode][it])
changes.value_set(hiitems[xcode][it], v);
}
EX void updateHi(eItem it, int v) {
updateHi_for_code(it, v, modecode());
}
EX void gainItem(eItem it) {
int g = gold();
bool lhu = landUnlocked(laHell);
items[it]++; if(it != itLotus) updateHi(it, items[it]);
if(it == itRevolver && items[it] > 6) items[it] = 6;
achievement_collection(it);
multi::treasures[multi::cpid]++;
#if CAP_DAILY
if(daily::on) achievement_final(false);
#endif
int g2 = gold();
if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) {
items[itFireShard]--;
items[itAirShard]--;
items[itWaterShard]--;
items[itEarthShard]--;
gainItem(itElemental);
gainItem(itElemental);
gainItem(itElemental);
gainItem(itElemental);
addMessage(XLAT("You construct some Elemental Gems!", it) + itemcounter(items[itElemental]));
playSound(cwt.at, "elementalgem");
}
if(it == itBounty)
items[itRevolver] = 6;
if(it == itHyperstone && items[itHyperstone] == 10)
achievement_victory(true);
if(chaosmode && gold() >= 300)
achievement_gain_once("CHAOS", rg::chaos);
#if ISMOBILE
if(g < lastsafety + R30*3/2 && g2 >= lastsafety + R30*3/2)
addMessage(XLAT("The Orb of Safety from the Land of Eternal Motion might save you."));
#endif
#define IF(x) if(g < (x) && g2 >= x && !peace::on)
IF(R60/4)
addMessage(XLAT("Collect treasure to access more different lands..."));
IF(R30)
addMessage(XLAT("You feel that you have enough treasure to access new lands!"));
IF(R30*3/2)
addMessage(XLAT("Collect more treasures, there are still more lands waiting..."));
IF(R60)
addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!"));
IF(R30*5/2)
addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell..."));
IF(R10 * 9)
addMessage(XLAT("To access Hell, collect %1 treasures each of 9 kinds...", its(R10)));
if(landUnlocked(laHell) && !lhu) {
addMessage(XLAT("Abandon all hope, the gates of Hell are opened!"));
addMessage(XLAT("And the Orbs of Yendor await!"));
}
}
EX string itemcounter(int qty) {
string s = ""; s += " ("; s += its(qty); s += ")"; return s;
}
EX void gainShard(cell *c2, const char *msg) {
invismove = false;
string s = XLAT(msg);
if(is_mirrorland(c2) && !peace::on) {
collectMessage(c2, itShard);
gainItem(itShard);
s += itemcounter(items[itShard]);
}
addMessage(s);
c2->wall = waNone;
invismove = false;
}
EX void placeItems(int qty, eItem it) {
int dcs = isize(dcal);
for(int i=1; qty && i<dcs; i++) {
cell *c = dcal[i];
if(!c->monst && !c->item && passable(c, NULL, 0))
c->item = it, qty--;
}
}
EX bool cantGetGrimoire(cell *c2, bool verbose IS(true)) {
if(chaosmode) return false;
if(!eubinary && !c2->master->alt) return false;
if(c2->item == itGrimoire && items[itGrimoire] > celldistAlt(c2)/-temple_layer_size()) {
if(verbose)
addMessage(XLAT("You already have this Grimoire! Seek new tomes in the inner circles."));
return true;
}
return false;
}
EX void gainLife() {
items[itOrbLife] ++;
if(items[itOrbLife] > 5 && !inv::on) items[itOrbLife] = 5;
}
EX void collectMessage(cell *c2, eItem which) {
bool specialmode = yendor::on || princess::challenge || cheater || !in_full_game();
if(which == itDodeca && peace::on) return;
if(which == itTreat) ;
else if(isElementalShard(which)) {
int tsh =
items[itFireShard] + items[itAirShard] + items[itWaterShard] + items[itEarthShard] +
items[itElemental];
if(tsh == 0) {
addMessage(XLAT("Collect four different Elemental Shards!"));
addMessage(XLAT("Unbalanced shards in your inventory are dangerous."));
}
else {
string t = XLAT("You collect %the1. (%2)", which, its(items[which]+1));
addMessage(t);
}
}
else if(which == itKey) {
addMessage(XLAT("You have found the Key! Now unlock this Orb of Yendor!"));
}
else if(which == itGreenStone && !items[itGreenStone])
addMessage(XLAT("This orb is dead..."));
else if(which == itGreenStone)
addMessage(XLAT("Another Dead Orb."));
else if(itemclass(which) != IC_TREASURE) {
if(!inv::activating)
addMessage(XLAT("You have found %the1!", which));
}
else if(which == itBabyTortoise) {
playSound(c2, playergender() ? "speak-princess" : "speak-prince");
addMessage(XLAT("Aww, poor %1... where is your family?", which));
}
else if(gold() == 0 && !specialmode)
addMessage(XLAT("Wow! %1! This trip should be worth it!", which));
else if(gold() == 1 && !specialmode)
addMessage(XLAT("For now, collect as much treasure as possible..."));
else if(gold() == 2 && !specialmode)
addMessage(XLAT("Prove yourself here, then find new lands, with new quests..."));
else if(!items[which] && itemclass(which) == IC_TREASURE)
addMessage(XLAT("You collect your first %1!", which));
else if(items[which] == 4 && maxgold() == U5-1 && !specialmode) {
addMessage(XLAT("You feel that %the2 become%s2 more dangerous.", which, c2->land));
addMessage(XLAT("With each %1 you collect...", which, c2->land));
}
else if(items[which] == 9 && maxgold() == 9 && !specialmode) {
if(inv::on) {
addMessage(XLAT("The treasure gives your magical powers!", c2->land));
if(!ISMOBILE)
addMessage(XLAT("Press 'i' to access your magical powers.", c2->land));
}
else
addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land));
}
else if(items[which] == R10 && maxgold() == R10 && !specialmode && !inv::on) {
addMessage(XLAT("You feel that %the1 slowly become%s1 dangerous...", c2->land));
addMessage(XLAT("Better find some other place."));
}
else if(which == itHunting && items[itHunting] == 4 && !specialmode && !ISMOBWEB)
addMessage(XLAT("Hint: hold Alt to highlights enemies and other important features."));
else if(which == itSpice && items[itSpice] == U10*7/10 && !specialmode && isLandIngame(laHell))
addMessage(XLAT("You have a vision of the future, fighting demons in Hell..."));
else if(which == itSpice && items[itSpice] == U10-1 && !specialmode && isLandIngame(laRedRock))
addMessage(XLAT("You will be fighting red rock snakes, too..."));
else if(which == itKraken && items[itKraken] == U10-1 && !specialmode)
addMessage(XLAT("You feel that a magical weapon is waiting for you..."));
// else if(which == itFeather && items[itFeather] == 10)
// addMessage(XLAT("There should be a Palace somewhere nearby..."));
else if(which == itElixir && items[itElixir] == U5-1 && !specialmode)
addMessage(XLAT("With this Elixir, your life should be long and prosperous..."));
else if(which == itRuby && items[itRuby] == U5-1 && !specialmode && isLandIngame(laMountain)) {
addMessage(XLAT("You feel something strange about gravity here..."));
}
else if(which == itPalace && items[itPalace] == U5-1 && !specialmode && isLandIngame(laDungeon)) {
addMessage(XLAT("The rug depicts a man in a deep dungeon, unable to leave."));
}
else if(which == itFeather && items[itFeather] == 25-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the presence of free saves on the Crossroads."));
else if(which == itHell && items[itHell] == 25-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the Orbs of Yendor nearby..."));
else if(which == itHell && items[itHell] == 50-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the Orbs of Yendor in the Crossroads..."));
else if(which == itHell && items[itHell] == 100-1 && !specialmode && inv::on && !chaosmode)
addMessage(XLAT("You feel the Orbs of Yendor everywhere..."));
else if(which == itBone && items[itBone] % 25 == 24 && !specialmode && inv::on)
addMessage(XLAT("You have gained an offensive power!"));
else if(which == itHell && items[itHell] >= 100 && items[itHell] % 25 == 24 && !specialmode && inv::on)
addMessage(XLAT("A small reward for braving the Hell."));
else if(which == itIvory && items[itIvory] == U5-1 && !specialmode && (isLandIngame(laMountain) || isLandIngame(laDungeon))) {
addMessage(XLAT("You feel attuned to gravity, ready to face mountains and dungeons."));
}
else if(which == itBone && items[itBone] == U5+1 && !specialmode && isLandIngame(laHell))
addMessage(XLAT("The Necromancer's Totem contains hellish incantations..."));
else if(which == itStatue && items[itStatue] == U5+1 && !specialmode)
addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny..."));
else if(which == itStatue && items[itStatue] == U5-1 && isLandIngame(laTemple))
addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh..."));
else if(which == itDiamond && items[itDiamond] == U10-2 && !specialmode)
addMessage(XLAT("Still, even greater treasures lie ahead..."));
else if(which == itFernFlower && items[itFernFlower] == U5-1 && isLandIngame(laEmerald))
addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds..."));
else if(which == itEmerald && items[itEmerald] == U5-1 && !specialmode && isLandIngame(laCamelot))
addMessage(XLAT("You overhear miners talking about a castle..."));
else if(which == itEmerald && items[itEmerald] == U5 && !specialmode && isLandIngame(laCamelot))
addMessage(XLAT("A castle in the Crossroads..."));
else if(which == itShard) ;
else {
int qty = (which == itBarrow) ? c2->landparam : 1;
string t;
if(which == itBarrow && items[which] < 25 && items[which] + qty >= 25)
t = XLAT("Your energy swords get stronger!");
else if(maxgold() < 25 && items[which] + qty >= 25)
t = XLAT("You feel even more attuned to the magic of this land!");
else t = XLAT("You collect %the1. (%2)", which, its(items[which]+qty));
addMessage(t);
}
}
EX bool itemHiddenFromSight(cell *c) {
return isWatery(c) && !items[itOrbInvis] && !(items[itOrbFish] && playerInWater())
&& !(shmup::on && shmup::boatAt(c));
}
}