From cc986a2f505d43f7c3082a56dfaec687dac521d1 Mon Sep 17 00:00:00 2001
From: Ihnatus <ignatus31oct@mail.ru>
Date: Tue, 13 Sep 2022 22:02:50 +0300
Subject: [PATCH] Lua: API for advanced research checking

See OSDN#45068

Signed-off-by: Ihnatus <ignatus31oct@mail.ru>
---
 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 <fc_config.h>
 #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