1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2025-08-30 17:27:57 +00:00
Files
hyperrogue/orbgen.cpp
Joseph C. Sible e15138e0a9 Make the Orb of Safety in the Haunted Woods forbidden instead of dangerous
If you somehow get an Orb of Safety in the Haunted Woods (e.g., by bringing
in a Water Elemental followed by a Pirate in a boat with one), using it is
perfectly safe and will put you in the Graveyard.
2025-08-15 23:02:10 -04:00

639 lines
23 KiB
C++

// Hyperbolic Rogue -- orb generation routines
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file orbgen.cpp
* \brief Orb generation: which orbs in which lands, functions generating prize/local orbs, etc.
*/
#include "hyper.h"
namespace hr {
// orbgen flags
#if HDR
enum eOrbLandRelation {
olrForbidden, // never appears: forbidden
olrDangerous, // never appears: would be dangerous
olrUseless, // never appears: useless here
olrNoPrizes, // no prizes in this land
olrNoPrizeOrb,// orb not allowed as a prize
olrPrize25, // prize for collecting 25
olrPrize3, // prize for collecting 3
olrNative, // native orb in this land
olrNative1, // native orb in this land (1)
olrGuest, // extra orb in this land
olrPNative, // Land of Power: native
olrPBasic, // Land of Power: basic orbs
olrPPrized, // Land of Power: prized orbs
olrPNever, // Land of Power: foreign orbs
olrHub, // hub lands
olrMonster, // available from a monster
olrAlways, // always available
olrBurns // burns
};
#endif
#if HDR
namespace orbgenflags {
// generates in the given land from 10 treasures, in the classic mode
static constexpr int LOCAL10 = 1;
// generates in the Crossroads from 10 treasures, in the classic mode
static constexpr int CROSS10 = 2;
// generates in other places from 25 treasures, in the classic mode
static constexpr int GLOBAL25 = 4;
// in OSM you get it once at 10 treasures
static constexpr int OSM_AT10 = 8;
// 'native' functions return this
static constexpr int NATIVE = 64;
// 'local' orb will be also placed in OSM (at 25 treasures); needs LOCAL10
static constexpr int OSM_LOCAL25 = 128;
// 'crossroads' orb will be also placed in OSM (at 50 treasures)
static constexpr int OSM_CROSS50 = 256;
// 'crossroads' orb will be also placed in OSM (at 25 treasures)
static constexpr int OSM_CROSS25 = 512;
// 'global' orb will be also placed in OSM (at 100 treasures)
static constexpr int OSM_GLOBAL100 = 1024;
// do not create in the Crossroads in the tactics mode
static constexpr int NO_TACTIC = (1<<11);
// guest Orb
static constexpr int GUEST = (1<<12);
// typical combinations
static constexpr int S_NATIVE = LOCAL10 | CROSS10 | GLOBAL25 | NATIVE;
static constexpr int S_GUEST = LOCAL10 | OSM_AT10 | GUEST;
static constexpr int S_YENDOR = S_NATIVE | OSM_LOCAL25 | OSM_CROSS50 | OSM_GLOBAL100 | NO_TACTIC;
static constexpr int S_NAT_NT = S_NATIVE | NO_TACTIC;
static constexpr int S_NA_O25 = S_NATIVE | OSM_CROSS25;
}
struct orbinfo {
int flags;
eLand l;
int lchance;
int gchance;
eItem orb;
bool is_native() const { using namespace orbgenflags; return flags & NATIVE; }
};
#endif
EX vector<orbinfo> orbinfos = {
{orbgenflags::S_NATIVE, laGraveyard, 200, 200,itGreenStone}, // must be first so that it does not reduce
{orbgenflags::S_NATIVE, laJungle, 1200, 1500,itOrbLightning},
{orbgenflags::S_NATIVE, laIce, 2000, 1500,itOrbFlash},
{orbgenflags::S_NATIVE, laCaves, 1800, 2000,itOrbLife},
{orbgenflags::S_NATIVE, laAlchemist, 800, 800,itOrbSpeed},
{orbgenflags::S_NATIVE, laDesert, 2500, 1500,itOrbShield},
{orbgenflags::S_YENDOR, laHell, 2000, 1000,itOrbYendor},
{orbgenflags::S_NATIVE, laRlyeh, 1500, 1500,itOrbTeleport},
{orbgenflags::S_NA_O25, laMotion, 2000, 700, itOrbSafety},
{orbgenflags::S_GUEST, laIce, 1500, 0, itOrbWinter},
{orbgenflags::S_GUEST, laDragon, 2500, 0, itOrbWinter},
{orbgenflags::S_GUEST, laDryForest, 2500, 0, itOrbWinter},
{orbgenflags::S_NATIVE, laCocytus, 1500, 1500, itOrbMorph},
{orbgenflags::S_GUEST, laCocytus, 1500, 0, itOrbWinter},
{orbgenflags::S_GUEST, laCaves, 1200, 0, itOrbDigging},
{orbgenflags::S_NATIVE, laDryForest, 500, 4500, itOrbThorns},
{orbgenflags::S_GUEST, laDeadCaves, 1800, 0, itGreenStone},
{orbgenflags::S_NAT_NT, laDeadCaves, 1800, 1500, itOrbDigging},
{orbgenflags::S_NATIVE, laEmerald, 1500, 3500, itOrbPsi},
{orbgenflags::S_NATIVE, laWineyard, 900, 1200, itOrbAether},
{orbgenflags::S_NATIVE, laHive, 800, 1200, itOrbInvis},
{orbgenflags::S_NATIVE, laPower, 0, 3000, itOrbFire},
{orbgenflags::S_NATIVE, laMinefield, 0, 3500, itOrbFriend},
{orbgenflags::S_NATIVE, laTemple, 0, 3000, itOrbDragon},
{orbgenflags::S_NATIVE, laCaribbean, 0, 3500, itOrbTime},
{orbgenflags::S_NATIVE, laRedRock, 0, 2500, itOrbSpace},
{orbgenflags::S_NATIVE, laCamelot, 1000, 1500, itOrbIllusion},
{orbgenflags::S_NATIVE, laOcean, 0, 1500, itOrbEmpathy},
{orbgenflags::S_GUEST, laOcean, 0, 0, itOrbAir},
{orbgenflags::S_NATIVE, laPalace, 0, 4000, itOrbDiscord},
{orbgenflags::S_GUEST, laPalace, 0, 0, itOrbFrog},
{orbgenflags::S_NATIVE, laZebra, 500, 2100, itOrbFrog},
{orbgenflags::S_NAT_NT, laLivefjord, 0, 1800, itOrbFish},
{orbgenflags::S_NAT_NT, laPrincessQuest, 0, 200, itOrbLove},
{orbgenflags::S_NATIVE, laIvoryTower, 500, 4000, itOrbMatter},
{orbgenflags::S_NAT_NT, laElementalWall, 1500, 4000, itOrbSummon},
{orbgenflags::S_NATIVE, laStorms, 1000, 2500, itOrbStunning},
{orbgenflags::S_NATIVE, laOvergrown, 1000, 800, itOrbWoods},
{orbgenflags::S_GUEST, laOvergrown, 1000, 0, itOrbLuck},
{orbgenflags::S_NATIVE, laWhirlwind, 1250, 3000, itOrbAir},
{orbgenflags::S_NATIVE, laHaunted, 1000, 5000, itOrbUndeath},
{orbgenflags::S_NATIVE, laClearing, 5000, 5000, itOrbFreedom},
{orbgenflags::S_NATIVE, laRose, 2000, 8000, itOrbBeauty},
{orbgenflags::S_NATIVE, laWarpCoast, 2000, 8000, itOrb37},
{orbgenflags::S_NATIVE, laDragon, 500, 5000, itOrbDomination},
{orbgenflags::S_NATIVE, laTortoise, 2500, 1500, itOrbShell},
{orbgenflags::S_NATIVE, laEndorian, 150, 2500, itOrbEnergy},
{orbgenflags::S_GUEST, laEndorian, 450, 0, itOrbTeleport},
{orbgenflags::S_NATIVE, laKraken, 500, 2500, itOrbSword},
{orbgenflags::S_NATIVE, laBurial, 500, 2500, itOrbSword2},
{orbgenflags::S_NATIVE, laTrollheim, 750, 1800, itOrbStone},
{orbgenflags::S_NATIVE, laMountain, 400, 3500, itOrbNature},
{orbgenflags::S_NATIVE, laDungeon, 120, 2500, itOrbRecall},
{orbgenflags::S_NATIVE, laReptile, 500, 2100, itOrbDash},
{orbgenflags::S_NATIVE, laBull, 720, 3000, itOrbHorns},
{orbgenflags::S_NATIVE, laPrairie, 0, 3500, itOrbBull},
{orbgenflags::S_GUEST, laWhirlpool, 0, 0, itOrbSafety},
{orbgenflags::S_NATIVE, laVolcano, 0, 7000, itOrbLava},
{orbgenflags::S_NATIVE, laHunting, 0, 2500, itOrbSide3},
{orbgenflags::S_NATIVE, laBlizzard, 0, 2000, itOrbWinter},
{orbgenflags::S_NATIVE, laTerracotta, 800, 2500, itOrbSide1},
{orbgenflags::S_NATIVE, laDual, 600, 2500, itOrbSide2},
{orbgenflags::S_GUEST, laSnakeNest, 2000, 0, itOrbDomination},
{orbgenflags::S_GUEST, laDocks, 3000, 0, itOrbFish},
{orbgenflags::S_GUEST, laDocks, 3000, 0, itOrbDragon},
{orbgenflags::S_GUEST, laDocks, 3000, 0, itOrbDash},
{orbgenflags::S_GUEST, laSwitch, 2000, 0, itOrbSpace},
{orbgenflags::S_NATIVE, laSwitch, 2000, 3000, itOrbPhasing},
{orbgenflags::S_NATIVE, laMagnetic, 2000, 3000, itOrbMagnetism},
{orbgenflags::S_NATIVE, laRuins, 1200, 2500, itOrbSlaying},
{orbgenflags::S_NATIVE, laWestWall, 2000, 4200, itOrbGravity},
{orbgenflags::S_NATIVE, laVariant, 900, 4200, itOrbIntensity},
{orbgenflags::S_GUEST, laVariant, 4000, 0, itOrbFire},
{orbgenflags::S_GUEST, laVariant, 4000, 0, itOrbWinter},
{orbgenflags::S_GUEST, laVariant, 4000, 0, itOrbPhasing},
{orbgenflags::S_GUEST, laVariant, 4000, 0, itOrbDash},
{orbgenflags::S_NATIVE, laBrownian, 900, 4200, itOrbChoice},
{orbgenflags::S_GUEST, laBrownian, 1000, 0, itOrbAir},
{orbgenflags::S_GUEST, laBrownian, 1000, 0, itOrbDash},
{orbgenflags::S_GUEST, laBrownian, 1000, 0, itOrbSafety},
{orbgenflags::S_NATIVE, laFrog, 1000, 1500, itOrbImpact},
{orbgenflags::S_NATIVE, laEclectic, 1000, 1000, itOrbChaos},
{orbgenflags::S_GUEST, laEclectic, 4000, 0, itOrbWinter},
{orbgenflags::S_GUEST, laEclectic, 2000, 0, itOrbLightning},
{orbgenflags::S_NATIVE, laWet, 800, 2500, itOrbPlague},
{orbgenflags::S_GUEST, laWet, 1200, 0, itOrbFish},
{orbgenflags::S_GUEST, laWet, 1200, 0, itOrbAether},
{orbgenflags::S_GUEST, laWet, 1200, 0, itOrbFrog},
{orbgenflags::S_NATIVE, laCursed, 400, 1500, itOrbPurity},
{orbgenflags::S_NAT_NT, laDice, 500, 800, itOrbLuck},
{orbgenflags::S_GUEST, laDice, 750, 0, itOrbAir},
{orbgenflags::S_NATIVE, laWhirlpool, 0, 2000, itOrbWater}, // needs to be last
};
EX eItem nativeOrbType(eLand l) {
if(isElemental(l)) l = laElementalWall;
if(inv::on && (l == laMirror || l == laMirrorOld || isCrossroads(l)))
return itOrbMirror;
if(l == laMirror || l == laMirrorOld) return itShard;
for(auto& oi: orbinfos)
if(oi.l == l && oi.is_native())
return oi.orb;
return itNone;
}
const orbinfo& getNativityOrbInfo(eItem orb) {
for(auto& oi: orbinfos)
if(oi.orb == orb && oi.is_native())
return oi;
static orbinfo oi;
oi.l = laMirror;
return oi;
}
EX string olrDescriptions[18] = {
"forbidden to find in %the1",
"too dangerous to use in %the1",
"useless in %the1",
"only native Orbs allowed in %the1",
"this Orb is never unlocked globally (only hubs)",
"collect 25 %2 to unlock it in %the1",
"collect 3 %2 to unlock it in %the1",
"native to %the1 (collect 10 %2)",
"native to %the1 (collect 1 %2)",
"secondary in %the1 (collect 10 %3, or 25 %2)",
"the native Orb of %the1",
"this Orb appears on floors and is used by witches",
"a prized Orb, it appears only in cabinets",
"this Orb never appears in %the1",
"Hub Land: orbs appear here if unlocked in their native land",
"kill a monster, or collect 25 %2",
"always available",
"would be destroyed in %the1"
};
EX eOrbLandRelation getOLR(eItem it, eLand l) {
if(l == laMotion && it == itOrbChoice && !inv::on) return olrUseless;
if(l == laPower) {
if(it == itOrbFire) return olrPNative;
if(
it == itOrbFlash || it == itOrbSpeed || it == itOrbWinter || it == itOrbAether ||
it == itOrbLife) return olrPBasic;
if(
it == itOrbLightning || it == itOrbThorns || it == itOrbInvis ||
it == itOrbShield || it == itOrbTeleport || it == itOrbPsi ||
it == itOrbDragon || it == itOrbIllusion || it == itOrbTime)
return olrPPrized;
return olrPNever;
}
if(it == itOrbWater && l == laWestWall) return olrUseless;
if(it == itOrbSafety && l == laCrossroads5) return olrDangerous;
if(it == itOrbFire && l == laKraken) return olrUseless;
if(it == itOrbDragon && l == laKraken) return olrUseless;
if(it == itOrbDigging && l == laKraken) return olrUseless;
if(it == itOrbIllusion && l == laKraken) return olrUseless;
if(it == itOrbYendor && among(l, laWhirlwind, laWestWall)) return olrUseless;
if(it == itOrbLife && (l == laKraken)) return olrUseless;
if(l == laVolcano && it == itOrbSafety) return olrDangerous;
if(l == laVariant && it == itOrbSafety) return olrDangerous;
if(l == laVolcano && itemBurns(it)) return olrBurns;
if(it == itOrbAir && l == laAlchemist) return olrUseless;
// if(it == itOrbShield && l == laMotion) return olrUseless;
if(it == itOrbIllusion && l == laCamelot) return olrNative1;
if(it == itOrbLove) return olrNoPrizeOrb;
if(nativeOrbType(l) == it) return olrNative;
for(const orbinfo& oi: orbinfos)
if((oi.flags & orbgenflags::GUEST) && oi.l == l && oi.orb == it)
return olrGuest;
if(it == itOrbLuck && l == laIvoryTower)
return olrUseless;
if(it == itOrbLuck && l == laEndorian)
return olrUseless;
if(it == itOrbLuck && l == laDungeon)
return olrUseless;
if(it == itOrbWater && l == laRedRock)
return olrUseless;
if(it == itOrbLuck && l == laTortoise)
return olrUseless;
if(it == itOrbLuck && l == laMountain)
return olrUseless;
if(it == itOrbLuck && l == laWestWall)
return olrUseless;
if(it == itOrbLuck && l == laCamelot)
return olrUseless;
if(it == itOrbLuck && l == laHaunted)
return olrUseless;
if(it == itOrbNature && l == laWineyard)
return olrDangerous;
if((it == itOrbFrog || it == itOrbDiscord) && l == laPrincessQuest)
return olrGuest;
if(it == itOrbDragon && l == laRlyeh)
return olrMonster;
if(it == itOrbSafety && l == laWhirlpool)
return olrAlways;
if(it == itOrbSafety && l == laPrairie)
return olrAlways;
if(it == itGreenStone && isHaunted(l))
return olrAlways;
if(it == itOrbWater && l == laLivefjord)
return olrMonster;
if(isCrossroads(l) || l == laOcean)
return olrHub;
if(it == itOrbSlaying && !among(l,
laMirror, laHell, laEmerald, laDryForest, laCamelot, laPalace, laStorms, laRose, laTortoise, laBurial, laDungeon, laReptile,
laPrairie, laBull, laVolcano, laTerracotta, laRuins, laVariant, laEclectic, laBrownian, laCursed))
return olrUseless;
if(l == laCocytus)
if(it == itOrbDragon || it == itOrbFire || it == itOrbFlash || it == itOrbLightning)
return olrDangerous;
if(it == itOrbSafety) {
if(l == laCaves || l == laLivefjord || l == laRedRock || l == laCocytus || l == laHell ||
l == laDesert || l == laAlchemist || l == laDeadCaves || l == laMinefield ||
l == laDragon || l == laWet || l == laCursed)
return olrDangerous;
if(isHaunted(l))
return olrForbidden;
}
if(it == itOrbMatter)
if(among(l, laCaves, laEmerald, laAlchemist, laCaribbean, laMinefield, laCocytus, laWestWall))
return olrUseless;
if(l == laPrincessQuest)
if(among(it, itOrbAether, itOrbFlash, itOrbTeleport, itOrbSummon, itOrbFreedom, itOrbFriend, itOrbPhasing, itOrbChaos))
return olrForbidden;
if(l == laTemple)
return olrNoPrizes;
if(it == itOrbDigging) {
if(among(l, laCaves, laOcean, laLivefjord, laEmerald, laDesert, laDeadCaves, laRedRock, laCaribbean, laGraveyard,
laMountain, laHunting, laWarpSea, laWarpCoast, laCursed, laWet))
return olrPrize25;
return olrUseless;
}
if(it == itOrbWoods && (l == laWarpSea || l == laWarpCoast))
return olrDangerous;
if(it == itOrbWoods && !among(l,
laDryForest, laWineyard, laCaribbean, laOvergrown, laHaunted, laHauntedWall, laHauntedBorder, laTortoise, laFrog, laEclectic,
laVariant, laBull))
return olrUseless;
if(it == itShard) {
if(l == laDesert || l == laIce || l == laJungle || l == laGraveyard ||
l == laRlyeh || l == laHell || l == laDryForest || l == laWineyard ||
l == laHive || l == laCamelot || l == laRedRock || l == laPalace ||
l == laLivefjord || l == laZebra || isElemental(l) || l == laPrincessQuest ||
l == laDragon || l == laTortoise || l == laBurial || l == laTrollheim ||
l == laOcean || l == laHaunted || l == laWarpCoast || l == laRose)
return olrPrize25;
return olrForbidden;
}
if(it == itOrbWater)
if(l == laMotion || l == laZebra || l == laIvoryTower || l == laEndorian ||
l == laMountain || l == laReptile || l == laDungeon)
return olrUseless;
if(it == itOrbWinter && l == laMinefield)
return olrForbidden;
if(it == itOrbWinter && !among(l, laRlyeh, laTemple, laVolcano, laCursed))
return olrUseless;
if(it == itOrbLife && l == laMotion)
return olrUseless;
if(it == itOrbFish && l == laKraken)
return olrAlways;
if(it == itOrbSword && l == laBurial)
return olrAlways;
if(it == itOrbFish && !among(l, laOcean, laLivefjord, laWhirlpool, laCamelot, laTortoise, laWarpCoast, laWarpSea, laCocytus, laBrownian, laVariant, laWet, laFrog))
return olrUseless;
if(it == itOrbDomination && l != laOcean && l != laRedRock && l != laDesert &&
l != laRlyeh && l != laDragon)
return olrUseless;
if(it == itOrbIllusion) return olrPrize3;
if(l == laTortoise)
if(it == itOrbFlash || it == itOrbLightning || it == itOrbFreedom ||
it == itOrbPsi || it == itOrbFriend || it == itOrbDragon)
return olrForbidden;
if(l == laEndorian)
if(it == itOrbDragon || it == itOrbFire || it == itOrbLightning || it == itOrbLava)
return olrDangerous;
if(isHaunted(l) && it == itOrbLava)
return olrUseless;
if(l == laGraveyard && it == itOrbLava)
return olrUseless;
if(l == laDryForest)
if(it == itOrbFire || it == itOrbLightning || it == itOrbLava)
return olrDangerous;
if(l == laWet && among(it, itOrbDragon, itOrbLava, itOrbFire))
return olrUseless;
if(l == laDungeon) {
if(it == itOrbSafety || it == itOrbFrog ||
it == itOrbTeleport || it == itOrbMatter || it == itOrbNature ||
it == itOrbAether || it == itOrbSummon || it == itOrbStone || it == itOrbChaos)
return olrForbidden;
}
if(l == laTerracotta && it == itOrbFriend)
return olrDangerous;
return olrPrize25;
}
EX int orbsUnlocked() {
int i = 0;
for(int t=0; t<ittypes; t++)
if(itemclass(eItem(t)) == IC_TREASURE && items[t] >= R10)
i++;
return i;
}
EX ld orbprizefun(int tr) {
if(tactic::on) return 1;
if(tr < 10) return 0;
return .6 + .4 * log(tr/25.) / log(2);
}
EX ld orbcrossfun(int tr) {
if(tactic::on || (ls::single() && isCrossroads(specialland))) return 1;
if(tr < 10) return 0;
if(tr > 25) return 1;
return (tr*2 + 50) / 100.;
}
EX bool buildPrizeMirror(cell *c, int freq) {
if(inv::on) return false;
if(items[itShard] < 25) return false;
if(freq && hrand(freq * 100 / orbprizefun(items[itShard])) >= 100)
return false;
return mirror::build(c);
}
#if HDR
extern cellwalker cwt;
#endif
EX eLand getPrizeLand(cell *c IS(cwt.at)) {
eLand l = c->land;
if(isElemental(l)) l = laElementalWall;
if(isHaunted(l)) l = laHaunted;
if(l == laMercuryRiver) l = laTerracotta;
if(l == laWarpSea) l = laWarpCoast;
if(l == laPalace && princess::dist(c) < OUT_OF_PRISON)
l = laPrincessQuest;
return l;
}
EX void placePrizeOrb(cell *c) {
if(peace::on) return;
if(daily::on) return;
eLand l = getPrizeLand(c);
// these two lands would have too much orbs according to normal rules
if(l == laPalace && hrand(100) >= 20) return;
if(l == laPrincessQuest && hrand(100) >= 20) return;
if(l == laGraveyard && hrand(100) >= 15) return;
if(l == laBurial && hrand(100) >= 10) return;
if(l == laLivefjord && hrand(100) >= 35) return;
if(l == laMinefield && hrand(100) >= 25) return;
if(l == laElementalWall && hrand(100) >= 25) return;
for(auto& oi: orbinfos) {
if(!(oi.flags & orbgenflags::GLOBAL25)) continue;
int mintreas = 25;
if(inv::on) {
if(oi.flags & orbgenflags::OSM_GLOBAL100) mintreas = 100;
else continue;
}
eOrbLandRelation olr = getOLR(oi.orb, l);
if(olr != olrPrize25 && olr != olrPrize3) continue;
int treas = items[treasureType(oi.l)];
if(olr == olrPrize3) treas *= 10;
if(olr == olrPrize25 || olr == olrPrize3 || olr == olrGuest || olr == olrMonster || olr == olrAlways) {
if(treas < mintreas) continue;
}
else continue;
int gch = oi.gchance;
if(!gch) continue;
gch = int(gch / orbprizefun(treas));
if(hrand(gch) >= 60) continue;
if(oi.orb == itOrbWater && c->land != laOcean && c->land != laKraken) {
if(cellHalfvine(c)) continue;
c->item = oi.orb;
c->wall = waStrandedBoat;
return;
}
c->item = oi.orb;
}
// printf("land: %s orb: %s\n", dnameof(l), dnameof(c->item));
}
// 10 not in chaos, less in chaos
EX int treasureForLocal() {
return (ls::any_chaos() ? 1+hrand(10) : 10);
}
EX bool extra_safety_for_memory(cell *c) {
if(hyperbolic && (arcm::in() || S3 > 3) && !quotient && !tactic::on && in_full_game()) {
if(hrand(1000) < 1) {
c->item = itOrbSafety;
return true;
}
}
return false;
}
EX void placeLocalOrbs(cell *c) {
eLand l = c->land;
if(l == laZebra && c->wall == waTrapdoor) return;
if(isGravityLand(l) && l != laWestWall && cellEdgeUnstable(c)) return;
if(isElemental(l)) l = laElementalWall;
if(daily::on) return;
if(extra_safety_for_memory(c)) return;
if(peace::on) return;
for(auto& oi: orbinfos) {
if(!(oi.flags & orbgenflags::LOCAL10)) continue;
if(oi.l != l) continue;
if(yendor::on && (oi.orb == itOrbSafety || oi.orb == itOrbYendor))
continue;
if(!oi.lchance) continue;
int ch = hrand(oi.lchance);
if(ch == 1 && ls::any_chaos() && hrand(2) == 0 && items[treasureType(oi.l)] * landMultiplier(oi.l) >= (11+hrand(15)))
ch = 0;
int tc = items[treasureType(oi.l)] * landMultiplier(oi.l);
int tcmin = treasureForLocal();
if(inv::on) {
if(!(oi.flags & orbgenflags::OSM_LOCAL25))
tc = 0;
else
tcmin = 25;
}
if(ch == 0 && tc >= tcmin) {
// printf("local orb\n");
c->item = oi.orb;
if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat;
return;
}
else if(oi.gchance && (ch >= 1 && ch < 11) && getOLR(itShard, l) == olrPrize25 && l != laRedRock && l != laWhirlwind)
buildPrizeMirror(c, 10);
else if(oi.gchance && (ch >= 11 && ch < 11+PRIZEMUL))
placePrizeOrb(c);
}
}
EX void placeLocalSpecial(cell *c, int outof, int loc IS(1), int priz IS(1)) {
if(safety || daily::on || extra_safety_for_memory(c) || peace::on) return;
int i = hrand(outof);
if(i < loc && items[treasureType(c->land)] >= treasureForLocal() && !inv::on)
c->item = nativeOrbType(c->land);
else if(i >= loc && i < loc + PRIZEMUL * priz)
placePrizeOrb(c);
}
EX void placeCrossroadOrbs(cell *c) {
if(racing::on) return;
if(peace::on) return;
if(daily::on) return;
for(auto& oi: orbinfos) {
if(!(oi.flags & orbgenflags::CROSS10)) continue;
if(!oi.gchance) continue;
int treas = items[treasureType(oi.l)] * landMultiplier(oi.l);
int mintreas = 10;
if(inv::on) {
if(oi.flags & orbgenflags::OSM_CROSS25)
mintreas = 25;
else if(oi.flags & orbgenflags::OSM_CROSS50)
mintreas = 50;
else continue;
}
if(ls::single() && isCrossroads(specialland)) {
if(oi.flags & orbgenflags::NO_TACTIC)
continue;
else mintreas = 0;
}
if(treas < mintreas) continue;
if(oi.orb == itOrbSafety && c->land == laCrossroads5) continue;
int mul = c->land == laCrossroads5 ? 10 : 1;
int gch = oi.gchance;
if(!inv::on) gch /= orbcrossfun(treas); else gch /= 2;
if(hrand(gch) >= mul) continue;
if(hrand(50+items[itHyperstone]) >= 50) continue;
c->item = oi.orb;
if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat;
}
}
EX void placeOceanOrbs(cell *c) {
if(peace::on) return;
for(auto& oi: orbinfos) {
if(!(oi.flags & orbgenflags::CROSS10)) continue;
int treas = items[treasureType(oi.l)] * landMultiplier(oi.l);
int mintreas = 10;
if(inv::on) {
if(oi.flags & orbgenflags::OSM_CROSS25)
mintreas = 25;
else if(oi.flags & orbgenflags::OSM_CROSS50)
mintreas = 50;
else continue;
}
if(treas < mintreas) continue;
if(!oi.gchance) continue;
if(oi.orb == itOrbLife) continue; // useless
if(hrand(oi.gchance) >= 20) continue;
c->item = oi.orb;
}
}
}