From cc986a2f505d43f7c3082a56dfaec687dac521d1 Mon Sep 17 00:00:00 2001 From: Ihnatus Date: Tue, 13 Sep 2022 22:02:50 +0300 Subject: [PATCH] Lua: API for advanced research checking See OSDN#45068 Signed-off-by: Ihnatus --- common/scriptcore/api_game_methods.c | 134 +++++++++++++++++++++ common/scriptcore/api_game_methods.h | 9 ++ common/scriptcore/api_game_specenum.c | 5 + common/scriptcore/api_specenum.h | 30 +++++ common/scriptcore/luascript_types.h | 1 + common/scriptcore/tolua_common_z.pkg | 6 +- common/scriptcore/tolua_game.pkg | 63 ++++++++++ server/scripting/api_server_game_methods.c | 57 +++++++++ server/scripting/api_server_game_methods.h | 3 + server/scripting/tolua_server.pkg | 8 ++ 10 files changed, 315 insertions(+), 1 deletion(-) diff --git a/common/scriptcore/api_game_methods.c b/common/scriptcore/api_game_methods.c index 08a14a1099..b72da8d3a4 100644 --- a/common/scriptcore/api_game_methods.c +++ b/common/scriptcore/api_game_methods.c @@ -520,6 +520,140 @@ bool api_methods_player_knows_tech(lua_State *L, Player *pplayer, advance_number(ptech)) == TECH_KNOWN; } +/**********************************************************************//** + Return TRUE iff pplayer can research ptech now (but does not know it). + In client, considers known information only. +**************************************************************************/ +bool api_method_player_can_research(lua_State *L, Player *pplayer, + Tech_Type *ptech) +{ + LUASCRIPT_CHECK_STATE(L, FALSE); + LUASCRIPT_CHECK_SELF(L, pplayer, FALSE); + LUASCRIPT_CHECK_ARG_NIL(L, ptech, 3, Tech_Type, FALSE); + + return TECH_PREREQS_KNOWN + == research_invention_state(research_get(pplayer), + advance_number(ptech)); +} + +/**********************************************************************//** + Return current ptech research (or losing) cost for pplayer + pplayer is optional, simplified calculation occurs without it + that does not take into account "Tech_Cost_Factor" effect. + In client, calculates a value summing only known leakage + sources that may be different from the actual one given + by :researching_cost() method. For techs not currently + researchable, often can't calculate an actual value even + on server (bases on current potential leakage sources, and, + for "CivI|II" style, current pplayer's number of known techs or, + if pplayer is absent, minimal number of techs to get to ptech). +**************************************************************************/ +int api_methods_player_tech_cost(lua_State *L, Player *pplayer, + Tech_Type *ptech) +{ + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_ARG_NIL(L, ptech, 3, Tech_Type, 0); + + if (!pplayer && TECH_COST_CIV1CIV2 == game.info.tech_cost_style) { + /* Avoid getting error messages and return at least something */ + return ptech->cost * (double) game.info.sciencebox / 100.0; + } + return + research_total_bulbs_required(pplayer ? research_get(pplayer) : NULL, + advance_index(ptech), TRUE); +} + +/**********************************************************************//** + Returns current research target for a player. + If the player researches a future tech, returns a string with + its translated name. + If the target is unset or unknown, returns nil. + In clients, an unknown value usually is nil but may be different + if an embassy has been lost during the session (see OSDN#45076) +**************************************************************************/ +lua_Object +api_methods_player_researching(lua_State *L, Player *pplayer) +{ + const struct research *presearch; + int rr; + + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_SELF(L, pplayer, 0); + presearch = research_get(pplayer); + LUASCRIPT_CHECK(L, presearch, "player's research not set", 0); + + rr = presearch->researching; + + switch (rr) { + case A_FUTURE: + lua_pushstring(L, research_advance_name_translation(presearch, rr)); + break; + case A_UNSET: + case A_UNKNOWN: + lua_pushnil(L); + break; + default: + /* A regular tech */ + fc_assert(rr >= A_FIRST && rr <= A_LAST); + tolua_pushusertype(L, advance_by_number(rr), "Tech_Type"); + } + + return lua_gettop(L); +} + +/**********************************************************************//** + Number of bulbs on the research stock of pplayer + In clients, unknown value is initialized with 0 but can be different + if an embassy has been lost during the session (see OSDN#45076) +**************************************************************************/ +int api_methods_player_bulbs(lua_State *L, Player *pplayer) +{ + const struct research *presearch; + + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_SELF(L, pplayer, 0); + presearch = research_get(pplayer); + LUASCRIPT_CHECK(L, presearch, "player's research not set", 0); + + return presearch->bulbs_researched; +} + +/**********************************************************************//** + Total cost of pplayer's current research target. + In clients, unknown value is initialized with 0 but can be different + if an embassy has been lost during the session (see OSDN#45076) +**************************************************************************/ +int api_methods_player_research_cost(lua_State *L, Player *pplayer) +{ + const struct research *presearch; + + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_SELF(L, pplayer, 0); + presearch = research_get(pplayer); + LUASCRIPT_CHECK(L, presearch, "player's research not set", 0); + + return is_server() + ? research_total_bulbs_required(presearch, presearch->researching, FALSE) + : presearch->client.researching_cost; +} + +/**********************************************************************//** + Number of future techs known to pplayer + In clients, unknown value is initialized with 0 but can be different + if an embassy has been lost during the session (see OSDN#45076) +**************************************************************************/ +int api_methods_player_future(lua_State *L, Player *pplayer) +{ + const struct research *presearch; + + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_SELF(L, pplayer, 0); + presearch = research_get(pplayer); + LUASCRIPT_CHECK(L, presearch, "player's research not set", 0); + + return presearch->future_tech; +} + /**********************************************************************//** How much culture player has? **************************************************************************/ diff --git a/common/scriptcore/api_game_methods.h b/common/scriptcore/api_game_methods.h index f700dc2caf..755587c81d 100644 --- a/common/scriptcore/api_game_methods.h +++ b/common/scriptcore/api_game_methods.h @@ -87,6 +87,15 @@ int api_methods_player_num_units(lua_State *L, Player *pplayer); int api_methods_player_gold(lua_State *L, Player *pplayer); bool api_methods_player_knows_tech(lua_State *L, Player *pplayer, Tech_Type *ptech); +bool api_method_player_can_research(lua_State *L, Player *pplayer, + Tech_Type *ptech); +int api_methods_player_tech_cost(lua_State *L, Player *pplayer, + Tech_Type *ptech); +lua_Object +api_methods_player_researching(lua_State *L, Player *pplayer); +int api_methods_player_bulbs(lua_State *L, Player *pplayer); +int api_methods_player_research_cost(lua_State *L, Player *pplayer); +int api_methods_player_future(lua_State *L, Player *pplayer); bool api_methods_player_shares_research(lua_State *L, Player *pplayer, Player *aplayer); const char *api_methods_research_rule_name(lua_State *L, Player *pplayer); diff --git a/common/scriptcore/api_game_specenum.c b/common/scriptcore/api_game_specenum.c index e041c83b10..3670401fa4 100644 --- a/common/scriptcore/api_game_specenum.c +++ b/common/scriptcore/api_game_specenum.c @@ -27,6 +27,7 @@ /* common */ #include "events.h" +#include "fc_types.h" /* common/scriptcore */ #include "api_specenum.h" @@ -38,6 +39,8 @@ Define the __index function for each exported specenum type. **************************************************************************/ API_SPECENUM_DEFINE_INDEX(event_type, "E_") +API_SPECENUM_DEFINE_INDEX_REV(tech_cost_style); +API_SPECENUM_DEFINE_INDEX_REV(tech_leakage_style); /**********************************************************************//** Load the specenum modules into Lua state L. @@ -45,6 +48,8 @@ API_SPECENUM_DEFINE_INDEX(event_type, "E_") int api_game_specenum_open(lua_State *L) { API_SPECENUM_CREATE_TABLE(L, event_type, "E"); + API_SPECENUM_CREATE_TABLE_REV(L, tech_cost_style, "TECH_COST"); + API_SPECENUM_CREATE_TABLE_REV(L, tech_leakage_style, "TECH_LEAKAGE"); return 0; } diff --git a/common/scriptcore/api_specenum.h b/common/scriptcore/api_specenum.h index 0012e9553e..eb8a1550c2 100644 --- a/common/scriptcore/api_specenum.h +++ b/common/scriptcore/api_specenum.h @@ -18,6 +18,7 @@ extern "C" { #endif /* __cplusplus */ #define API_SPECENUM_INDEX_NAME(type) api_specenum_##type##_index +#define API_SPECENUM_NAME_NAME(type) api_specenum_##type##_name /**********************************************************************//** Define a the __index (table, key) -> value metamethod @@ -47,11 +48,40 @@ extern "C" { return 1; \ } +/**********************************************************************//** + Define the __index (table, key) -> value metamethod + Return the enum name whose value is supplied. + The fetched value is written back to the lua table, and further accesses + will resolve there instead of this function. + Note that the indices usually go from 0, not 1. +**************************************************************************/ +#define API_SPECENUM_DEFINE_INDEX_REV(type_name) \ + static int (API_SPECENUM_NAME_NAME(type_name))(lua_State *L) \ + { \ + enum type_name _key; \ + const char *_value; \ + luaL_checktype(L, 1, LUA_TTABLE); \ + _key = luaL_checkinteger(L, 2); \ + if (type_name##_is_valid(_key)) { \ + _value = type_name##_name(_key); \ + /* T[_key] = _value */ \ + lua_pushinteger(L, _key); \ + lua_pushstring(L, _value); \ + lua_rawset(L, 1); \ + lua_pushstring(L, _value); \ + } else { \ + lua_pushnil(L); \ + } \ + return 1; \ + } + void api_specenum_create_table(lua_State *L, const char *name, lua_CFunction findex); #define API_SPECENUM_CREATE_TABLE(L, type, name) \ api_specenum_create_table((L), (name), API_SPECENUM_INDEX_NAME(type)) +#define API_SPECENUM_CREATE_TABLE_REV(L, type, name) \ + api_specenum_create_table((L), (name), API_SPECENUM_NAME_NAME(type)) diff --git a/common/scriptcore/luascript_types.h b/common/scriptcore/luascript_types.h index d226e425b0..a87aecfbee 100644 --- a/common/scriptcore/luascript_types.h +++ b/common/scriptcore/luascript_types.h @@ -58,6 +58,7 @@ typedef enum direction8 Direction; typedef struct disaster_type Disaster; typedef struct achievement Achievement; typedef struct action Action; +typedef struct packet_game_info Game_Info; typedef void Nonexistent; diff --git a/common/scriptcore/tolua_common_z.pkg b/common/scriptcore/tolua_common_z.pkg index b1341e30d4..1369f67b30 100644 --- a/common/scriptcore/tolua_common_z.pkg +++ b/common/scriptcore/tolua_common_z.pkg @@ -52,7 +52,8 @@ do "Disaster", "Achievement", "Action", - "Direction" + "Direction", + "Game_Info" } local function id_eq (o1, o2) @@ -124,6 +125,9 @@ tolua = { type=tolua.type, } +-- Hide some unwanted API +game[".set"] = nil + -- Hide all private methods methods_private = nil diff --git a/common/scriptcore/tolua_game.pkg b/common/scriptcore/tolua_game.pkg index 981fb0dcf6..c70a64d6a6 100644 --- a/common/scriptcore/tolua_game.pkg +++ b/common/scriptcore/tolua_game.pkg @@ -92,6 +92,7 @@ struct Unit_Type { struct Tech_Type { const int item_number @ id; + const int cost @ cost_base; }; struct Terrain { @@ -120,8 +121,26 @@ struct Unit_List_Link { struct City_List_Link { }; +/* Declaring all fields as const, readonly */ +struct Game_Info { + const int base_tech_cost; + const int min_tech_cost; + const int tech_leak_pct; + const bool tech_steal_allow_holes; + const bool tech_trade_allow_holes; + const bool tech_trade_loss_allow_holes; + const bool tech_parasite_allow_holes; + const bool tech_loss_allow_holes; + const int sciencebox; + const char tech_cost_style @ tech_cost_style_id; + const char tech_leakage @ tech_leakage_style_id; +}; + /* Module Game */ +$#define game_info_substructure game.info module game { + extern Game_Info game_info_substructure @ info; + int api_methods_game_turn @ current_turn (lua_State *L); @@ -150,6 +169,8 @@ module Player { module properties { int api_methods_player_number @ id (lua_State *L, Player *self); + int api_methods_player_bulbs + @ bulbs (lua_State *L, Player *pplayer); } const char *api_methods_player_controlling_gui @@ -159,12 +180,22 @@ module Player { @ num_cities (lua_State *L, Player *self); int api_methods_player_num_units @ num_units (lua_State *L, Player *self); + int api_methods_player_future + @ num_future_techs (lua_State *L, Player *pplayer); bool api_methods_player_has_wonder @ has_wonder (lua_State *L, Player *self, Building_Type *building); int api_methods_player_gold @ gold (lua_State *L, Player *self); bool api_methods_player_knows_tech @ knows_tech (lua_State *L, Player *self, Tech_Type *ptech); + bool api_method_player_can_research + @ can_research (lua_State *L, Player *pplayer, Tech_Type *ptech); + int api_methods_player_tech_cost + @ tech_cost (lua_State *L, Player *pplayer, Tech_Type *ptech); + lua_Object api_methods_player_researching + @ researching (lua_State *L, Player *pplayer); + int api_methods_player_research_cost + @ researching_cost (lua_State *L, Player *pplayer); bool api_methods_player_shares_research @ shares_research (lua_State *L, Player *self, Player *other); const char *api_methods_research_rule_name @@ -433,6 +464,14 @@ module Tech_Type { @ name_translation (lua_State *L, Tech_Type *self); } +$[ +local ptcost = Player.tech_cost +-- Unpersonalized tech cost method (without "Tech_Cost_Factor" effect) +function Tech_Type:cost() + return ptcost(nil, self) +end +$] + /* Module Terrain. */ module Terrain { const char *api_methods_terrain_rule_name @@ -555,6 +594,13 @@ module find { @ nonexistent (lua_State *L); } +$[ +local game_info = game.info +function find.game_info() + return game_info +end +$] + module E { /* Notify events module is exported by api_specenum */ } @@ -610,7 +656,24 @@ Direction.properties.next_cw = direction.next_cw Direction.properties.opposite = direction.opposite $] +module Game_Info { + +} + $[ +-- Getting enums from game.info as rule names +-- The tables are defined and filled in api_game_specenum.c +local tcs, tls = TECH_COST, TECH_LEAKAGE +-- Remove the tables from _G, likely no more use for them +TECH_COST, TECH_LEAKAGE = nil, nil +Game_Info.properties = { + tech_cost_style = function(self) + return tcs[self.tech_cost_style_id]; + end, + tech_leakage_style = function(self) + return tls[self.tech_leakage_style_id]; + end +} -- *************************************************************************** -- Player and Tile: cities_iterate and units_iterate methods diff --git a/server/scripting/api_server_game_methods.c b/server/scripting/api_server_game_methods.c index 15df33c442..cfc562de68 100644 --- a/server/scripting/api_server_game_methods.c +++ b/server/scripting/api_server_game_methods.c @@ -15,6 +15,9 @@ #include #endif +/* common */ +#include "research.h" + /* common/scriptcore */ #include "luascript.h" @@ -162,3 +165,57 @@ int api_methods_nation_trait_default(lua_State *L, Nation_Type *pnation, return pnation->server.traits[tr].fixed; } + +/**********************************************************************//** + In multiresearch mode, returns bulbs saved for a specific tech + in pplayer's research. + In other modes, returns the additional bulbs the player may get switching + to this tech (negative for penalty) +**************************************************************************/ +int api_methods_player_tech_bulbs(lua_State *L, Player *pplayer, + Tech_Type *tech) +{ + const struct research *presearch; + Tech_type_id tn; + + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_SELF(L, pplayer, 0); + LUASCRIPT_CHECK_ARG_NIL(L, tech, 3, Tech_Type, 0); + tn = advance_number(tech); + presearch = research_get(pplayer); + LUASCRIPT_CHECK(L, presearch, "player's research not set", 0); + + if (game.server.multiresearch) { + return presearch->inventions[tn].bulbs_researched_saved; + } else { + if (presearch->researching_saved == tn) { + return + presearch->bulbs_researching_saved - presearch->bulbs_researched; + } else if (!presearch->got_tech && tn != presearch->researching + && presearch->bulbs_researched > 0) { + return -presearch->bulbs_researched * game.server.techpenalty / 100; + } else { + return 0; + } + } +} + +/**********************************************************************//** + Returns whether pplayer is in "got tech" state and can invest their + remaining rsearched bulbs freely. +**************************************************************************/ +bool api_methods_player_got_tech(lua_State *L, Player *pplayer) +{ + const struct research *presearch; + + LUASCRIPT_CHECK_STATE(L, 0); + LUASCRIPT_CHECK_SELF(L, pplayer, 0); + presearch = research_get(pplayer); + LUASCRIPT_CHECK(L, presearch, "player's research not set", 0); + + if (game.server.multiresearch) { + return presearch->got_tech_multi; + } else { + return presearch->got_tech; + } +} diff --git a/server/scripting/api_server_game_methods.h b/server/scripting/api_server_game_methods.h index 404ec1bc69..c74a0bd61b 100644 --- a/server/scripting/api_server_game_methods.h +++ b/server/scripting/api_server_game_methods.h @@ -37,5 +37,8 @@ int api_methods_nation_trait_max(lua_State *L, Nation_Type *pnation, const char *tname); int api_methods_nation_trait_default(lua_State *L, Nation_Type *pnation, const char *tname); +int api_methods_player_tech_bulbs(lua_State *L, Player *pplayer, + Tech_Type *tech); +bool api_methods_player_got_tech(lua_State *L, Player *pplayer); #endif /* FC__API_SERVER_GAME_METHODS_H */ diff --git a/server/scripting/tolua_server.pkg b/server/scripting/tolua_server.pkg index 219d0883fd..ac62a1a426 100644 --- a/server/scripting/tolua_server.pkg +++ b/server/scripting/tolua_server.pkg @@ -497,6 +497,10 @@ $] /* Additions to common Player module. */ module Player { + module properties { + bool api_methods_player_got_tech + @ got_tech (lua_State *L, Player *pplayer); + } int api_methods_player_trait @ trait (lua_State *L, Player *pplayer, const char *tname); int api_methods_player_trait_base @@ -505,6 +509,8 @@ module Player { @ trait_current_mod (lua_State *L, Player *pplayer, const char *tname); void api_methods_player_lose @ lose (lua_State *L, Player *pplayer, Player *looter = NULL); + int api_methods_player_tech_bulbs + @ bulbs_saved (lua_State *L, Player *pplayer, Tech_Type *tech); } $[ @@ -514,9 +520,11 @@ $] /* server game parameters */ $#define game_server_autoupgrade_veteran_loss (game.server.autoupgrade_veteran_loss) $#define game_server_upgrade_veteran_loss (game.server.upgrade_veteran_loss) +$#define game_server_multiresearch (game.server.multiresearch) module game { extern const int game_server_autoupgrade_veteran_loss @ autoupgrade_veteran_loss; extern const int game_server_upgrade_veteran_loss @ upgrade_veteran_loss; + extern const bool game_server_multiresearch @ multiresearch; } /* Additions to common Nation_Type module. */ -- 2.34.1