diff --git a/scripts/api/all.lua b/scripts/api/all.lua index a50b00b875..e6e538480e 100644 --- a/scripts/api/all.lua +++ b/scripts/api/all.lua @@ -1,3 +1,4 @@ +require("api/sector.lua") require("api/modelData.lua") require("api/shipTemplate.lua") require("api/entity/spaceobject.lua") diff --git a/scripts/api/entity/spaceobject.lua b/scripts/api/entity/spaceobject.lua index 3be3208d81..5a8e6470a0 100644 --- a/scripts/api/entity/spaceobject.lua +++ b/scripts/api/entity/spaceobject.lua @@ -288,7 +288,7 @@ end --- Example: entity:getSectorName() function Entity:getSectorName() local x, y = self:getPosition() - return getSectorName(x, y) + return Sector.getSectorName(x, y) end --- Deals a specific amount of a specific type of damage to this entity. --- Requires a numeric value for the damage amount, and accepts an optional DamageInfo type. diff --git a/scripts/api/sector.lua b/scripts/api/sector.lua new file mode 100644 index 0000000000..bd9d778420 --- /dev/null +++ b/scripts/api/sector.lua @@ -0,0 +1,117 @@ +-- Sector naming conventions + +-- The game map is divided into 20U (20,000) square sectors. +-- By default, sectors are named with a letter (Y axis) and a number (X axis) with the origin coordinates 0,0 at the northwest corner of sector F5. +-- Access sector names and coordinates using global functions Sector.getSectorName(x, y) and Sector.sectorToXY(sector_name), or their legacy wrappers without the Sector namespacing. +-- +-- Scenarios can override the naming scheme by assigning to the Sector-namespaced functions. +-- +-- Override examples: +-- +-- - Name sectors numerically (i.e. "2, 4" for two right and four down from origin) +-- +-- Sector.getSectorName = function(x, y) +-- return string.format("%d,%d", math.floor(x / 20000), math.floor(y / 20000)) +-- end +-- Sector.sectorToXY = function(name) +-- local x, y = name:match("(-?%d+),(-?%d+)") +-- if not x then return 0, 0, false end +-- return tonumber(x) * 20000, tonumber(y) * 20000, true +-- end + +Sector = {} +local SECTOR_SIZE = 20000 + +-- Truncate float division +local function truncDiv(a, b) + if a >= 0 then + return math.floor(a / b) + else + return math.ceil(a / b) + end +end + +local function truncMod(a, b) + return a - truncDiv(a, b) * b +end + +--- string Sector.getSectorName(float x, float y) +--- Given x/y coordinates, return the name of the sector that contains those coordinates as a single string. +--- This function and Sector.sectorToXY() define the naming scheme for sectors. To change how sectors are named in a scenario, override these functions to translate coordinate values. +--- Sectors are always rendered in EmptyEpsilon radar views as 20U in size and square in shape. Overriding this function changes only how the sector grid labels are named. +--- Example: +--- Sector.getSectorName(0, 0) -- returns "F5" by default +function Sector.getSectorName(x, y) + local sector_x = math.floor(x / SECTOR_SIZE) + 5 + local sector_y = math.floor(y / SECTOR_SIZE) + 5 + local y_str + local x_str + + if sector_y >= 0 then + if sector_y < 26 then + y_str = string.char(string.byte('A') + sector_y) + else + y_str = string.char(string.byte('A') - 1 + math.floor(sector_y / 26)) .. + string.char(string.byte('A') + (sector_y % 26)) + end + else + y_str = string.char(string.byte('z') + truncDiv(sector_y + 1, 26)) + if truncMod(sector_y, 26) == 0 then + y_str = y_str .. "a" + else + y_str = y_str .. string.char(string.byte('z') + 1 + truncMod(sector_y, 26)) + end + end + + x_str = tostring(sector_x) + return y_str .. x_str +end + +--- float x, float y, bool is_valid Sector.sectorToXY(string sector_name) +--- Given a sector name, return the x/y coordinates for the sector's northwest (top-left) corner point. +--- This also returns a third Boolean value that returns true if the given sector name was valid, or false if not. Check the input's returned validity before applying the returned coordinate values. +--- This function and Sector.getSectorName() define the naming scheme for sectors. To change how sectors are named in a scenario, override these functions to translate coordinate values. +--- Sectors are always rendered in EmptyEpsilon radar views as 20U in size and square in shape. Overriding this function changes only how coordinates translate to labels. +--- Example: +--- Sector.sectorToXY("F5") -- returns 0, 0, true +function Sector.sectorToXY(sector_name) + if #sector_name < 2 then + return 0, 0, false + end + + local x, y + local intpart + + local a1 = string.sub(sector_name, 1, 1) + local a2 = string.sub(sector_name, 2, 2) + + if string.byte(a1) >= string.byte('A') and string.byte(a2) >= string.byte('A') then + intpart = tonumber(string.sub(sector_name, 3)) + if not intpart then + return 0, 0, false + end + if string.byte(a1) > string.byte('a') then + y = ((string.byte('z') - string.byte(a1)) * 26 + (string.byte('z') - string.byte(a2) + 6)) * -SECTOR_SIZE + else + y = ((string.byte(a1) - string.byte('A')) * 26 + (string.byte(a2) - string.byte('A') + 21)) * SECTOR_SIZE + end + else + local alpha = string.upper(a1) + intpart = tonumber(string.sub(sector_name, 2)) + if not intpart then + return 0, 0, false + end + y = (string.byte(alpha) - string.byte('F')) * SECTOR_SIZE + end + + x = (intpart - 5) * SECTOR_SIZE + return x, y, true +end + +function getSectorName(x, y) + return Sector.getSectorName(x, y) +end + +function sectorToXY(sector_name) + return Sector.sectorToXY(sector_name) +end diff --git a/src/gameGlobalInfo.cpp b/src/gameGlobalInfo.cpp index cdf517710f..087d550bfe 100644 --- a/src/gameGlobalInfo.cpp +++ b/src/gameGlobalInfo.cpp @@ -416,18 +416,21 @@ string GameGlobalInfo::getMissionTime() { string getSectorName(glm::vec2 position) { - constexpr float sector_size = 20000; - int sector_x = floorf(position.x / sector_size) + 5; - int sector_y = floorf(position.y / sector_size) + 5; - string y; - string x; - if (sector_y >= 0) - if (sector_y < 26) - y = string(char('A' + (sector_y))); - else - y = string(char('A' - 1 + (sector_y / 26))) + string(char('A' + (sector_y % 26))); - else - y = string(char('z' + ((sector_y + 1) / 26))) + ((sector_y % 26) == 0 ? "a" : string(char('z' + 1 + (sector_y % 26)))); - x = string(sector_x); - return y + x; + if (gameGlobalInfo) + { + if (gameGlobalInfo->main_scenario_script) + { + auto res = gameGlobalInfo->main_scenario_script->call("getSectorName", position.x, position.y); + if (res.isOk()) + return res.value(); + } + if (gameGlobalInfo->script_environment_base) + { + auto res = gameGlobalInfo->script_environment_base->call("getSectorName", position.x, position.y); + if (res.isOk()) + return res.value(); + } + LOG(Warning, "Failed to call Lua getSectorName"); + } + return "??"; } diff --git a/src/gameGlobalInfo.h b/src/gameGlobalInfo.h index 62b02343f8..7c98c87b27 100644 --- a/src/gameGlobalInfo.h +++ b/src/gameGlobalInfo.h @@ -132,4 +132,3 @@ class GameGlobalInfo : public MultiplayerObject, public Updatable }; string getSectorName(glm::vec2 position); -glm::vec2 sectorToXY(string sectorName); diff --git a/src/script.cpp b/src/script.cpp index 13f587c1ac..0c38047334 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -225,11 +225,6 @@ static void luaVictory(string faction) engine->setGameSpeed(0.0); } -static string luaGetSectorName(float x, float y) -{ - return getSectorName({x, y}); -} - static string luaGetScenarioSetting(string key) { if (gameGlobalInfo->scenario_settings.find(key) != gameGlobalInfo->scenario_settings.end()) @@ -328,60 +323,6 @@ static int luaCreateAdditionalScript(lua_State* L) return 1; } -static int luaSectorToXY(lua_State* L) -{ - string sector = luaL_checkstring(L, 1); - constexpr float sector_size = 20000; - int x, y, intpart; - - if(sector.length() < 2){ - lua_pushnumber(L, 0); - lua_pushnumber(L, 0); - lua_pushboolean(L, false); - return 3; - } - - // Y axis is complicated - if(sector[0] >= char('A') && sector[1] >= char('A')) { - // Case with two letters - char a1 = sector[0]; - char a2 = sector[1]; - try{ - intpart = stoi(sector.substr(2)); - } catch(const std::exception& e) { - lua_pushnumber(L, 0); - lua_pushnumber(L, 0); - lua_pushboolean(L, false); - return 3; - } - if(a1 > char('a')){ - // Case with two lowercase letters (zz10) counting down towards the North - y = (((char('z') - a1) * 26) + (char('z') - a2 + 6)) * -sector_size; // 6 is the offset from F5 to zz5 - }else{ - // Case with two uppercase letters (AB20) counting up towards the South - y = (((a1 - char('A')) * 26) + (a2 - char('A') + 21)) * sector_size; // 21 is the offset from F5 to AA5 - } - }else{ - //Case with just one letter (A9/a9 - these are the same sector, as case only matters in the two-letter sectors) - char alphaPart = toupper(sector[0]); - try{ - intpart = stoi(sector.substr(1)); - }catch(const std::exception& e){ - lua_pushnumber(L, 0); - lua_pushnumber(L, 0); - lua_pushboolean(L, false); - return 3; - } - y = (alphaPart - char('F')) * sector_size; - } - // X axis is simple - x = (intpart - 5) * sector_size; // 5 is the numeric component of the F5 origin - lua_pushnumber(L, x); - lua_pushnumber(L, y); - lua_pushboolean(L, true); - return 3; -} - static bool luaIsInsideZone(float x, float y, sp::ecs::Entity e) { auto zone = e.getComponent(); @@ -1238,22 +1179,6 @@ bool setupScriptEnvironment(sp::script::Environment& env) /// (The GM can unpause the game, but the scenario with its update function is destroyed.) /// Example: victory("Exuari") -- ends the scenario, Exuari win env.setGlobal("victory", &luaVictory); - /// string getSectorName(float x, float y) - /// Returns the name of the sector containing the given x/y coordinates. - /// Coordinates 0,0 are the top-left ("northwest") point of sector F5. - /// See also SpaceObject:getSectorName(). - /// Example: getSectorName(20000,-40000) -- returns "D6" - env.setGlobal("getSectorName", &luaGetSectorName); - /// glm::vec2 sectorToXY(string sector_name) - /// Returns the top-left ("northwest") x/y coordinates for the given sector mame. - /// If the sector name is invalid, this returns coordinates 0, 0. This function also returns a third optional Boolean value that indicates whether the sector name was valid. - /// Examples: - /// x, y = sectorToXY("F5") -- x = 0, y = 0 - /// x, y = sectorToXY("A0") -- x = -100000, y = -100000 - /// x, y = sectorToXY("zz-23") -- x = -560000, y = -120000 - /// x, y, valid = sectorToXY("BA12") -- x = 140000, y = 940000, valid = true - /// x, y, valid = sectorToXY("FOOBAR9000") -- x = 0, y = 0, valid = false - env.setGlobal("sectorToXY", &luaSectorToXY); /// bool isInsideZone(x, y, zone_entity) /// Checks whether the given x/y coordinates are within the specified zone. /// Example: