From 9dbbd113a78af66702d089e53e44db515ef17e38 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Fri, 31 Jul 2026 11:26:30 +0200 Subject: [PATCH] lineofsight:: first commit --- celldrawer.cpp | 33 ++++++++++--------- config.cpp | 8 +++++ environment.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ graph.cpp | 6 +++- help.cpp | 9 +++++- hud.cpp | 2 ++ mapeditor.cpp | 2 +- menus.cpp | 8 ++++- monstermove.cpp | 2 ++ orbs.cpp | 5 +++ scores.cpp | 6 ++++ system.cpp | 9 ++++-- 12 files changed, 153 insertions(+), 21 deletions(-) diff --git a/celldrawer.cpp b/celldrawer.cpp index d750ecef..2ca4ac49 100644 --- a/celldrawer.cpp +++ b/celldrawer.cpp @@ -2904,8 +2904,10 @@ void celldrawer::draw() { checkTide(c); - if(1) { - + int seen = in_line_of_sight_for_player(c); + + if(seen) { + if(inmirror(c)) { if(inmirrorcount >= 10) return; cellwalker cw(c); @@ -3045,23 +3047,24 @@ void celldrawer::draw() { queuestrn(V, mapfontscale / 100, s, darkenedby(asciicol, darken), 2); } - draw_grid(); - if(onradar && WDIM == 2 && GDIM == 3) addradar(V, asciichar, darkenedby(asciicol, darken), 0); - if(WDIM == 2 && GDIM == 3) radar_grid(); #endif - - check_rotations(); - - #if CAP_EDIT - if(!inHighQual) mapeditor::drawGhosts(c, V, c->type); - #endif - -#if CAP_MODEL - netgen::buildVertexInfo(c, unshift(V)); -#endif } + + draw_grid(); + + if(WDIM == 2 && GDIM == 3) radar_grid(); + + check_rotations(); + + #if CAP_EDIT + if(!inHighQual) mapeditor::drawGhosts(c, V, c->type); + #endif + + #if CAP_MODEL + netgen::buildVertexInfo(c, unshift(V)); + #endif } void celldrawer::set_towerfloor(const cellfunction& cf) { diff --git a/config.cpp b/config.cpp index 61b82bba..3b168d39 100644 --- a/config.cpp +++ b/config.cpp @@ -1672,6 +1672,14 @@ EX void initConfig() { }, "stereo/high-FOV mode", 'm') ->set_reaction(reset_all_shaders); + param_enum(lineofsight, "mode-lineofsight", los::none) + ->editable({ + {"OFF", "You see everything in range, but also everything in range sees you!"}, + {"geodesic", "Graph geodesic: any sequence of tiles is OK for line-of-sight as long as there are no shortcuts"}, + {"geometric", "Approximations of geometric straight lines."}}, + "line-of-sight mode", 'v') + ->set_reaction(switchLOS_quiet); + param_f(vid.plevel_factor, "plevel_factor", 0.7); param_b(nohud, "no-hud", false); diff --git a/environment.cpp b/environment.cpp index b3224b1d..94df49a9 100644 --- a/environment.cpp +++ b/environment.cpp @@ -238,6 +238,7 @@ EX void computePathdist(eMonster param, bool include_allies IS(true)) { cellwalker cw1 = cw + j; // printf("i=%d cd=%d\n", i, c->move(i)->cpdist); cell *c2 = cw1.peek(); + if(!in_line_of_sight(c2)) continue; flagtype f = P_MONSTER; if(param == moIvyRoot) f |= P_IVY; @@ -546,6 +547,8 @@ EX void bfs() { for(auto& t: tempmonsters) t.first->monst = t.second; buildAirmap(); + + create_los(); } EX void moverefresh(bool turn IS(true)) { @@ -932,4 +935,85 @@ EX bool looped(vector& whirlline) { return false; } +/*-- lineofsight --*/ + +#if HDR +enum class los { none, geodesic, geometric }; +#define PURELOS_LEVEL 10 +#endif + +EX los lineofsight; +/** when did we switch the lineofsight mode */ +EX int lineofsightAt; + +EX map current_fov; // 1 == seen, 2 = see through + +EX bool blocks_sight(cell *c, cell *last) { + if(inmirror(c)) { + cellwalker cw(c, neighborId(c, last)); + auto cw2 = mirror::reflect(cw); + return blocks_sight(cw2.at, cw2.cpeek()); + } + if(c->monst == passive_switch) return true; + return (isWall(c) && !among(c->wall, waFreshGrave, waAncientGrave, waClosedGate, waMirrorWall)) || thruVine(c, last); + } + +EX bool in_line_of_sight(cell *c) { + if(lineofsight == los::none) return true; + return current_fov[c]; + } + +EX bool in_line_of_sight_for_player(cell *c) { + if(in_line_of_sight(c)) return true; + int range = 0; + if(items[itOrbAether]) range = 2; + if(items[itOrbDash] || items[itOrbFrog] || items[itOrbPhasing]) range = 3; + if(items[itOrbInvis]) range = 999999; + return c->cpdist <= range; + } + +EX void create_los() { + current_fov.clear(); + if(lineofsight == los::none) lineofsight = los::geometric; + if(lineofsight == los::geodesic) { + for(auto c: dcal) { + if(c->cpdist == 0) current_fov[c] |= 3; + forCellEx(c1, c) if(c1->cpdist < c->cpdist && (current_fov[c1] & 2)) + current_fov[c] |= blocks_sight(c, c1) ? 1 : 3; + } + } + if(lineofsight == los::geometric) { + + for(cell *cp: player_positions()) current_fov[cp] = 3; + + for(auto c: dcal) for(auto cp: player_positions()) { + hyperpoint h = tC0(currentmap->relative_matrix(c, cp, C0)); + transmatrix T = spintox(h); + cellwalker at = cwt; + + while(true) { + int best_i = -1; + ld best_y = HUGE_VAL; + for(int i=0; itype; i++) { + cellwalker at1 = at + i; + if(at1.cpeek()->cpdist != at.at->cpdist + 1) continue; + transmatrix U = T * currentmap->adj(at.at, at1.spin); + hyperpoint U0 = tC0(U); + hyperpoint T0 = tC0(T); + if(U0[0] < T0[0] + 1e-6) continue; + ld y; + if(GDIM == 3) y = hypot(U0[1], U0[2]); else y = abs(U0[1]) + (U0[1] > 0 ? 1e-6 : 0); + if(y < best_y) { best_y = y; best_i = i; } + } + if(best_i < 0) break; + at = at + best_i; + T = T * currentmap->adj(at.at, at.spin); + at = at + wstep; + if(blocks_sight(at.at, at.cpeek())) { current_fov[at.at] |= 1; break; } + current_fov[at.at] |= 3; + } + } + } + } + } diff --git a/graph.cpp b/graph.cpp index e8d17b15..44ffa13b 100644 --- a/graph.cpp +++ b/graph.cpp @@ -500,7 +500,11 @@ EX color_t stdgridcolor = 0x202020FF; EX int gridcolor(cell *c1, cell *c2) { if(cmode & sm::DRAW && !mapeditor::drawing_tool) return Dark(forecolor); if(!c2) - return 0x202020 >> darken; + return Dark(0x202020); + + if(!in_line_of_sight(c1) || !in_line_of_sight(c2)) + return Dark(0x808080); + int rd1 = rosedist(c1), rd2 = rosedist(c2); if(rd1 != rd2) { int r = rd1+rd2; diff --git a/help.cpp b/help.cpp index a65f77bc..7c24a090 100644 --- a/help.cpp +++ b/help.cpp @@ -939,6 +939,10 @@ EX void describeMouseover() { cell *c = mousing ? mouseover : playermoved ? NULL : centerover; string& out = mouseovers; if(!c || instat || getcstat != '-') { } + else if(!in_line_of_sight_for_player(c)) { + out = XLAT("you cannot see this place"); + help = XLAT("You cannot currently see this tile.\n\nThis also means that your enemies (except ghosts) will not try to reach you through this tile."); + } else if(c->wall != waInvisibleFloor) { out = XLAT1(linf[c->land].name); set_help_to(c->land); @@ -1136,13 +1140,16 @@ EX void describeMouseover() { if(isWarped(c) && !isWarpedType(c->land)) out += ", warped"; + if(!in_line_of_sight(c)) + out += " (magical vision)"; + if(isWarped(c)) { appendHelp(string("\n\n") + XLAT(warpdesc)); if(S7 != 7 || !BITRUNCATED) if(c->item != itOrb37) appendHelp("\n\n" + other_geometry() + forbidden_unmarked()); } - + if(isElectricLand(c) || isElectricLand(cwt.at->land)) { using namespace elec; eCharge ch = getCharge(c); diff --git a/hud.cpp b/hud.cpp index 29b753e1..d84b576c 100644 --- a/hud.cpp +++ b/hud.cpp @@ -515,6 +515,8 @@ EX string mode_description() { if(multi::players > 1) md += " P" + its(multi::players); if(pureHardcore()) md += " hardcore"; else if(hardcore) md += " partial hardcore"; + if(lineofsightAt >= PURELOS_LEVEL) md += "partial lineofsight"; + else if(lineofsight != los::none) md += " lineofsight"; if(daily::on) md += " strange"; if(bow::crossbow_mode()) md += " b/" + bow::bowName[bow::style]; if(land_structure != default_land_structure()) diff --git a/mapeditor.cpp b/mapeditor.cpp index 5140923b..1e016bc7 100644 --- a/mapeditor.cpp +++ b/mapeditor.cpp @@ -1477,7 +1477,7 @@ EX namespace mapstream { if(shmup::on) shmup::init(); timerstart = time(NULL); turncount = 0; lastexplore = 0; - sagephase = 0; hardcoreAt = 0; + sagephase = 0; hardcoreAt = 0; lineofsightAt = 0; timerstopped = false; savecount = 0; savetime = 0; cheater = 1; diff --git a/menus.cpp b/menus.cpp index 91ae4204..bf0554f0 100644 --- a/menus.cpp +++ b/menus.cpp @@ -302,6 +302,10 @@ EX void switchHardcore_quiet() { else { hardcore = true; canmove = true; hardcoreAt = turncount; } } +EX void switchLOS_quiet() { + lineofsightAt = turncount; + } + EX void switchHardcore() { switchHardcore_quiet(); if(hardcore) @@ -841,7 +845,9 @@ EX void showChangeMode() { #if CAP_RACING dialog::addBoolItem(XLAT("racing mode"), racing::on, 'R'); dialog::add_action(racing::configure_race); -#endif +#endif + + add_edit(lineofsight); show_achievement_eligibility(); diff --git a/monstermove.cpp b/monstermove.cpp index c45994b3..e08ed217 100644 --- a/monstermove.cpp +++ b/monstermove.cpp @@ -1215,6 +1215,8 @@ EX void groupmove(eMonster movtype, flagtype mf) { for(int i=0; i dirtable; forCellIdAll(c2,t,c) dirtable.push_back(t); diff --git a/orbs.cpp b/orbs.cpp index b4ad1213..4be2e6e5 100644 --- a/orbs.cpp +++ b/orbs.cpp @@ -1402,6 +1402,11 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { addMessage(XLAT("You cannot target that far away!")); return itNone; } + if(!in_line_of_sight_for_player(c)) { + if(!isWeakCheck(a)) + addMessage(XLAT("You cannot target what you cannot see!")); + return itNone; + } } vector orb_error_messages; diff --git a/scores.cpp b/scores.cpp index 2fdbd420..55841237 100644 --- a/scores.cpp +++ b/scores.cpp @@ -61,6 +61,9 @@ int modediff(score *S) { if(S->box[196] != (int) land_structure) diff += 32; if(S->box[119] != shmup::on) diff += 64; if(pureHardcore() && !isHardcore(S)) diff += 128; + if((S->box[422] < PURELOS_LEVEL) != (lineofsightAt < PURELOS_LEVEL)) diff += 8; + else if(S->box[421] != int(lineofsight)) diff += 8; + if(g != gNormal && S->box[120] != specialland) diff += 256; if(g != geometry) { @@ -84,6 +87,9 @@ string modedesc(score *S) { if(S->box[197] > 1) s += "/P" + its(S->box[197]); if(S->box[306]) s += "/i"; if(isHardcore(S)) s += "/h"; + if(S->box[421] == 1 && S->box[422] < PURELOS_LEVEL) s += "/l1"; + if(S->box[421] == 2 && S->box[422] < PURELOS_LEVEL) s += "/l2"; + if(S->box[422] >= PURELOS_LEVEL) s += "/l?"; return s; } diff --git a/system.cpp b/system.cpp index 1ab49cc2..f177defb 100644 --- a/system.cpp +++ b/system.cpp @@ -419,7 +419,7 @@ EX void initgame() { timerstart = time(NULL); turncount = 0; lastexplore = 0; rosewave = 0; rosephase = 0; tickstart = ticks; noiseuntil = 0; - sagephase = 0; hardcoreAt = 0; + sagephase = 0; hardcoreAt = 0; lineofsightAt = 0; timerstopped = false; savecount = 0; savetime = 0; loadcount = 0; current_loadcount = 0; load_branching = 0; @@ -485,7 +485,7 @@ EX namespace scores { /** \brief the amount of boxes reserved for each hr::score item */ #define MAXBOX 500 /** \brief currently used boxes in hr::score */ -#define POSSCORE 421 +#define POSSCORE 423 /** \brief a struct to keep local score from an earlier game */ struct score { /** \brief version used */ @@ -1062,6 +1062,11 @@ EX void applyBoxes() { setprio(bpWeirdStat); applyBoxNum(current_loadcount, "current load count"); + setprio(bpMode); + applyBoxEnum(lineofsight, "lineofsight"); + setprio(bpMode); + applyBoxNum(lineofsightAt, "@lineofsightAt"); + if(POSSCORE != boxid) printf("ERROR: %d boxes\n", boxid); if(isize(invorb)) { println(hlog, "ERROR: Orbs not taken into account"); exit(1); } }