// Hyperbolic Rogue - Complex features part II // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file complex2.cpp * \brief Continuation of complex.cpp * * Includes: Brownian, Irradiated, Free Fall */ #include "hyper.h" #ifdef CAP_COMPLEX2 namespace hr { EX namespace brownian { #if HDR const int level = 5; #endif map> > futures; int centersteps = 0; int totalsteps = 0; void rise(cell *c, int val) { if(c->wall == waSea) c->wall = waNone; if(c->land == laOcean || c->land == laNone) { c->land = laBrownian; c->landparam = 0; } c->bardir = NOBARRIERS; forCellCM(c1, c) { c1->bardir = NOBARRIERS; if(c1->mpdist > BARLEV) { setdist(c1, BARLEV, c); } if(c1->land == laOcean) { c1->land = laBrownian; c1->landparam = 0; c1->wall = waSea; } } c->landparam += val; } static const int FAT = (-100); // less than 0 void recurse(cell *c, int fatten_from) { int dl = getDistLimit(); while(true) { int cd = celldist(c); bool fat = cd > fatten_from; totalsteps++; if(cd >= dl * (fat ? 4 : ISMOBILE ? 2 : 3) + celldist(cwt.at)) { cell *c1 = c; while(true) { cell *c2 = ts::left_parent(c1, celldist); if(!c2 || c2->mpdist < BARLEV) break; setdist(c2, BARLEV, c1); c1 = c2; } futures[c1].emplace_back(c, fatten_from); return; } if(c->mpdist <= 7 || !among(c->land, laNone, laOcean, laBrownian) || (c->land != laBrownian && c->bardir != NODIR)) { centersteps++; return; } cell *c2 = c->cmove(hrand(c->type)); int cd2 = celldist(c2); // while(hrand(1000) < 1000 * chance) recurse(c); if(!fat && (cd2 > fatten_from || hrand(100000) == 0)) { recurse(c, FAT); fatten_from = FAT; } else if(fat) recurse(c, cd + dl * 6); rise(c, fat ? 256 : 1); c = c2; } } EX void dissolve_brownian(cell *c, int x) { if(c->land == laBrownian) { if(among(c->wall, waNone, waStrandedBoat, waMineOpen, waFire)) { if(c->landparam >= 4 * level) c->landparam = 4 * level - 1; c->landparam -= level * x; c->wall = waNone; if(c->landparam < 0) c->wall = waSea, c->landparam = 0; if(c->landparam == 0) c->landparam = 1; } } } EX void dissolve(cell *c, int x) { destroyTrapsAround(c); if(c->land == laBrownian) dissolve_brownian(c, x); else if(c->wall == waRed2) c->wall = waRed1; else if(c->wall == waRed3) c->wall = waRed2; else if(among(c->wall, waRed1, waDeadfloor2, waRubble, waBoat, waFire, waCIsland, waCIsland2, waBigBush, waSmallBush)) c->wall = waNone; else if(c->wall == waStrandedBoat) c->wall = waNone; else if(c->wall == waFrozenLake) c->wall = waLake; else if(among(c->wall, waReptile, waGargoyleFloor) || cellUnstable(c)) c->wall = waChasm; else if(among(c->wall, waNone, waDock, waBurningDock, waFloorA, waFloorB, waCavefloor, waDeadfloor, waMineMine, waMineUnknown, waMineOpen, waOpenGate, waClosePlate, waOpenPlate, waGargoyleBridge, waReptileBridge)) c->wall = waSea; else if(cellHalfvine(c)) destroyHalfvine(c, waNone, 4); } EX void init(cell *c) { if(!hyperbolic) return; recurse(c, FAT); recurse(c, FAT); } EX void init_further(cell *c) { if(!hyperbolic) return; int dl = getDistLimit(); dynamicval be(generatingEquidistant, true); int gdir = -1; for(int i=0; itype; i++) { if(c->move(i) && c->move(i)->mpdist < c->mpdist) gdir = i; } if(gdir < 0) return; cellwalker cw(c, gdir); for(int i=0; i<4; i++) { cw += revstep; setdist(cw.at, BARLEV, cw.peek()); buildEquidistant(cw.at); println(hlog, "from ", cw.peek(), " to ", cw.at, ", land = ", dnameof(cw.at->land), " lp = ", cw.at->landparam); } if(c->land != laOcean || !no_barriers_in_radius(cw.at, 2)) return; println(hlog, "brownian::init ", cw.at, " in distance ", celldistance(cw.at, cwt.at)); recurse(cw.at, celldist(c) + dl * 3); recurse(cw.at, celldist(c) + dl * 3); cell *c2 = c; while(c2->mpdist > 7) { forCellEx(c3, c2) if(c3->mpdist < c2->mpdist) { c2 = c3; goto next; } break; next: ; } if(!c2->monst && c2->wall != waBoat) c2->monst = moAcidBird; } EX void apply_futures(cell *c) { if(futures.count(c)) { auto m = move(futures[c]); futures.erase(c); for(auto p: m) recurse(p.first, p.second); futures.erase(c); printf("centersteps = %d futures = %d totalsteps = %d\n", centersteps, isize(futures), totalsteps); } } EX void build(cell *c, int d) { if(!hyperbolic) c->wall = waNone, c->landparam = 256; ONEMPTY { if(hrand(10000) < min(250, 100 + 2 * PT(kills[moAcidBird] + kills[moBrownBug], 50)) * (25 + min(items[itBrownian], 100)) / 25 && c->landparam >= 4 && c->landparam < 24) c->item = itBrownian; if(hrand_monster(8000) < 15 + items[itBrownian]) c->monst = moAcidBird; else if(hrand_monster(8000) < 15) c->monst = moAlbatross; else if(hrand_monster(8000) < 15 + items[itBrownian]) { c->monst = moBrownBug; c->hitpoints = 3; } } } EX colortable colors = { 0x603000, 0x804000, 0xA05000, 0xC09050, 0xE0D0A0 }; EX color_t get_color(int y) { return y < level ? gradient(colors[0], colors[1], 1, y, level-1) : y < 2 * level ? colors[2] : y < 3 * level ? colors[3] : colors[4]; } EX color_t& get_color_edit(int y) { return y < level/2 ? colors[0] : y < level ? colors[1] : y < 2 * level ? colors[2] : y < 3 * level ? colors[3] : colors[4]; } int hrc = addHook(hooks_removecells, 0, [] () { vector to_remove; for(auto p: futures) if(is_cell_removed(p.first)) to_remove.push_back(p.first); for(auto r: to_remove) futures.erase(r); }) + addHook(hooks_clearmemory, 0, [] () { futures.clear(); }) + addHook(hooks_gamedata, 0, [] (gamedata* gd) { gd->store(futures); }); EX } EX namespace westwall { EX void switchTreasure(cell *c) { c->item = itNone; if(safety) return; if(hrand(5000) < PT(100 + 2 * (kills[moAirElemental] + kills[moWindCrow]), 200) && c->landparam >= 5 + items[itWest]) c->item = itWest; else if(hrand(5000) < 20*PRIZEMUL) placeLocalOrbs(c); } EX int coastvalEdge1(cell *c) { if(c->land == laWestWall && !c->landparam) buildEquidistant(c); return coastvalEdge(c); } void build(vector& whirlline, int d) { again: cell *at = whirlline[isize(whirlline)-1]; cell *prev = whirlline[isize(whirlline)-2]; for(int i=0; itype; i++) if(at->move(i) && coastvalEdge1(at->move(i)) == d && at->move(i) != prev) { whirlline.push_back(at->move(i)); goto again; } } void moveAt(cell *c, manual_celllister& cl) { if(cl.listed(c)) return; if(c->land != laWestWall) return; vector whirlline; int d = coastvalEdge(c); whirlline.push_back(c); whirlline.push_back(gravity_state == gsAnti ? ts::right_of(c, coastvalEdge1) : ts::left_of(c, coastvalEdge1)); build(whirlline, d); reverse(whirlline.begin(), whirlline.end()); build(whirlline, d); int z = isize(whirlline); for(int i=0; impdist == BARLEV) switchTreasure(whirlline[i]); } for(int i=0; iitem) animateMovement(match(whirlline[i+1], whirlline[i]), LAYER_BOAT); } for(int i=0; iitem), "and mpdist is ", yi[i].actual_key()->mpdist); moveAt(yi[i].actual_key(), cl); if(yi[i].actualKey) { if(gravity_state == gsAnti) yi[i].age--; else yi[i].age++; setdist(yi[i].actual_key(), 8, NULL); } } } EX } EX namespace variant { #if HDR struct feature { color_t color_change; int rate_change; eMonster wanderer; void (*build)(cell*); }; extern array features; #endif #define VF [] (cell *c) bool hrand_var(int i) { return hrand_monster(i) < 25 + items[itVarTreasure] + yendor::hardness(); } array features {{ feature{(color_t)(-0x202020), 5, moNecromancer, VF { if(c->wall == waNone && hrand(1500) < 20) c->wall = waFreshGrave; if(hrand(20000) < 10 + items[itVarTreasure]) c->monst = moNecromancer; }}, {0x000010, 5, moLancer, VF { if(c->wall == waNone && !c->monst && hrand_var(80000)) c->monst = moLancer; } }, {0x100008,15, moMonk, VF { if(c->wall == waNone && !c->monst && hrand_var(80000)) c->monst = moMonk; } }, {0x080010, 5, moCrusher, VF { if(c->wall == waNone && !c->monst && hrand_var(80000)) c->monst = moCrusher; } }, {0x181418, 5, moSkeleton, VF { if(c->wall == waNone && !c->monst && hrand_var(80000)) c->monst = moSkeleton, c->hitpoints = 3; } }, {0x180000, 5, moPyroCultist, VF { if(c->wall == waNone && !c->monst && hrand_var(80000)) c->monst = moPyroCultist; } }, {0x00000C, 2, moFlailer, VF { if(c->wall == waNone && !c->monst && hrand_var(80000)) c->monst = moFlailer; } }, {0x1C0700, 1, moHedge, VF { if(c->wall == waNone && !c->monst && hrand_var(80000) && valence() == 3) c->monst = moHedge; } }, {0x000c00,-1, moNone, VF { if(hrand(1500) < 30) createArrowTrapAt(c, laVariant); } }, {0x001200,-1, moNone, VF { if(hrand(1500) < 50 && c->wall == waNone) c->wall = waTrapdoor; } }, {0x000c18,-1, moNone, VF { if(hrand(1500) < 30) build_pool(c, true); } }, {0x040A00,-1, moNone, VF { if(c->wall == waNone && !c->monst && !c->monst && hrand(1500) < 10) c->wall = waThumperOff; } }, {0x080A00,-1, moNone, VF { if(hrand(1500) < 20 && !c->monst && !c->wall) c->wall = waFireTrap; } }, {0x0C0A00, 0, moNone, VF { bool inyendor = yendor::on && specialland == laVariant && celldist(c) < 7; int chance = inyendor ? 800 : 100; if(c->wall == waNone && !c->monst && hrand(5000) < chance) c->wall = waExplosiveBarrel; } }, {0x060D04, 0, moNone, VF { if(c->wall == waNone && !c->monst && pseudohept(c) && hrand(30000) < 25 + items[itVarTreasure]) if(buildIvy(c, 0, c->type) && !peace::on) c->item = itVarTreasure; }}, {0x000A08, 0, moNone, VF { if(c->wall == waNone && !c->monst && hrand(5000) < 100) c->wall = waSmallTree; }}, {0x100A10, 1, moRagingBull, VF { if(c->wall == waNone && hrand(10000) < 10 + items[itVarTreasure]) c->monst = moSleepBull, c->hitpoints = 3; }}, {0x00110C, 0, moNone, VF { if(c->wall == waNone && !c->monst && hrand(5000) < 100) c->wall = waBigTree; }}, {0x000A28, 1, moNone, VF { if(hrand(500) < 10) build_pool(c, false); } }, {0x100A00, 2, moVariantWarrior, VF { if(c->wall == waNone && !c->monst && hrand_var(40000)) c->monst = moVariantWarrior; }}, {0x100708, 1, moRatling, VF { if(c->wall == waNone && !c->monst && hrand_var(50000)) c->monst = moRatling; }} }}; #undef VF EX } EX namespace camelot { /** number of Grails collected, to show you as a knight */ EX int knighted = 0; /** this value is used when using Orb of Safety in the Camelot in Pure Tactics Mode */ EX int anthraxBonus = 0; vector knight_names = { "DivisionByZero", "tricosahedron", "BillSYZ0317", "rjdimo", "Person", "Strange Yeah", "godamnsteemsux", "Allalinor", "Spuddnik", "QETC Crimson", "cannobeens", "Dylgear", "Patashu", "hotdogPi", "vincent", "Chimera245", "BreedPineapple", "TaeK", "Aliased", "Vipul", "english5040", "marvincast", "Lord Ignus", "Darth Calculus", "*****-mail", "Wheat Wizard", "xiqnyl", "zelda0x181e", "ad-jones", "mtomato", "Fififionek", "jkvw3", "J Pystynen", "krstefan11", "green orange", "ZombieGirl1616", "z.chlebicki", "Lokki", "zeno", "Fulgur14", "uqblf", "santagonewrong", "CtrlAltDestroy", "Vee Nought", "archmageomega", "Wroclaw", "lunaduskjr", "loper", "Sharklilly", "Dasypus Daimon", "Mateusz", "guysige", "MrSprucetree", "Atia", "nerdyjoe", "florrat", "psychopanda121", "Sprite Guard", "Fluffiest Princess", "no stop", "Kieroshark", "juhdanad", "lllllllllwith10ls", "NattieGilgamesh", "chocokels", "oren", "sir289", "pringle", "Spicy", "Cheetahs", "xy2", "Heavpoot", "2jjy", "Hirgon", "martradams", "TravelDemon", "The Big Extent", "fones4jenke13", "ekisacik", "j0eymcgrath", "EatChangmyeong", "craus", "akei_arkay", "__________", "Ichigo Jam", "supernewton", "Westville", "Huja (chat off)", "Just A Witch", "Particles", "The Horrendous Space Kablooie", "ddtizm", "amagikuser", "vkozulya", "gassa", "Factitious", "wonderfullizardofoz", "woofmao", "CandorVelexion", "Toricon", "Vectis99", "RobotNerd277", "jerrypoons", "MagmaMcFry", "unczane", "glass", "Wegener", "JeLomun", "kip", "Fooruman", "Prezombie", "ashley89", "bjobae", "MFErtre", "Roaringdragon2", "howilovepi", "Yulgash", "coper", "Tirear", "qoala _", "Tiegon", "Metroid26", "Sklorg", "Fumblestealth", "Toph", "jruderman", "ray", "Deathroll", "Sinquetica", "mootmoot", "Noobinator", "Gunblob", "Snakebird Priestess", "brisingre", "Khashishi", "Berenthas", "Misery", "Altripp", "Aldrenean", }; map knight_id; EX string knight_name(cell *c) { if(!knight_id.count(c)) knight_id[c] = rand() % isize(knight_names); return knight_names[knight_id[c]]; } EX void move_knight(cell *c1, cell *c2) { LATE ( move_knight(c1, c2); ) if(knight_id.count(c1)) { knight_id[c2] = knight_id[c1]; knight_id.erase(c1); } } EX void roundTableMessage(cell *c2) { if(!euclid && !cwt.at->master->alt) return; if(!euclid && !c2->master->alt) return; int dd = celldistAltRelative(c2) - celldistAltRelative(cwt.at); bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius()); if(dd>0) { if(grailWasFound(cwt.at)) { addMessage(XLAT("The Knights congratulate you on your success!")); knighted = roundTableRadius(cwt.at); } else if(!tooeasy) addMessage(XLAT("The Knights laugh at your failure!")); } else { if(grailWasFound(cwt.at)) addMessage(XLAT("The Knights stare at you!")); else if(tooeasy) { if(!tactic::on) addMessage(XLAT("Come on, this is too easy... find a bigger castle!")); } else addMessage(XLAT("The Knights wish you luck!")); } } EX void knightFlavorMessage(cell *c2) { if(!eubinary && !c2->master->alt) { addMessage(XLAT("\"I am lost...\"")); return; } if(tactic::on) { addMessage(XLAT("\"The Knights of the Horocyclic Table salute you!\"")); return; } bool grailfound = grailWasFound(c2); int rad = roundTableRadius(c2); bool tooeasy = (rad < newRoundTableRadius()); static int msgid = 0; changes.value_keep(msgid); retry: if(msgid >= 32) msgid = 0; if(msgid == 0 && grailfound) { addMessage(XLAT("\"I would like to congratulate you again!\"")); } else if(msgid == 1 && !tooeasy) { addMessage(XLAT("\"Find the Holy Grail to become one of us!\"")); } else if(msgid == 2 && !tooeasy) { addMessage(XLAT("\"The Holy Grail is in the center of the Round Table.\"")); } #if CAP_CRYSTAL else if(msgid == 3 && cryst) { if(crystal::pure()) addMessage(XLAT("\"Each piece of the Round Table is exactly %1 steps away from the Holy Grail.\"", its(roundTableRadius(c2)))); else addMessage(XLAT("\"According to Merlin, the Round Table is a perfect Euclidean sphere in %1 dimensions.\"", its(ginf[gCrystal].sides/2))); } #endif else if(msgid == 3 && !peace::on && in_full_game()) { addMessage(XLAT("\"I enjoy watching the hyperbug battles.\"")); } else if(msgid == 4 && in_full_game()) { addMessage(XLAT("\"Have you visited a temple in R'Lyeh?\"")); } else if(msgid == 5 && in_full_game()) { addMessage(XLAT("\"Nice castle, eh?\"")); } else if(msgid == 6 && items[itSpice] < 10 && !peace::on && in_full_game()) { addMessage(XLAT("\"The Red Rock Valley is dangerous, but beautiful.\"")); } else if(msgid == 7 && items[itSpice] < 10 && !peace::on && in_full_game()) { addMessage(XLAT("\"Train in the Desert first!\"")); } else if(msgid == 8 && sizes_known() && !tactic::on) { string s = ""; if(0) ; #if CAP_CRYSTAL else if(cryst) s = crystal::get_table_boundary(); #endif else if(!quotient) s = expansion.get_descendants(rad).get_str(100); if(s == "") { msgid++; goto retry; } addMessage(XLAT("\"Our Table seats %1 Knights!\"", s)); } else if(msgid == 9 && sizes_known() && !tactic::on) { string s = ""; if(0); #if CAP_CRYSTAL else if(cryst) s = crystal::get_table_volume(); #endif else if(!quotient) s = expansion.get_descendants(rad-1, expansion.diskid).get_str(100); if(s == "") { msgid++; goto retry; } addMessage(XLAT("\"There are %1 floor tiles inside our Table!\"", s)); } else if(msgid == 10 && !items[itPirate] && !items[itWhirlpool] && !peace::on && in_full_game()) { addMessage(XLAT("\"Have you tried to take a boat and go into the Ocean? Try it!\"")); } else if(msgid == 11 && !princess::saved && in_full_game()) { addMessage(XLAT("\"When I visited the Palace, a mouse wanted me to go somewhere.\"")); } else if(msgid == 12 && !princess::saved && in_full_game()) { addMessage(XLAT("\"I wonder what was there...\"")); } else if(msgid == 13 && !peace::on && in_full_game()) { addMessage(XLAT("\"Be careful in the Rose Garden! It is beautiful, but very dangerous!\"")); } else if(msgid == 14) { addMessage(XLAT("\"There is no royal road to geometry.\"")); } else if(msgid == 15) { addMessage(XLAT("\"There is no branch of mathematics, however abstract, ")); addMessage(XLAT("which may not some day be applied to phenomena of the real world.\"")); } else if(msgid == 16) { addMessage(XLAT("\"It is not possession but the act of getting there, ")); addMessage(XLAT("which grants the greatest enjoyment.\"")); } else if(msgid == 17) { addMessage(XLAT("\"We live in a beautiful and orderly world, ")); addMessage(XLAT("and not in a chaos without norms.\"")); } else if(msgid == 25) { addMessage(XLAT("\"Thank you very much for talking, and have a great rest of your day!\"")); } else { msgid++; goto retry; } msgid++; } EX } EX namespace mine { EX bool uncoverMines(cell *c, int lev, int dist, bool just_checking) { bool b = false; if(c->wall == waMineMine && just_checking) return true; if(c->wall == waMineUnknown) { if(just_checking) return true; else { changes.ccell(c); c->wall = waMineOpen; b = true; } } bool minesNearby = false; bool nominesNearby = false; bool mineopens = false; auto adj = adj_minefield_cells(c); for(cell *c2: adj) { if(c2->wall == waMineMine) minesNearby = true; if(c2->wall == waMineOpen) mineopens = true; if(c2->wall == waMineUnknown && !c2->item) nominesNearby = true; } if(lev && (nominesNearby || mineopens) && !minesNearby) for(cell *c2: adj) if(c2->wall == waMineUnknown || c2->wall == waMineOpen) { b |= uncoverMines(c2, lev-1, dist+1, just_checking); if(b && just_checking) return true; } if(minesNearby && !nominesNearby && dist == 0) { for(cell *c2: adj) if(c2->wall == waMineMine && c2->land == laMinefield) changes.ccell(c2), c2->landparam |= 1; } return b; } EX bool mightBeMine(cell *c) { return c->wall == waMineUnknown || c->wall == waMineMine; } EX hookset *hooks_mark; EX void performMarkCommand(cell *c) { if(!c) return; if(callhandlers(false, hooks_mark, c)) return; if(c->land == laCA && c->wall == waNone) c->wall = waFloorA; else if(c->land == laCA && c->wall == waFloorA) c->wall = waNone; if(c->land != laMinefield) return; if(c->item) return; if(!mightBeMine(c)) return; bool adj = false; forCellEx(c2, c) if(c2->wall == waMineOpen) adj = true; if(adj) c->landparam ^= 1; } EX bool marked_mine(cell *c) { if(!mightBeMine(c)) return false; if(c->item) return false; if(c->land != laMinefield) return true; return c->landparam & 1; } EX bool marked_safe(cell *c) { if(!mightBeMine(c)) return false; if(c->item) return true; if(c->land != laMinefield) return false; return c->landparam & 2; } EX bool safe() { return items[itOrbAether]; } EX void uncover_full(cell *c2) { int mineradius = bounded ? 3 : (items[itBombEgg] < 1 && !tactic::on) ? 0 : items[itBombEgg] < 20 ? 1 : items[itBombEgg] < 30 ? 2 : 3; bool nomine = !normal_gravity_at(c2); if(!nomine && uncoverMines(c2, mineradius, 0, true) && markOrb(itOrbAether)) nomine = true; if(!nomine) { uncoverMines(c2, mineradius, 0, false); mayExplodeMine(c2, moPlayer); } } EX void auto_teleport_charges() { if(specialland == laMinefield && firstland == laMinefield && bounded) items[itOrbTeleport] = isFire(cwt.at->wall) ? 0 : 1; } EX } EX namespace terracotta { #if HDR // predictable or not static constexpr bool randterra = false; #endif EX void check(cell *c) { if(c->wall == waTerraWarrior && !c->monst && !racing::on) { bool live = false; if(randterra) { c->landparam++; if((c->landparam == 3 && hrand(3) == 0) || (c->landparam == 4 && hrand(2) == 0) || c->landparam == 5) live = true; } else { c->landparam--; live = !c->landparam; } if(live) c->monst = moTerraWarrior, c->hitpoints = 7, c->wall = waNone; } } EX void check_around(cell *c) { forCellEx(c2, c) check(c2); } EX void check() { for(int i=0; icpdist < c->cpdist) mark(c2, cl); } EX int distance; EX bool ambushed; EX void check_state() { if(havewhat & HF_HUNTER) { manual_celllister cl; for(cell *c: dcal) { if(c->monst == moHunterDog) { if(c->cpdist > distance) distance = c->cpdist; mark(c, cl); } if(c->monst == moHunterGuard && c->cpdist <= 4) mark(c, cl); } if(items[itHunting] > 5 && items[itHunting] <= 22) { int q = 0; for(int i=0; icpdist > 3) break; if(c2->monst && !isFriendly(c2) && !slowMover(c2) && !isMultitile(c2)) restricted = true; } int qty = items[itHunting]; if(fixed_size) return fixed_size; switch(what) { case itCompass: return 0; case itHunting: return min(min(qty, max(33-qty, 6)), 15); case itOrbSide3: return restricted ? 10 : 20; case itOrbFreedom: return restricted ? 10 : 60; case itOrbImpact: case itOrbPlague: return 10; case itOrbThorns: case itOrb37: case itOrbChaos: return 20; case itOrbLava: return 20; case itOrbBeauty: return 35; case itOrbShell: return 35; case itOrbPsi: // return 40; -> no benefits return 20; case itOrbDash: case itOrbFrog: return 40; case itOrbAir: case itOrbDragon: return 50; case itOrbStunning: // return restricted ? 50 : 60; -> no benefits return 30; case itOrbBull: case itOrbSpeed: case itOrbShield: return 60; case itOrbInvis: return 80; case itOrbTeleport: return 300; case itGreenStone: case itOrbSafety: case itOrbYendor: return 0; case itKey: return 16; case itWarning: return qty; default: return restricted ? 6 : 10; break; // Flash can survive about 70, but this gives no benefits } } EX void ambush(cell *c, int dogs) { LATE ( ambush(c, dogs); ) int maxdist = gamerange(); celllister cl(c, maxdist, 1000000, NULL); cell *c0 = c; int d = 0; int dogs0 = 0; for(cell *cx: cl.lst) { int dh = cl.getdist(cx); if(dh <= 2 && cx->monst == moHunterGuard) cx->monst = moHunterDog, dogs0++; if(dh > d) c0 = cx, d = dh; } if(sphere) { for(int i = cl.lst.size()-1; i>0 && dogs; i--) if(!isPlayerOn(cl.lst[i]) && !cl.lst[i]->monst) cl.lst[i]->monst = moHunterDog, dogs--; } vector around; cell *clast = NULL; cell *ccur = c0; int v = valence(); if(v > 4) { for(cell *c: cl.lst) if(cl.getdist(c) == d) around.push_back(c); hrandom_shuffle(&around[0], isize(around)); } else { for(int tries=0; tries<10000; tries++) { cell *c2 = NULL; if(v == 3) { forCellEx(c1, ccur) if(c1 != clast && cl.listed(c1) && cl.getdist(c1) == d) c2 = c1; } if(v == 4) { for(int i=0; itype; i++) { cell *c1 = (cellwalker(ccur, i) + wstep + 1).peek(); if(!c1) continue; if(c1 != clast && cl.listed(c1) && cl.getdist(c1) == d) c2 = c1; } } if(!c2) break; if(c2->land == laHunting && c2->wall == waNone && c2->monst == moNone) around.push_back(c2); clast = ccur; ccur = c2; if(c2 == c0) break; } } int N = isize(around); int gaps = dogs; int result = dogs0; if(N) { ambushed = true; int shift = hrand(N); dogs = min(dogs, N); gaps = min(gaps, N); for(int i=0; imonst = moHunterDog; nextdog->stuntime = 1; drawFlash(nextdog); } result += dogs; } if(result) addMessage(XLAT("You are ambushed!")); } EX } } #endif