// 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" #if 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; if(c->landparam == 0 && ls::single()) c->land = laOcean; 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", "Phoenyx", "tricosahedron", "BillSYZ0317", "rjdimo", "Person", "Strange Yeah", "paradoxica", "godamnsteemsux", "Allalinor", "Spuddnik", "QETC Crimson", "aca", "cannobeens", "Dylgear", "Patashu", "SirLight_XXVII", "Lord Ignus", "Lotho", "hotdogPi", "vincent", "pofoss", "Zekava", "Chimera245", "BreedPineapple", "TaeK", "Aliased", "Vipul", "jkvw3", "english5040", "gregwar69", "marvincast", "Panacea108", "FEDPOL1", "Jenkar", "inakilbss", "*****-mail", "Wheat Wizard", "xiqnyl", "zelda0x181e", "ad-jones", "mtomato", "Fififionek", "J Pystynen", "krstefan11", "green orange", "ZombieGirl1616", "z.chlebicki", "9427", "Lokki", "gabbalis", "The Horrendous Space Kablooie", "zeno", "ETsc2", "Fulgur14", "Roger", "uqblf", "Grep", "Abigail", "Cyberfox VII", "ballom", "CtrlAltDestroy", "RushSecond", "ozir", "Vee Nought", "archmageomega", "Wroclaw", "lunaduskjr", "LucLucLuc", "loper", "RedsToad", "Sharklilly", "Dasypus Daimon", "Mateusz", "joshua", "guysige", "MrSprucetree", "Atia", "nerdyjoe", "florrat", "bderoo121", "Sprite Guard", "fischpferd", "Fluffiest Princess", "no stop", "Kieroshark", "elgan65536", "T-Jski", "martinus de monte", "Inthar", "mj.titze", "thelast19digitsofpi", "abacussssss", "ann_dec", "juhdanad", "lllllllllwith10ls", "rsstein", "The Emerald Derp Leader", "NattieGilgamesh", "chocokels", "oren", "y", "Nathanbananas", "juliuskwan", "wojtekor9", "CoderBro", "RidiculousPyro64", "pringle", "Spicy", "Cheetahs", "ShiningKatana", "xy2", "starchewer", "2jjy", "Nibaw", "Goon in Chief", "the.goosh222", "Toras", "ooshoo", "TravelDemon", "The Big Extent", "droplet739", "AlliterativeAnchovies", "fones4jenke13", "ekisacik", "j0eymcgrath", "EatChangmyeong", "Strichcoder", "jwhirpl", "craus", "akei_arkay", "lilypad", "Ichigo Jam", "supernewton", "Neapolitan Sixth", "Nemo", "Westville", "Huja", "lapinozz", "Just A Witch", "Borador", "Particles", "dewhi100", "nat.mayer", "ddtizm", "Tism Jones", "amagikuser", "vkozulya", "gassa", "Factitious", "wonderfullizardofoz", "woofmao", "CandorVelexion", "Toricon", "Vectis99", "RobotNerd277", "jerrypoons", "MagmaMcFry", "unczane", "glass", "Wegener", "JeLomun", "kip", "Warmonger", "Fooruman", "Zyalin", "Prezombie", "ashley89", "bjobae", "MFErtre", "Roaringdragon2", "howilovepi", "Yulgash", "Sir Endipitous", "Roshlev", "BTernaryTau", "HiGuy", "coper", "Tirear", "qoala _", "Tyzone", "Tiegon", "Airin", "Metroid26", "Sklorg", "Fumblestealth", "Toph", "Tzaphqiel", "jruderman", "ray", "Deathroll", "Sinquetica", "mootmoot", "Noobinator", "freeofme", "Helyea", "Snakebird Priestess", "brisingre", "Khashishi", "Shiny", "kabado", "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 int victory_time; EX void count_status() { bool last = kills[moBomberbird]; kills[moBomberbird] = 0; kills[moTameBomberbird] = 0; for(cell *c: currentmap->allcells()) if(c->wall == waMineUnknown) kills[moBomberbird]++; for(cell *c: currentmap->allcells()) if(among(c->wall, waMineMine, waMineUnknown) && mine::marked_mine(c)) kills[moTameBomberbird]++; if(last && !kills[moBomberbird]) { mine::victory_time = getgametime(); pushScreen(showMission); } } EX bool in_minesweeper() { return bounded && specialland == laMinefield; } 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 = ca::wlive; ca::list_adj(c); } else if(c->land == laCA && c->wall == ca::wlive) { c->wall = waNone; ca::list_adj(c); } 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(in_minesweeper()) { mine::count_status(); 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(cell *pc: player_positions()) forCellEx(c, pc) { if(shmup::on) { forCellEx(c2, c) check(c2); } else check(c); } } EX } EX namespace ambush { EX void mark(cell *c, manual_celllister& cl) { if(!cl.add(c)) return; forCellEx(c2, c) if(c2->cpdist < 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(cell *pc: player_positions()) forCellEx(c2, pc) if(cl.listed(c2)) q++; if(q == 1) havewhat |= HF_FAILED_AMBUSH; if(q == 2) { for(cell *pc: player_positions()) forCellEx(c2, pc) if(cl.listed(c2)) forCellEx(c3, pc) if(c3 != c2 && isNeighbor(c2,c3)) if(cl.listed(c3)) havewhat |= HF_FAILED_AMBUSH; } if(havewhat & HF_FAILED_AMBUSH && ambushed) { addMessage(XLAT("The Hunting Dogs give up.")); ambushed = false; } } } } EX int fixed_size; EX int size(cell *c, eItem what) { bool restricted = false; for(cell *c2: dcal) { if(c2->cpdist > 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 } EX namespace dice { struct die_structure { vector> sides; vector> spins; vector hardness; int faces; int order; die_structure(int ord, const vector>& v) { sides = v; spins = sides; faces = isize(sides); order = ord; for(int i=0; ifaces-1) return 1; if(val == 0) return -1; return 0; } EX map data; EX void generate_random(cell *c) { auto& dd = data[c]; dd.which = pick(&d4, &d8, &d20); dd.val = hrand(dd.which->faces); dd.dir = 1 + hrand(3) * 2; } EX void generate_specific(cell *c, die_structure *ds, int min_hardness, int max_hardness) { auto& dd = data[c]; dd.which = ds; dd.dir = 1 + hrand(3) * 2; vector sides; for(int i=0; ifaces; i++) if(ds->hardness[i] >= min_hardness && ds->hardness[i] <= max_hardness) sides.push_back(i); dd.val = sides[hrand(isize(sides))]; } EX void generate_full(cell *c, int hard) { int pct = hrand(100); int pct2 = hrand(4000); if(pct < 1) { c->wall = waBlandDie; generate_specific(c, &d4, 0, 99); } else if(pct < 3) { c->wall = waBlandDie; generate_specific(c, &d8, 0, 1); } else if(pct < 5) { c->wall = waBlandDie; generate_specific(c, &d20, 0, 1); } else if(pct < 9) { c->wall = waRichDie; generate_specific(c, &d20, 4, 5); } else if(pct < 10) { c->wall = waRichDie; generate_specific(c, &d8, 2, 3); } else if(pct2 < 1) { c->monst = moAnimatedDie; generate_specific(c, &d4, 0, 99); } else if(pct2 < 40) { c->monst = moAnimatedDie; generate_specific(c, &d8, 0, 99); } else if(pct2 < 40 + hard) { c->monst = moAnimatedDie; generate_specific(c, &d20, 0, 99); } } EX die_data roll_effect(movei mi, die_data dd) { auto &cto = mi.t; auto &th = mi.s; int rdir = mi.dir_force(); int t = th->type; int val = dd.val; int dir = dd.dir; auto& dw = dd.which; int si = isize(dw->sides[val]); if(t % si) { println(hlog, "error: bad roll\n"); return dd; } int sideid = (rdir - dir) * si / t; if(sideid < 0) sideid += si; int val1 = dw->sides[val][sideid]; int si1 = isize(dw->sides[val1]); int sideid1 = dw->spins[val][sideid]; int t1 = cto->type; if(t1 % si1) { println(hlog, "error: bad roll target\n"); return dd; } int rdir1 = mi.rev_dir_force(); int dir1 = rdir1 - sideid1 * t1 / si1; dir1 = gmod(dir1, t1); dd.val = val1; dd.dir = dir1; return dd; } EX void roll(movei mi) { auto &cto = mi.t; auto &th = mi.s; changes.map_value(data, cto); changes.map_value(data, th); data[cto] = roll_effect(mi, data[th]); data.erase(th); } EX void draw_die(cell *c, const shiftmatrix& V, ld scale, color_t color) { if(!data.count(c)) { queuepoly(V, cgi.shAsymmetric, 0xFF0000FF); return; } eGeometry orig = geometry; bool fpp = GDIM == 3; auto& dd = data[c]; int val = dd.val; int dir = dd.dir; auto& dw = dd.which; auto& side = dw->sides[val]; int si = isize(side); if(c == lmouseover_distant) { set visited; struct celldata_t { cell* c; die_data dd; shiftmatrix V; }; vector data; auto visit = [&] (cell *c, die_data dd, shiftmatrix V) { if(visited.count(c)) return; visited.insert(c); data.emplace_back(celldata_t{c, dd, V}); }; visit(c, dd, V); for(int i=0; iadj(dat.c, id); gridline(dat.V, C0, V2, C0, 0xFF800080, 0); visit(c2, re, V2); } } } shiftmatrix V1 = V * ddspin(c, dir) * spin(M_PI); // loop: vector face_drawn(dd.which->faces, false); vector > facequeue; auto add_to_queue = [&] (const transmatrix& T, int d) { if(face_drawn[d]) return; face_drawn[d] = true; facequeue.emplace_back(T, d); }; transmatrix S = Id; ld outradius, inradius; if(1) { dynamicval g(geometry, gSphere); ld alpha = 360 * degree / dw->order; inradius = edge_of_triangle_with_angles(alpha, 60*degree, 60*degree); outradius = edge_of_triangle_with_angles(60*degree, alpha, 60*degree); } hyperpoint shift = inverse_shift(V1, tC0(die_target)); hyperpoint log_shift = inverse_exp(shiftless(shift)) * (inradius / cgi.hexhexdist); if(1) { dynamicval g(geometry, gSphere); hyperpoint de = direct_exp(log_shift); S = rgpushxto0(de); if(GDIM == 3) { for(int i=0; i<4; i++) swap(S[i][2], S[i][3]); for(int i=0; i<4; i++) swap(S[2][i], S[3][i]); } for(int i=0; i<4; i++) S[i][1] *= -1; } add_to_queue(S, val); ld dieradius = scale / 2; if(dw->faces == 20) dieradius /= 1.3; if(dw->faces == 8) dieradius /= 1.15; ld base_to_base; if(1) { dynamicval g(geometry, gSpace534); hyperpoint h = cspin(2, 0, M_PI-outradius) * zpush0(-dieradius); base_to_base = binsearch(-5, 5, [h] (ld d) { return (zpush(d) * h)[2] >= sin_auto(vid.depth); }); } vector > ordering; for(int i=0; ifaces; i++) { transmatrix T = facequeue[i].first; int ws = facequeue[i].second; for(int d=0; d g(geometry, gSpace534); add_to_queue(T * cspin(0, 1, 2*M_PI*d/si) * cspin(2, 0, inradius) * cspin(0, 1, M_PI-2*M_PI*dw->spins[ws][d]/si), dw->sides[ws][d]); } if(1) { dynamicval g(geometry, gSpace534); hyperpoint h = zpush(base_to_base) * T * zpush0(dieradius); ld z = fpp ? hdist0(h) : asin_auto(h[2]); ordering.emplace_back(-z, i); } } sort(ordering.begin(), ordering.end()); for(auto o: ordering) { int i = o.second; transmatrix T = facequeue[i].first; transmatrix face; hyperpoint sum = zpush(base_to_base) * C0 * -3; auto sphere_to_space = [&] (hyperpoint h) { if(fpp) return h; ld z = asin_auto(h[2]); h /= cos_auto(z); h[2] = h[3]; h[3] = 0; dynamicval g(geometry, orig); return zshift(h, geom3::scale_at_lev(z)); }; for(int d=0; d<=si; d++) { hyperpoint h, hs; if(1) { dynamicval g(geometry, gSpace534); h = zpush(base_to_base) * T * cspin(0, 1, 2*M_PI*(d+.5)/si) * cspin(2, 0, outradius) * zpush0(dieradius); set_column(face, d, h); sum += h; hs = sphere_to_space(h); } curvepoint(hs); } set_column(face, 3, sum); queuecurve(V1, 0xFFFFFFFF, color & 0xFFFFFF9F, PPR::WALL); pointfunction pf = [&] (ld x, ld y) { hyperpoint hs; dynamicval g(geometry, gSpace534); return sphere_to_space(normalize(face * hyperpoint(1/3. -y/2 -x, 1/3. + y, 1/3. -y/2 +x, 1e-2))); }; int fid = dw->faces - facequeue[i].second; if(dw->faces == 4) fid = facequeue[i].second + 1; string s; if(fid == 6) s = "6."; else if(fid == 9) s = "9."; else s = its(fid); write_in_space(V1, max_glfont_size, dw->faces < 10 ? -1.2 : -.75, s, 0xFFFFFFFF, 0, 8, PPR::WALL, pf); } } EX } } #endif