Implement unit path prediction in siege engine.

develop
Alexander Gavrilov 2012-09-09 20:54:12 +04:00
parent 9679b7729c
commit 8ab615f6d0
1 changed files with 316 additions and 1 deletions

@ -124,6 +124,11 @@ static int random_int(int val)
return int(int64_t(rand())*val/RAND_MAX);
}
static int point_distance(df::coord speed)
{
return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z)));
}
/*
* Configuration management
*/
@ -134,6 +139,7 @@ struct EngineInfo {
int id;
coord_range target;
df::coord center;
int proj_speed, hit_delay;
bool hasTarget() { return is_range_valid(target); }
bool onTarget(df::coord pos) { return is_in_range(target, pos); }
@ -155,8 +161,11 @@ static EngineInfo *find_engine(df::building *bld, bool create = false)
return NULL;
auto *obj = &engines[bld];
obj->id = bld->id;
obj->center = df::coord(bld->centerx, bld->centery, bld->z);
obj->proj_speed = 2;
obj->hit_delay = 3;
coord_engines[obj->center] = bld;
return obj;
@ -285,7 +294,7 @@ struct ProjectilePath {
void calc_line()
{
speed = target - origin;
divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z)));
divisor = point_distance(speed);
if (divisor <= 0) divisor = 1;
direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1);
}
@ -504,6 +513,292 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::
* Unit tracking
*/
static const float MAX_TIME = 1000000.0f;
struct UnitPath {
df::unit *unit;
std::map<float, df::coord> path;
struct Hit {
UnitPath *path;
df::coord pos;
int dist;
float time, lmargin, rmargin;
};
static std::map<df::unit*, UnitPath*> cache;
static UnitPath *get(df::unit *unit)
{
auto &cv = cache[unit];
if (!cv) cv = new UnitPath(unit);
return cv;
};
UnitPath(df::unit *unit) : unit(unit)
{
df::coord pos = unit->pos;
df::coord dest = unit->path.dest;
auto &upath = unit->path.path;
if (dest.isValid() && !upath.x.empty())
{
float time = unit->counters.job_counter+0.5f;
float speed = Units::computeMovementSpeed(unit)/100.0f;
for (size_t i = 0; i < upath.size(); i++)
{
df::coord new_pos = upath[i];
if (new_pos == pos)
continue;
float delay = speed;
if (new_pos.x != pos.x && new_pos.y != pos.y)
delay *= 362.0/256.0;
path[time] = pos;
pos = new_pos;
time += delay + 1;
}
}
path[MAX_TIME] = pos;
}
void get_margin(std::map<float,df::coord>::iterator &it, float time, float *lmargin, float *rmargin)
{
auto it2 = it;
*lmargin = (it == path.begin()) ? MAX_TIME : time - (--it2)->first;
*rmargin = (it->first == MAX_TIME) ? MAX_TIME : it->first - time;
}
df::coord posAtTime(float time, float *lmargin = NULL, float *rmargin = NULL)
{
CHECK_INVALID_ARGUMENT(time < MAX_TIME);
auto it = path.upper_bound(time);
if (lmargin)
get_margin(it, time, lmargin, rmargin);
return it->second;
}
bool findHits(EngineInfo *engine, std::vector<Hit> *hit_points, float bias)
{
df::coord origin = engine->center;
Hit info;
info.path = this;
for (auto it = path.begin(); it != path.end(); ++it)
{
info.pos = it->second;
info.dist = point_distance(origin - info.pos);
info.time = float(info.dist)*(engine->proj_speed+1) + engine->hit_delay + bias;
get_margin(it, info.time, &info.lmargin, &info.rmargin);
if (info.lmargin > 0 && info.rmargin > 0)
{
if (engine->onTarget(info.pos))
hit_points->push_back(info);
}
}
return !hit_points->empty();
}
};
std::map<df::unit*, UnitPath*> UnitPath::cache;
static void push_margin(lua_State *L, float margin)
{
if (margin == MAX_TIME)
lua_pushnil(L);
else
lua_pushnumber(L, margin);
}
static int traceUnitPath(lua_State *L)
{
auto unit = Lua::CheckDFObject<df::unit>(L, 1);
CHECK_NULL_POINTER(unit);
size_t idx = 1;
auto info = UnitPath::get(unit);
lua_createtable(L, info->path.size(), 0);
float last_time = 0.0f;
for (auto it = info->path.begin(); it != info->path.end(); ++it)
{
Lua::Push(L, it->second);
if (idx > 1)
{
lua_pushnumber(L, last_time);
lua_setfield(L, -2, "from");
}
if (idx < info->path.size())
{
lua_pushnumber(L, it->first);
lua_setfield(L, -2, "to");
}
lua_rawseti(L, -2, idx++);
last_time = it->first;
}
return 1;
}
static int unitPosAtTime(lua_State *L)
{
auto unit = Lua::CheckDFObject<df::unit>(L, 1);
float time = luaL_checknumber(L, 2);
CHECK_NULL_POINTER(unit);
float lmargin, rmargin;
auto info = UnitPath::get(unit);
Lua::Push(L, info->posAtTime(time, &lmargin, &rmargin));
push_margin(L, lmargin);
push_margin(L, rmargin);
return 3;
}
static void proposeUnitHits(EngineInfo *engine, std::vector<UnitPath::Hit> *hits, float bias)
{
auto &active = world->units.active;
for (size_t i = 0; i < active.size(); i++)
{
auto unit = active[i];
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
unit->flags1.bits.caged ||
unit->flags1.bits.chained ||
unit->flags1.bits.rider ||
unit->flags1.bits.hidden_in_ambush)
continue;
UnitPath::get(unit)->findHits(engine, hits, bias);
}
}
static int proposeUnitHits(lua_State *L)
{
auto bld = Lua::CheckDFObject<df::building_siegeenginest>(L, 1);
float bias = luaL_optnumber(L, 2, 0);
auto engine = find_engine(bld);
if (!engine)
luaL_error(L, "no such engine");
if (!engine->hasTarget())
luaL_error(L, "target not set");
std::vector<UnitPath::Hit> hits;
proposeUnitHits(engine, &hits, bias);
lua_createtable(L, hits.size(), 0);
for (size_t i = 0; i < hits.size(); i++)
{
auto &hit = hits[i];
lua_createtable(L, 0, 6);
Lua::PushDFObject(L, hit.path->unit); lua_setfield(L, -2, "unit");
Lua::Push(L, hit.pos); lua_setfield(L, -2, "pos");
lua_pushnumber(L, hit.dist); lua_setfield(L, -2, "dist");
lua_pushnumber(L, hit.time); lua_setfield(L, -2, "time");
push_margin(L, hit.lmargin); lua_setfield(L, -2, "lmargin");
push_margin(L, hit.rmargin); lua_setfield(L, -2, "rmargin");
lua_rawseti(L, -2, i+1);
}
return 1;
}
#if 0
struct UnitContext {
AimContext &ctx;
struct UnitInfo {
df::unit *unit;
UnitPath path;
float score;
UnitInfo(df::unit *unit) : unit(unit), path(unit) {}
};
std::map<df::unit*, UnitInfo*> units;
UnitContext(AimContext &ctx) : ctx(ctx) {}
~UnitContext()
{
for (auto it = units.begin(); it != units.end(); ++it)
delete it->second;
}
float unit_score(df::unit *unit)
{
float score = 1.0f;
if (unit->flags1.bits.tame && unit->civ_id == ui->civ_id)
score = -1.0f;
if (unit->flags1.bits.diplomat || unit->flags1.bits.merchant)
score = -2.0f;
else if (Units::isCitizen(unit))
score = -10.0f;
else
{
if (unit->flags1.bits.marauder)
score += 0.5f;
if (unit->flags1.bits.active_invader)
score += 1.0f;
if (unit->flags1.bits.invader_origin)
score += 1.0f;
if (unit->flags1.bits.invades)
score += 1.0f;
if (unit->flags1.bits.hidden_ambusher)
score += 1.0f;
}
if (unit->flags1.bits.ridden)
{
for (size_t i = 0; i < unit->refs.size(); i++)
{
if (!unit->refs[i]->getType() == general_ref_type::UNIT_RIDER)
continue;
if (auto rider = unit->refs[i]->getUnit())
score += unit_score(rider);
}
}
}
void select_units()
{
auto &active = world->units.active;
for (size_t i = 0; i < active.size(); i++)
{
auto unit = active[i];
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
unit->flags1.bits.caged ||
unit->flags1.bits.chained ||
unit->flags1.bits.rider ||
unit->flags1.bits.hidden_in_ambush)
continue;
auto info = units[unit] = new UnitInfo(unit);
info->findHits(ctx, ctx.proj_hit_delay);
info->score = unit_score(unit);
}
}
};
#endif
/*
* Projectile hook
*/
@ -617,6 +912,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(getTargetArea),
DFHACK_LUA_COMMAND(traceUnitPath),
DFHACK_LUA_COMMAND(unitPosAtTime),
DFHACK_LUA_COMMAND(proposeUnitHits),
DFHACK_LUA_END
};
@ -648,6 +946,17 @@ static bool enable_plugin()
return true;
}
static void clear_caches()
{
if (!UnitPath::cache.empty())
{
for (auto it = UnitPath::cache.begin(); it != UnitPath::cache.end(); ++it)
delete it->second;
UnitPath::cache.clear();
}
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
@ -688,3 +997,9 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
enable_hooks(false);
return CR_OK;
}
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
clear_caches();
return CR_OK;
}