From a5336d1e7db39ebc8f0c895c0bfbf2d3352577a3 Mon Sep 17 00:00:00 2001
From: Kalle Olavi Niemitalo <kon@iki.fi>
Date: Sun, 30 Oct 2011 14:05:20 +0200
Subject: [PATCH 1/2] Replaced AiList and _ai_scripts_ with AiTypes

Also replaced RegisterAi and DefineAi with DefineAiType,
which now takes a table rather than a set of parameters.
This table is the object that DefineAiType adds to AiTypes.
The keys of the table are constant strings, not numbers.

The purpose is to make the engine more aware of AI initialization
functions, although this commit doesn't yet get that far.
---
 doc/scripts/ai.html             |  110 ++++++++++++++++++++++-----------------
 doc/scripts/index.html          |    6 ++-
 doc/scripts/savegame.html       |   12 +----
 engine/ai/ai.cpp                |   16 +++---
 engine/ai/script_ai.cpp         |   76 +++++++++++++++++----------
 engine/stratagus/script.cpp     |    1 +
 scripts/ai.lua                  |   31 ++---------
 scripts/ais/air_attack.lua      |    8 ++-
 scripts/ais/attackdefense.lua   |    7 ++-
 scripts/ais/blitz.lua           |    9 ++--
 scripts/ais/broke.lua           |    7 ++-
 scripts/ais/default.lua         |    7 ++-
 scripts/ais/just_attack.lua     |    8 ++-
 scripts/ais/naval.lua           |    8 ++-
 scripts/ais/passive.lua         |    7 ++-
 scripts/ais/rush.lua            |    7 ++-
 scripts/ais/spacious.lua        |    7 ++-
 scripts/ais/tankrush.lua        |    7 ++-
 scripts/ais/zombies.lua         |    7 ++-
 scripts/menus/ingame/editor.lua |   27 +++++-----
 20 files changed, 206 insertions(+), 162 deletions(-)

diff --git a/doc/scripts/ai.html b/doc/scripts/ai.html
index ef5de59..6876902 100644
--- a/doc/scripts/ai.html
+++ b/doc/scripts/ai.html
@@ -29,7 +29,8 @@
 <a href="config.html">NEXT</a> 
 <a href="index.html">LUA Index</a>
 <hr>
-<a href="#DefineAi">DefineAi</a>
+<a href="#AiTypes">AiTypes</a>
+<a href="#DefineAiType">DefineAiType</a>
 <a href="#DefineAiHelper">DefineAiHelper</a>
 <a href="#AiCheckForce">AiCheckForce</a>
 <a href="#AiDebug">AiDebug</a>
@@ -77,34 +78,77 @@ The main script is responsible for setting minimums for the forces 0 and 1. This
 between defend and attack for an AI player.<br>
 <br>
 
+<h2>Variables</h2>
+
+<a name="AiTypes"></a>
+<h3>AiTypes</h3>
+
+<p>This table lists all the AI types defined in Lua.  It is used by
+both the engine and the Lua code.  In AiTypes, every AI type has an
+entry that is a table with these keys:</p>
+
+<dl>
+  <dt>Ident = string</dt>
+  <dd>Unique, language-independent identifier of this AI type.
+    By convention, each identifier should begin with "ai-".
+    The Ident string is also the key of the entry within AiTypes.
+    The map setup file sets e.g. <code>Players[1].AiName = "ai-blitz"</code>
+    and the engine then finds the AI type whose identifier is that string;
+    or if no such AI type exists, the engine falls back to "ai-passive".
+    In saved games, these names are used in
+    the "ai-type" parameter of <a href="savegame.html#DefineAiPlayer">DefineAiPlayer</a>
+    and the "ai-name" parameter of <a href="savegame.html#Player">Player</a>.</dd>
+  <dt>Name = string</dt>
+  <dd>Name of the AI the Player sees and which gets translated.
+    The engine does not use this; only the Lua code does.</dd>
+  <dt>Init = function or nil</dt>
+  <dd>Initializes the AI state of the player to whatever the AI type
+    requires.  When starting a new game, the engine calls the Init
+    function for each player that is going to use this AI type.
+    <b>FIXME: not yet correctly implemented.</b></dd>
+  <dt>EachSecond = function</dt>
+  <dd>Decides AI actions: primarily, what to build.
+    The engine calls the EachSecond function once per second
+    (at the default game speed).</dd>
+  <dt>(other)</dt>
+  <dd>Lua code can add other keys in the table.
+    The engine will ignore them.
+    (Perhaps there should be a naming convention so that
+    Lua-specific and engine-specific keys won't clash.
+    Or we can just document all keys here.)</dd>
+</dl>
+
+<p>Lua code is free to read the AiTypes table but should not
+modify it or its elements directly.  Instead, it should call
+<a href="#DefineAiType">DefineAiType</a>, so that the engine
+knows to update its data structures accordingly.</p>
+
 <h2>Functions</h2>
-<a name="DefineAi"></a>
-<h3>DefineAi(name, script)</h3>
+<a name="DefineAiType"></a>
+<h3>DefineAiType(table)</h3>
 
-This defines how a special AI works. Each level can use his own AI definition.
+<p>Defines an AI type and adds it to <a href="#AiTypes">AiTypes</a>.</p>
 
 <dl>
-<dt>name</dt>
-<dd>
-Unique, language-independent name of this AI definition.
-By convention, each name should begin with "ai-".
-The map setup file sets e.g. <code>Players[1].AiName = "ai-blitz"</code>
-and the engine then finds the AI definition whose name is that string;
-or if no such AI definition exists, the engine falls back to "ai-passive".
-In saved games, these names are used in
-the "ai-type" parameter of <a href="savegame.html#DefineAiPlayer">DefineAiPlayer</a>
-and the "ai-name" parameter of <a href="savegame.html#Player">Player</a>.
-</dd>
-<dt>script</dt>
-<dd>A lua function.
-</dd>
+  <dt>table</dt>
+  <dd>Describes the AI type, in the same format
+    as an element of <a href="#AiTypes">AiTypes</a>.</dd>
 </dl>
 
+<p><b>Note:</b> Individual maps should not call DefineAiType.
+Call it during global initialization instead.
+The engine does not provide any way to undefine an AI type
+and reacts poorly to redefinitions.</p>
+
 <h4>Example</h4>
 
 <pre>
     -- Defines the passive computer AI, which does nothing.
-DefineAi("ai-passive", function() AiSleep(10000) end)
+DefineAiType({
+	Ident = "ai-passive",
+	Name = _("Passive"),
+	Init = nil, -- which is the default anyway
+	EachSecond = function() end })
 </pre>
 
 <a name="DefineAiHelper"></a>
@@ -560,34 +604,6 @@ supported.
 The current AI script support is very limited, many new functions are needed.
 If you want to write this you know who to contact.
 
-<h2>Examples</h2>
-
-<pre>
-DefineAi("harsh-example", "land-attack",
- function()
-</pre><p style="margin-left:1cm;">
-This is the AI "harsh-example" usable for land-attack.
-</p><pre>
-     AiNeed("unit-vault")
-</pre><p style="margin-left:1cm;">
-The first need unit is the great-hall.
-</p><pre>
-     AiNeed("unit-engineer")
-</pre><p style="margin-left:1cm;">
-The next unit should be the peon.
-</p><pre>
-     AiWait("unit-vault")
-</pre><p style="margin-left:1cm;">
-Now wait until the vault is available. Could hang if no great vault can be
-build.
-</p><pre>
-     AiWait("unit-engineer")
-</pre><p style="margin-left:1cm;">
-Now wait unit we have a worker to build the next things.
-</p>
-
-FIXME: need some complex examples.
-
 <hr>
 All trademarks and copyrights on this page are owned by their respective owners.
 <address>(c) 2002-2010 by <a href="http://boswars.org">
diff --git a/doc/scripts/index.html b/doc/scripts/index.html
index 300b6dc..6824309 100644
--- a/doc/scripts/index.html
+++ b/doc/scripts/index.html
@@ -93,6 +93,8 @@
 <dd></dd>
 <dt><a href="ai.html#AiSleep">AiSleep</a></dt>
 <dd></dd>
+<dt><a href="ai.html#AiTypes">AiTypes</a></dt>
+<dd></dd>
 <dt><a href="ai.html#AiWait">AiWait</a></dt>
 <dd></dd>
 <dt><a href="ai.html#AiWaitForce">AiWaitForce</a></dt>
@@ -113,12 +115,12 @@
 <dd></dd>
 <dt><a href="game.html#CreateUnit">CreateUnit</a></dt>
 <dd></dd>
-<dt><a href="ai.html#DefineAi">DefineAi</a></dt>
-<dd></dd>
 <dt><a href="ai.html#DefineAiHelper">DefineAiHelper</a></dt>
 <dd></dd>
 <dt><a href="savegame.html#DefineAiPlayer">DefineAiPlayer</a></dt>
 <dd></dd>
+<dt><a href="ai.html#DefineAiType">DefineAiType</a></dt>
+<dd></dd>
 <dt><a href="limits.html#DefineAllow">DefineAllow</a></dt>
 <dd></dd>
 <dt><a href="unittype.html#DefineAnimations">DefineAnimations</a></dt>
diff --git a/doc/scripts/savegame.html b/doc/scripts/savegame.html
index 248d3d2..5e24c61 100644
--- a/doc/scripts/savegame.html
+++ b/doc/scripts/savegame.html
@@ -63,16 +63,8 @@ map setup files should not.</p>
     <dl>
       <dt>"ai-type", string</dt>
       <dd>
-	Name of the AI definition.  Matches the name parameter
-	of <a href="ai.html#DefineAi">DefineAi</a>.
-      </dd>
-      <dt>"script", string</dt>
-      <dd>
-	<em>Obsolete since r10061 (2011-07-08):
-	  No longer used, because "ai-type" is already unique.</em><br>
-
-	Name of the AI script function in the "_ai_scripts_" Lua table.
-	<a href="ai.html#DefineAi">DefineAi</a> generates this name.
+	Identifier of the AI type.  Matches the Ident field of the
+	table parameter of <a href="ai.html#DefineAiType">DefineAiType</a>.
       </dd>
       <dt>"script-debug", boolean</dt>
       <dd>
diff --git a/engine/ai/ai.cpp b/engine/ai/ai.cpp
index 469dd8e..2c08218 100644
--- a/engine/ai/ai.cpp
+++ b/engine/ai/ai.cpp
@@ -140,7 +140,10 @@
 -- Variables
 ----------------------------------------------------------------------------*/
 
-std::vector<CAiType *> AiTypes; /// List of all AI types.
+/** List of all AI types.
+ * DefineAiType keeps this in sync with the AiTypes Lua variable.  */
+std::vector<CAiType *> AiTypes;
+
 AiHelper AiHelpers;             /// AI helper variables
 
 PlayerAi *AiPlayer;             /// Current AI player
@@ -156,12 +159,11 @@ static void AiExecuteScript()
 {
 	if (AiPlayer->AiType != NULL)
 	{
-		lua_pushstring(Lua, "_ai_scripts_");
-		lua_gettable(Lua, LUA_GLOBALSINDEX);
-		lua_pushstring(Lua, AiPlayer->AiType->Name.c_str());
-		lua_rawget(Lua, -2);
-		LuaCall(0, 1);
-		lua_pop(Lua, 1);
+		lua_getfield(Lua, LUA_GLOBALSINDEX, "AiTypes");
+		lua_getfield(Lua, -1, AiPlayer->AiType->Name.c_str());
+		lua_getfield(Lua, -1, "EachSecond");
+		LuaCall(0, 1);	// removes the function from the stack
+		lua_pop(Lua, 2); // remove the tables too
 	}
 }
 
diff --git a/engine/ai/script_ai.cpp b/engine/ai/script_ai.cpp
index 2468e2a..1fc5b8c 100644
--- a/engine/ai/script_ai.cpp
+++ b/engine/ai/script_ai.cpp
@@ -273,35 +273,63 @@ static int CclDefineAiHelper(lua_State *l)
 }
 
 /**
-**  Define an AI engine.
+**  Define an AI type.
 **
 **  @param l  Lua state.
+**
+**  @par Lua parameters;
+**  @b ai_type A table that describes the AI type.
+**  See the documentation of the AiTypes Lua variable for details.
 */
-static int CclDefineAi(lua_State *l)
+static int CclDefineAiType(lua_State *l)
 {
+	// Index of the ai_type parameter from Lua.
+	const int aitype_index = 1;
+
 	CAiType *aitype;
-#ifdef DEBUG
-	const CAiType *ait;
-#endif
 
-	LuaCheckArgs(l, 2);
-	if (!lua_isfunction(l, 2))
+	LuaCheckArgs(l, 1);
+	luaL_checktype(l, aitype_index, LUA_TTABLE);
+	// The following type checks do not use luaL_checktype because
+	// it is intended for arguments of functions only and generates
+	// error messages accordingly.
+
+	lua_getfield(l, aitype_index, "Ident");
+	const int ident_index = lua_gettop(l);
+	if (!lua_isstring(l, ident_index))
 	{
-		LuaError(l, "incorrect argument");
+		LuaError(l, "incorrect argument: Ident of ai_type must be a string");
 	}
+	size_t ident_length = 0;
+	const char *ident_ptr = lua_tolstring(l, ident_index, &ident_length);
+	// Leave it on the stack.
+
+	lua_getfield(l, aitype_index, "Init");
+	if (!lua_isfunction(l, -1) && !lua_isnil(l, -1))
+	{
+		LuaError(l, "incorrect argument: Init of ai_type must be a function or nil");
+	}
+	lua_pop(l, 1);
+
+	lua_getfield(l, aitype_index, "EachSecond");
+	if (!lua_isfunction(l, -1))
+	{
+		LuaError(l, "incorrect argument: EachSecond must be a function");
+	}
+	lua_pop(l, 1);
 
 	aitype = new CAiType;
-	AiTypes.insert(AiTypes.begin(), aitype);
+	AiTypes.push_back(aitype);
 
 	//
 	// AI Name
 	//
-	aitype->Name = LuaToString(l, 1);
+	aitype->Name.assign(ident_ptr, ident_ptr + ident_length);
 
 #ifdef DEBUG
-	for (int i = 1; i < (int)AiTypes.size(); ++i)
+	for (size_t i = 0; i < AiTypes.size() - 1; ++i)
 	{
-		ait = AiTypes[i];
+		const CAiType *ait = AiTypes[i];
 		if (aitype->Name == ait->Name)
 		{
 			DebugPrint("Warning two or more AI's with the same name '%s'\n" _C_ ait->Name.c_str());
@@ -310,24 +338,14 @@ static int CclDefineAi(lua_State *l)
 #endif
 
 	//
-	// AI Script
+	// Add to the AiTypes Lua variable.
 	//
-	lua_pushstring(l, "_ai_scripts_");
-	lua_gettable(l, LUA_GLOBALSINDEX);
-	if (lua_isnil(l, -1))
-	{
-		lua_pop(l, 1);
-		lua_pushstring(l, "_ai_scripts_");
-		lua_newtable(l);
-		lua_settable(l, LUA_GLOBALSINDEX);
-		lua_pushstring(l, "_ai_scripts_");
-		lua_gettable(l, LUA_GLOBALSINDEX);
-	}
-	lua_pushstring(l, aitype->Name.c_str());
-	lua_pushvalue(l, 2);
-	lua_rawset(l, -3);
-	lua_pop(l, 1);
+	lua_getfield(l, LUA_GLOBALSINDEX, "AiTypes");
+	lua_pushvalue(l, ident_index);
+	lua_pushvalue(l, aitype_index);
+	lua_settable(l, -3);
 
+	// Lua will discard the values we left on the stack.
 	return 0;
 }
 
@@ -1109,7 +1127,7 @@ void AiCclRegister()
 	// Loading all into memory isn't necessary.
 
 	lua_register(Lua, "DefineAiHelper", CclDefineAiHelper);
-	lua_register(Lua, "DefineAi", CclDefineAi);
+	lua_register(Lua, "DefineAiType", CclDefineAiType);
 
 	lua_register(Lua, "AiDebug", CclAiDebug);
 	lua_register(Lua, "AiDebugPlayer", CclAiDebugPlayer);
diff --git a/engine/stratagus/script.cpp b/engine/stratagus/script.cpp
index 2414a4f..1f629e4 100644
--- a/engine/stratagus/script.cpp
+++ b/engine/stratagus/script.cpp
@@ -954,6 +954,7 @@ static bool IsBlacklistedGlobal(const char *key)
 		!strcmp(key, "io") ||
 		!strcmp(key, "debug") ||
 		!strcmp(key, "coroutine") ||
+		!strcmp(key, "AiTypes") ||			    // data-dep
 		!strcmp(key, "AllowAll") ||			    // Lua const
 		!strcmp(key, "DownButton") ||			    // #define
 		!strcmp(key, "EditorCommandLine") ||		    // enum
diff --git a/scripts/ai.lua b/scripts/ai.lua
index 05d8833..23f96f5 100644
--- a/scripts/ai.lua
+++ b/scripts/ai.lua
@@ -27,27 +27,8 @@
 --
 --
 
--- The list of registered AIs in BOS
--- Every AI has an entry name: {internal_name, name, fun, initfun}
--- See at RegisterAi() for a description what these are
--- internal_name is also the key of the entry in AiList.
-local AiList = {}
-
-function GetAiList()
-  return AiList
-end
-
--- Function to register an AI to BOS
--- Parameters:
--- internal_name : Internal name of the Ai (without leading "ai-")
--- name          : Name of the AI the Player sees and which gets translated
--- fun           : main AI function
--- initfun       : initialization function, can be omitted
-function RegisterAi(internal_name, name, fun, initfun)
-  internal_name = "ai-" .. internal_name
-  DefineAi(internal_name, fun)
-  AiList[internal_name] = {internal_name, name, fun, initfun}
-end
+-- Table of registered AI types in BOS.  See HTML documentation for details.
+AiTypes = {}
 
 -- This call examines the buttons that were defined with DefineButton
 -- and figures out which types of units can build/train/repair others.
@@ -55,11 +36,12 @@ end
 DefineAiHelper()
 
 -- Execute all AI init scripts
+-- FIXME: needs to be done per player; probably move to C++
 function InitAiScripts()
-  for key,value in next,AiList do
+  for ident,ai_type in next,AiTypes do
     -- check if this AI actually has an init script
-    if (value[4] ~= nil) then
-      value[4]()
+    if (ai_type.Init ~= nil) then
+      ai_type.Init()
     end
   end
 end
@@ -76,4 +58,3 @@ for i,f in ipairs(list) do
     Load("scripts/ais/" .. f)
   end
 end
-
diff --git a/scripts/ais/air_attack.lua b/scripts/ais/air_attack.lua
index 08e470b..2bf144b 100644
--- a/scripts/ais/air_attack.lua
+++ b/scripts/ais/air_attack.lua
@@ -95,6 +95,8 @@ function AiAirAttack()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("air_attack", _("Air attack"), AiAirAttack, 
-           InitAiScripts_air_attack)
-
+DefineAiType({
+	Ident = "ai-air_attack",
+	Name = _("Air attack"),
+	Init = InitAiScripts_air_attack,
+	EachSecond = AiAirAttack })
diff --git a/scripts/ais/attackdefense.lua b/scripts/ais/attackdefense.lua
index 1505c6d..68970fa 100644
--- a/scripts/ais/attackdefense.lua
+++ b/scripts/ais/attackdefense.lua
@@ -302,5 +302,8 @@ function AiAttackDefense()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("attackdefense", _("Attack/Defense"), AiAttackDefense, InitAiScripts_attackdefense)
-
+DefineAiType({
+	Ident = "ai-attackdefense",
+	Name = _("Attack/Defense"),
+	Init = InitAiScripts_attackdefense,
+	EachSecond = AiAttackDefense })
diff --git a/scripts/ais/blitz.lua b/scripts/ais/blitz.lua
index 76a5b36..0940d9a 100644
--- a/scripts/ais/blitz.lua
+++ b/scripts/ais/blitz.lua
@@ -195,7 +195,8 @@ function AiBlitz()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("blitz", _("Blitz"), AiBlitz, InitAiScripts_blitz)
-
-
-
+DefineAiType({
+	Ident = "ai-blitz",
+	Name = _("Blitz"),
+	Init = InitAiScripts_blitz,
+	EachSecond = AiBlitz })
diff --git a/scripts/ais/broke.lua b/scripts/ais/broke.lua
index 2c83c63..78a0f3b 100755
--- a/scripts/ais/broke.lua
+++ b/scripts/ais/broke.lua
@@ -283,5 +283,8 @@ function AiBroke()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("broke", _("Broke"), AiBroke, InitAiScripts_broke)
-
+DefineAiType({
+	Ident = "ai-broke",
+	Name = _("Broke"),
+	Init = InitAiScripts_broke,
+	EachSecond = AiBroke })
diff --git a/scripts/ais/default.lua b/scripts/ais/default.lua
index 17b6bea..0e962b3 100644
--- a/scripts/ais/default.lua
+++ b/scripts/ais/default.lua
@@ -186,5 +186,8 @@ function AiDefault()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("default", _("Default"), AiDefault, InitAiScripts_default)
-
+DefineAiType({
+	Ident = "ai-default",
+	Name = _("Default"),
+	Init = InitAiScripts_default,
+	EachSecond = AiDefault })
diff --git a/scripts/ais/just_attack.lua b/scripts/ais/just_attack.lua
index ac363e3..102b609 100644
--- a/scripts/ais/just_attack.lua
+++ b/scripts/ais/just_attack.lua
@@ -100,6 +100,8 @@ function AiJustAttack()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("just_attack", _("Just attack"), AiJustAttack,
-           InitAiScripts_just_attack)
-
+DefineAiType({
+	Ident = "ai-just_attack",
+	Name = _("Just attack"),
+	Init = InitAiScripts_just_attack,
+	EachSecond = AiJustAttack })
diff --git a/scripts/ais/naval.lua b/scripts/ais/naval.lua
index f3142e2..f1b81cd 100644
--- a/scripts/ais/naval.lua
+++ b/scripts/ais/naval.lua
@@ -137,6 +137,8 @@ function AiNaval()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("naval", _("Naval"), AiNaval,
-           InitAiScripts_naval)
-
+DefineAiType({
+	Ident = "ai-naval",
+	Name = _("Naval"),
+	Init = InitAiScripts_naval,
+	EachSecond = AiNaval })
diff --git a/scripts/ais/passive.lua b/scripts/ais/passive.lua
index 15335d3..1e20213 100644
--- a/scripts/ais/passive.lua
+++ b/scripts/ais/passive.lua
@@ -31,5 +31,8 @@
 function AiPassive()
 end
 
-RegisterAi("passive", _("Passive"), AiPassive)
-
+DefineAiType({
+	Ident = "ai-passive",
+	Name = _("Passive"),
+	Init = nil,
+	EachSecond = AiPassive })
diff --git a/scripts/ais/rush.lua b/scripts/ais/rush.lua
index de7242f..73c4e9b 100644
--- a/scripts/ais/rush.lua
+++ b/scripts/ais/rush.lua
@@ -158,5 +158,8 @@ function AiRush()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("rush", _("Rush"), AiRush, InitAiScripts_rush)
-
+DefineAiType({
+	Ident = "ai-rush",
+	Name = _("Rush"),
+	Init = InitAiScripts_rush,
+	EachSecond = AiRush })
diff --git a/scripts/ais/spacious.lua b/scripts/ais/spacious.lua
index 41bf184..2bce869 100644
--- a/scripts/ais/spacious.lua
+++ b/scripts/ais/spacious.lua
@@ -221,5 +221,8 @@ function AiSpacious()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("spacious", _("Spacious"), AiSpacious, InitAiScripts_spacious)
-
+DefineAiType({
+	Ident = "ai-spacious",
+	Name = _("Spacious"),
+	Init = InitAiScripts_spacious,
+	EachSecond = AiSpacious })
diff --git a/scripts/ais/tankrush.lua b/scripts/ais/tankrush.lua
index c084cf4..55d5af7 100644
--- a/scripts/ais/tankrush.lua
+++ b/scripts/ais/tankrush.lua
@@ -154,5 +154,8 @@ function AiTankRush()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("tankrush", _("Tank rush"), AiTankRush, InitAiScripts_tankrush)
-
+DefineAiType({
+	Ident = "ai-tankrush",
+	Name = _("Tank rush"),
+	Init = InitAiScripts_tankrush,
+	EachSecond = AiTankRush })
diff --git a/scripts/ais/zombies.lua b/scripts/ais/zombies.lua
index 708fd42..e0606b9 100644
--- a/scripts/ais/zombies.lua
+++ b/scripts/ais/zombies.lua
@@ -156,5 +156,8 @@ function AiZombies()
     return AiLoop(ai_funcs, ai_pos)
 end
 
-RegisterAi("zombies", _("Zombies"), AiZombies, InitAiScripts_zombies)
-
+DefineAiType({
+	Ident = "ai-zombies",
+	Name = _("Zombies"),
+	Init = InitAiScripts_zombies,
+	EachSecond = AiZombies })
diff --git a/scripts/menus/ingame/editor.lua b/scripts/menus/ingame/editor.lua
index bd1bc79..ea9b94a 100644
--- a/scripts/menus/ingame/editor.lua
+++ b/scripts/menus/ingame/editor.lua
@@ -205,15 +205,14 @@ function RunEditorPlayerPropertiesMenu()
   l:adjustSize()
   menu:add(l, 210, 40 + (22 * 0))
 
-  local types = {}		-- key >= 1; value = name
-  local itypes = {}		-- key >= 0; value = internal_name
-  local ainametonum = {}	-- key = internal_name; value >= 0
+  local types = {}		-- key >= 1; value = .Name
+  local itypes = {}		-- key >= 0; value = .Ident
+  local ainametonum = {}	-- key = .Ident; value >= 0
   local i = 1
-  local ailist = GetAiList()
-  for aiiname,x in pairs(ailist) do
-    types[i] = ailist[aiiname][2]
-    ainametonum[aiiname] = i - 1
-    itypes[i - 1] = aiiname
+  for ai_ident,ai_type in pairs(AiTypes) do
+    types[i] = ai_type.Name
+    ainametonum[ai_ident] = i - 1
+    itypes[i - 1] = ai_ident
     i = i + 1
   end
 
@@ -221,12 +220,14 @@ function RunEditorPlayerPropertiesMenu()
   -- then add them to the list, so that the editor will at least
   -- display them and save them back as they were.
   for i=0,LastEditablePlayer do
-    local internal_name = Players[i].AiName
-    if ainametonum[internal_name] == nil then
-      local name = internal_name .. _(" (unsupported)")
+    local ident = Players[i].AiName
+    -- Check ainametonum[ident] rather than AiTypes[ident]
+    -- so we add each ident only once even if multiple players have it.
+    if ainametonum[ident] == nil then
+      local name = ident .. _(" (unsupported)")
       types[#types + 1] = name
-      itypes[#itypes + 1] = internal_name
-      ainametonum[internal_name] = #itypes
+      itypes[#itypes + 1] = ident
+      ainametonum[ident] = #itypes
     end
   end
 
-- 
1.7.7

