Open source Star Ruler 2 source code!
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
#include "design/subsystem.h"
|
||||
#include "design/hull.h"
|
||||
#include "util/refcount.h"
|
||||
#include "image.h"
|
||||
#include <unordered_set>
|
||||
|
||||
class SaveFile;
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
struct DesignClass {
|
||||
unsigned id;
|
||||
std::string name;
|
||||
std::vector<const Design*> designs;
|
||||
};
|
||||
|
||||
struct DesignError {
|
||||
bool fatal;
|
||||
std::string text;
|
||||
const Subsystem* subsys;
|
||||
const SubsystemDef::ModuleDesc* module;
|
||||
vec2i hex;
|
||||
|
||||
DesignError(bool Fatal, std::string Text,
|
||||
const Subsystem* Subsys = 0, const SubsystemDef::ModuleDesc* Module = 0,
|
||||
vec2i Hex = vec2i(-1, -1))
|
||||
: fatal(Fatal), text(Text), subsys(Subsys), module(Module), hex(Hex) {
|
||||
}
|
||||
};
|
||||
|
||||
class Empire;
|
||||
class Design : public AtomicRefCounted {
|
||||
public:
|
||||
//Metadata
|
||||
const HullDef* hull;
|
||||
std::string name;
|
||||
double size;
|
||||
double hexSize;
|
||||
unsigned interiorHexes;
|
||||
unsigned exteriorHexes;
|
||||
unsigned stateCount;
|
||||
unsigned effectorStateCount;
|
||||
unsigned effectorCount;
|
||||
unsigned dataCount;
|
||||
bool initialized;
|
||||
double totalHP;
|
||||
double quadrantTotalHP[4];
|
||||
mutable bool outdated;
|
||||
Color color;
|
||||
Color dullColor;
|
||||
|
||||
//Errors
|
||||
std::vector<DesignError> errors;
|
||||
bool hasFatalErrors() const;
|
||||
bool hasTag(const std::string& tag) const;
|
||||
|
||||
std::unordered_set<int> numTags;
|
||||
bool hasTag(int index) const;
|
||||
|
||||
//Subsystems
|
||||
std::vector<Subsystem> subsystems;
|
||||
std::vector<Subsystem*> damageOrder;
|
||||
std::vector<SubsystemDef::ShipModifier> modifiers;
|
||||
HexGrid<int> grid;
|
||||
HexGrid<int> hexIndex;
|
||||
HexGrid<int> hexStatusIndex;
|
||||
std::vector<vec2u> hexes;
|
||||
std::unordered_set<uint64_t> errorHexes;
|
||||
unsigned usedHexCount;
|
||||
float* shipVariables;
|
||||
vec2u cropMin;
|
||||
vec2u cropMax;
|
||||
|
||||
//Ownership and usage data
|
||||
mutable unsigned id;
|
||||
mutable Empire* owner;
|
||||
mutable bool used;
|
||||
mutable bool obsolete;
|
||||
mutable int revision;
|
||||
mutable threads::atomic_int built;
|
||||
mutable threads::atomic_int active;
|
||||
mutable render::Sprite icon;
|
||||
mutable render::Sprite distantIcon;
|
||||
mutable render::Sprite fleetIcon;
|
||||
bool forceHull;
|
||||
|
||||
mutable net::Message* data;
|
||||
mutable asIScriptObject* clientData;
|
||||
mutable asIScriptObject* serverData;
|
||||
|
||||
//Manual updates bump revision numbers and
|
||||
//use these 'horizontal' lists.
|
||||
mutable heldPointer<const Design> newer;
|
||||
const Design* newest() const;
|
||||
const Design* next() const;
|
||||
|
||||
//Automatic updates do not bump revisions and
|
||||
//use this 'vertical' list of designs.
|
||||
mutable heldPointer<const Design> original;
|
||||
mutable heldPointer<const Design> updated;
|
||||
const Design* mostUpdated() const;
|
||||
const Design* base() const;
|
||||
|
||||
mutable DesignClass* cls;
|
||||
|
||||
//Designs are created via descriptors
|
||||
struct Descriptor {
|
||||
struct System {
|
||||
const SubsystemDef* type;
|
||||
vec3d direction;
|
||||
std::vector<vec2u> hexes;
|
||||
std::vector<const SubsystemDef::ModuleDesc*> modules;
|
||||
|
||||
System() : type(nullptr), direction(vec3d::front()) {
|
||||
}
|
||||
};
|
||||
|
||||
Empire* owner;
|
||||
const HullDef* hull;
|
||||
std::string name;
|
||||
std::string className;
|
||||
std::string hullName;
|
||||
double size;
|
||||
vec2u gridSize;
|
||||
std::vector<Descriptor::System> systems;
|
||||
std::vector<const SubsystemDef*> appliedSystems;
|
||||
bool staticHull;
|
||||
bool forceHull;
|
||||
asIScriptObject* settings;
|
||||
|
||||
Descriptor() : owner(0), hull(0), size(1), staticHull(false), forceHull(false), gridSize(1,1), settings(nullptr) {
|
||||
}
|
||||
|
||||
~Descriptor() {
|
||||
if(hull)
|
||||
hull->drop();
|
||||
if(settings)
|
||||
settings->Release();
|
||||
}
|
||||
};
|
||||
|
||||
Design(const Design::Descriptor& desc);
|
||||
Design(net::Message& msg);
|
||||
Design(SaveFile& file);
|
||||
Design();
|
||||
~Design();
|
||||
|
||||
void init(net::Message& msg);
|
||||
void init(const Design::Descriptor& desc);
|
||||
void write(net::Message& msg) const;
|
||||
void save(SaveFile& file) const;
|
||||
void toDescriptor(Design::Descriptor& desc) const;
|
||||
void makeDistanceMap(Image& img, vec2i pos, vec2i size) const;
|
||||
void bindData();
|
||||
void buildDamageOrder();
|
||||
|
||||
unsigned getQuadrant(const vec2u& pos) const;
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
void initData(net::Message& msg);
|
||||
};
|
||||
@@ -0,0 +1,378 @@
|
||||
#include "design/effect.h"
|
||||
#include "compat/misc.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "network/message.h"
|
||||
#include "str_util.h"
|
||||
#include <unordered_map>
|
||||
|
||||
static std::vector<EffectDef*> effectDefinitions;
|
||||
static umap<std::string, int> effectIndices;
|
||||
|
||||
static const char* callback_retval[EH_COUNT] = {
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"DamageEventStatus",
|
||||
"DamageEventStatus",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
};
|
||||
|
||||
static const char* callback_basedecl[EH_COUNT] = {
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"DamageEvent&, const vec2u&",
|
||||
"DamageEvent&, vec2u&, vec2d&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&, Empire@, Empire@",
|
||||
"Event&",
|
||||
"Event&",
|
||||
};
|
||||
|
||||
void clearEffectDefinitions() {
|
||||
effectIndices.clear();
|
||||
foreach(it, effectDefinitions)
|
||||
delete *it;
|
||||
effectDefinitions.clear();
|
||||
}
|
||||
|
||||
void loadEffectDefinitions(const std::string& filename) {
|
||||
EffectDef* def = 0;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
datahandler("Effect", [&](std::string& value) {
|
||||
auto it = effectIndices.find(value);
|
||||
if(it != effectIndices.end()) {
|
||||
def = 0;
|
||||
error("Error: Duplicate effect %s.", value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def = new EffectDef();
|
||||
def->name = value;
|
||||
def->id = (unsigned)effectDefinitions.size();
|
||||
def->valueCount = 0;
|
||||
|
||||
effectDefinitions.push_back(def);
|
||||
effectIndices[def->name] = def->id;
|
||||
});
|
||||
|
||||
datahandler("Value", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
if(def->valueCount >= EFFECT_MAX_VALUES) {
|
||||
error("(Effect %s): Error: Maximum of 6 values allowed per effect.", def->name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
EffectDef::ValueDesc desc;
|
||||
auto pos = value.find('=');
|
||||
if(pos != std::string::npos) {
|
||||
if(pos < value.size() - 1)
|
||||
desc.defaultValue = Formula::fromInfix(value.substr(pos+1).c_str());
|
||||
value = trim(value.substr(0, pos));
|
||||
}
|
||||
|
||||
auto it = def->valueNames.find(value);
|
||||
if(it != def->valueNames.end()) {
|
||||
error("(Effect %s): Error: Duplicate effect value %s.", def->name.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->valueNames[value] = def->valueCount;
|
||||
def->values.push_back(desc);
|
||||
++def->valueCount;
|
||||
});
|
||||
|
||||
datahandler("Start", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Start] = value;
|
||||
});
|
||||
|
||||
datahandler("Destroy", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Destroy] = value;
|
||||
});
|
||||
|
||||
datahandler("End", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_End] = value;
|
||||
});
|
||||
|
||||
datahandler("Suspend", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Suspend] = value;
|
||||
});
|
||||
|
||||
datahandler("Continue", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Continue] = value;
|
||||
});
|
||||
|
||||
datahandler("Change", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Change] = value;
|
||||
});
|
||||
|
||||
datahandler("Damage", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Damage] = value;
|
||||
});
|
||||
|
||||
datahandler("Tick", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Tick] = value;
|
||||
});
|
||||
|
||||
datahandler("GlobalDamage", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_GlobalDamage] = value;
|
||||
});
|
||||
|
||||
datahandler("RetrofitPre", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Retrofit_Pre] = value;
|
||||
});
|
||||
|
||||
datahandler("RetrofitPost", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Retrofit_Post] = value;
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
EffectDef::EffectDef() : id(-1), valueCount(0), hooks() {
|
||||
}
|
||||
|
||||
void EffectDef::setHook(EffectHook hook, const std::string& ref) {
|
||||
std::vector<std::string> args;
|
||||
split(ref, args, "::");
|
||||
|
||||
if(args.size() != 2) {
|
||||
error("(Effect %s): Error: Invalid script function reference.", name.c_str());
|
||||
hooks[hook] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//Find the module the function is in
|
||||
scripts::Module* modu = devices.scripts.server->getModule(args[0].c_str());
|
||||
if(!modu) {
|
||||
error("(Effect %s): Error: Invalid script module '%s'.", name.c_str(), args[0].c_str());
|
||||
hooks[hook] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//Build declaration for function
|
||||
std::string def;
|
||||
def = callback_retval[hook];
|
||||
def += " "+args[1]+"(";
|
||||
def += callback_basedecl[hook];
|
||||
for(unsigned i = 0; i < valueCount; ++i) {
|
||||
def += ",double";
|
||||
}
|
||||
def += ")";
|
||||
|
||||
//Find the function
|
||||
asIScriptFunction* func = modu->getFunction(def.c_str());
|
||||
if(!func) {
|
||||
error("(Effect %s): Error: Could not find script function '%s'.", name.c_str(), def.c_str());
|
||||
hooks[hook] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
hooks[hook] = func;
|
||||
}
|
||||
|
||||
Effect::Effect() : type(0) {
|
||||
}
|
||||
|
||||
Effect::Effect(const EffectDef* Type) : type(Type) {
|
||||
}
|
||||
|
||||
void Effect::call(EffectHook hook, EffectEvent& event) const {
|
||||
if(!type)
|
||||
return;
|
||||
auto* func = type->hooks[hook];
|
||||
if(!func)
|
||||
return;
|
||||
scripts::Call cl = devices.scripts.server->call(func);
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
|
||||
DamageEventStatus Effect::damage(DamageEvent& event, const vec2u& position) const {
|
||||
if(!type || type->hooks[EH_Damage] == nullptr)
|
||||
return DE_Continue;
|
||||
scripts::Call cl = devices.scripts.server->call(type->hooks[EH_Damage]);
|
||||
unsigned status = DE_Continue;
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
cl.push((void*)&position);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call(status);
|
||||
}
|
||||
return (DamageEventStatus)status;
|
||||
}
|
||||
|
||||
void Effect::ownerChange(EffectEvent& event, Empire* prevEmpire, Empire* newEmpire) const {
|
||||
if(!type || type->hooks[EH_Owner_Change] == nullptr)
|
||||
return;
|
||||
scripts::Call cl = devices.scripts.server->call(type->hooks[EH_Owner_Change]);
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
cl.push((void*)prevEmpire);
|
||||
cl.push((void*)newEmpire);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
|
||||
DamageEventStatus Effect::globalDamage(DamageEvent& event, vec2u& position, vec2d& endPoint) const {
|
||||
if(!type || type->hooks[EH_GlobalDamage] == nullptr)
|
||||
return DE_Continue;
|
||||
scripts::Call cl = devices.scripts.server->call(type->hooks[EH_GlobalDamage]);
|
||||
unsigned status = DE_Continue;
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
cl.push((void*)&position);
|
||||
cl.push((void*)&endPoint);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call(status);
|
||||
}
|
||||
return (DamageEventStatus)status;
|
||||
}
|
||||
|
||||
void Effect::writeData(net::Message& msg) const {
|
||||
if(type) {
|
||||
msg.write1();
|
||||
msg.writeLimited(type->id, (unsigned)effectDefinitions.size()-1);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
for(size_t i = 0; i < EFFECT_MAX_VALUES; ++i)
|
||||
msg << (float)values[i];
|
||||
}
|
||||
|
||||
void Effect::readData(net::Message& msg) {
|
||||
if(msg.readBit()) {
|
||||
unsigned typeId = msg.readLimited((unsigned)effectDefinitions.size()-1);
|
||||
type = getEffectDefinition(typeId);
|
||||
}
|
||||
else {
|
||||
type = nullptr;
|
||||
}
|
||||
for(size_t i = 0; i < EFFECT_MAX_VALUES; ++i) {
|
||||
float v;
|
||||
msg >> v;
|
||||
values[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect()
|
||||
: remaining(0.0) {
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect(const TimedEffect& other)
|
||||
: remaining(other.remaining) {
|
||||
event = other.event;
|
||||
effect = other.effect;
|
||||
}
|
||||
|
||||
TimedEffect::~TimedEffect() {
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect(const EffectDef* Type, double Time)
|
||||
: effect(Type), remaining(Time) {
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect(const Effect& Effect, double Time)
|
||||
: remaining(Time) {
|
||||
effect = Effect;
|
||||
}
|
||||
|
||||
void TimedEffect::call(EffectHook hook) {
|
||||
effect.call(hook, event);
|
||||
}
|
||||
|
||||
void TimedEffect::tick(double time) {
|
||||
event.time = time;
|
||||
effect.call(EH_Tick, event);
|
||||
|
||||
if(event.status != ES_Suspended) {
|
||||
remaining -= time;
|
||||
|
||||
if(remaining <= 0.0)
|
||||
event.status = ES_Ended;
|
||||
}
|
||||
}
|
||||
|
||||
EffectEvent::EffectEvent()
|
||||
: time(0.0), efficiency(1.f), partiality(1.f), source(-1), destination(-1), obj(0),
|
||||
target(0), status(ES_Active), custom1(0.f), custom2(0.f) {
|
||||
}
|
||||
|
||||
EffectEvent::~EffectEvent() {
|
||||
}
|
||||
|
||||
DamageEvent::DamageEvent()
|
||||
: damage(0.0), pierce(0.f), partiality(1.f), flags(0), source(-1), destination(-1), obj(0),
|
||||
target(0), custom1(0), custom2(0), spillable(true) {
|
||||
}
|
||||
|
||||
DamageEvent::~DamageEvent() {
|
||||
}
|
||||
|
||||
const EffectDef* getEffectDefinition(const std::string& name) {
|
||||
auto it = effectIndices.find(name);
|
||||
if(it == effectIndices.end())
|
||||
return 0;
|
||||
return effectDefinitions[it->second];
|
||||
}
|
||||
|
||||
const EffectDef* getEffectDefinition(int index) {
|
||||
if(index < 0 || index >= (int)effectDefinitions.size())
|
||||
return 0;
|
||||
return effectDefinitions[index];
|
||||
}
|
||||
|
||||
unsigned getEffectDefinitionCount() {
|
||||
return effectDefinitions.size();
|
||||
}
|
||||
|
||||
void enumerateEffectDefinitions(void (*cb)(const std::string&,int)) {
|
||||
foreach(it, effectDefinitions)
|
||||
cb((*it)->name, (*it)->id);
|
||||
}
|
||||
|
||||
void bindEffectHooks() {
|
||||
foreach(it, effectDefinitions) {
|
||||
foreach(h, (*it)->hookDefinitions) {
|
||||
(*it)->setHook(h->first, h->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
#include "util/formula.h"
|
||||
#include "compat/misc.h"
|
||||
#include "obj/object.h"
|
||||
#include "util/refcount.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef EFFECT_MAX_VALUES
|
||||
#define EFFECT_MAX_VALUES 6
|
||||
#endif
|
||||
|
||||
class asIScriptFunction;
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
enum EffectHook {
|
||||
EH_Start,
|
||||
EH_Tick,
|
||||
EH_Suspend,
|
||||
EH_Continue,
|
||||
EH_Destroy,
|
||||
EH_End,
|
||||
EH_Change,
|
||||
EH_Damage,
|
||||
EH_GlobalDamage,
|
||||
EH_Retrofit_Pre,
|
||||
EH_Retrofit_Post,
|
||||
EH_Owner_Change,
|
||||
EH_Save,
|
||||
EH_Load,
|
||||
EH_COUNT
|
||||
};
|
||||
|
||||
class EffectDef {
|
||||
public:
|
||||
std::string name;
|
||||
int id;
|
||||
|
||||
umap<std::string, unsigned> valueNames;
|
||||
unsigned valueCount;
|
||||
struct ValueDesc {
|
||||
Formula* defaultValue;
|
||||
|
||||
ValueDesc() : defaultValue(nullptr) {}
|
||||
};
|
||||
std::vector<ValueDesc> values;
|
||||
|
||||
std::map<EffectHook, std::string> hookDefinitions;
|
||||
asIScriptFunction* hooks[EH_COUNT];
|
||||
void setHook(EffectHook hook, const std::string& ref);
|
||||
|
||||
EffectDef();
|
||||
};
|
||||
|
||||
enum EffectStatus {
|
||||
ES_Active,
|
||||
ES_Suspended,
|
||||
ES_Ended,
|
||||
};
|
||||
|
||||
class Subsystem;
|
||||
class EffectEvent {
|
||||
public:
|
||||
vec3d impact;
|
||||
vec2d direction;
|
||||
heldPointer<Object> obj;
|
||||
heldPointer<Object> target;
|
||||
double time;
|
||||
float efficiency;
|
||||
float partiality;
|
||||
int source;
|
||||
int destination;
|
||||
float custom1;
|
||||
float custom2;
|
||||
EffectStatus status;
|
||||
|
||||
EffectEvent();
|
||||
~EffectEvent();
|
||||
};
|
||||
|
||||
enum DamageEventStatus {
|
||||
DE_Continue,
|
||||
DE_SkipHex,
|
||||
DE_EndDamage,
|
||||
};
|
||||
|
||||
class DamageEvent {
|
||||
public:
|
||||
vec3d impact;
|
||||
double damage;
|
||||
float pierce;
|
||||
float partiality;
|
||||
float custom1;
|
||||
float custom2;
|
||||
unsigned flags;
|
||||
bool spillable;
|
||||
|
||||
int source;
|
||||
int destination;
|
||||
heldPointer<Object> obj;
|
||||
heldPointer<Object> target;
|
||||
|
||||
DamageEvent();
|
||||
~DamageEvent();
|
||||
};
|
||||
|
||||
class Effect {
|
||||
public:
|
||||
const EffectDef* type;
|
||||
double values[EFFECT_MAX_VALUES];
|
||||
|
||||
Effect();
|
||||
Effect(const EffectDef* Type);
|
||||
|
||||
void call(EffectHook hook, EffectEvent& event) const;
|
||||
DamageEventStatus damage(DamageEvent& event, const vec2u& position) const;
|
||||
DamageEventStatus globalDamage(DamageEvent& event, vec2u& position, vec2d& endPoint) const;
|
||||
void ownerChange(EffectEvent& event, Empire* prevEmpire, Empire* newEmpire) const;
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
void readData(net::Message& msg);
|
||||
};
|
||||
|
||||
class TimedEffect {
|
||||
public:
|
||||
EffectEvent event;
|
||||
Effect effect;
|
||||
double remaining;
|
||||
|
||||
TimedEffect();
|
||||
TimedEffect(const TimedEffect& other);
|
||||
TimedEffect(const EffectDef* Type, double Time);
|
||||
TimedEffect(const Effect& Effect, double Time);
|
||||
~TimedEffect();
|
||||
void call(EffectHook hook);
|
||||
void tick(double time);
|
||||
};
|
||||
|
||||
void loadEffectDefinitions(const std::string& filename);
|
||||
const EffectDef* getEffectDefinition(const std::string& name);
|
||||
const EffectDef* getEffectDefinition(int index);
|
||||
unsigned getEffectDefinitionCount();
|
||||
void enumerateEffectDefinitions(void (*cb)(const std::string&,int));
|
||||
void bindEffectHooks();
|
||||
void clearEffectDefinitions();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,261 @@
|
||||
#pragma once
|
||||
#include "util/formula.h"
|
||||
#include "scripts/manager.h"
|
||||
#include "design/effect.h"
|
||||
#include "render/render_state.h"
|
||||
#include "threads.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
class Object;
|
||||
class Effector;
|
||||
class Design;
|
||||
struct EffectorTarget;
|
||||
class HullDef;
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
namespace resource {
|
||||
class Sound;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
struct ParticleSystemDesc;
|
||||
};
|
||||
|
||||
typedef Object*(*nativeTargetAlgorithm)(const Effector*, Object*, EffectorTarget* targ);
|
||||
struct TargetAlgorithm {
|
||||
union {
|
||||
nativeTargetAlgorithm native;
|
||||
asIScriptFunction* script;
|
||||
};
|
||||
bool isNative;
|
||||
};
|
||||
|
||||
typedef double(*nativeTargetWeighter)(const Effector*, Object*, Object*, void*);
|
||||
struct TargetWeighter {
|
||||
union {
|
||||
nativeTargetWeighter native;
|
||||
asIScriptFunction* script;
|
||||
};
|
||||
bool isNative;
|
||||
void* arg;
|
||||
|
||||
TargetWeighter() {
|
||||
script = 0;
|
||||
isNative = false;
|
||||
arg = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct EffectorTarget;
|
||||
|
||||
enum EffectorActivationType {
|
||||
EAT_Inactive,
|
||||
EAT_Activate,
|
||||
EAT_Repeat
|
||||
};
|
||||
|
||||
typedef EffectorActivationType(*nativeEffectorActivation)(const Effector*, Object*, EffectorTarget& targ, double&, double*, double*);
|
||||
struct EffectorActivation {
|
||||
union {
|
||||
nativeEffectorActivation native;
|
||||
asIScriptFunction* script;
|
||||
};
|
||||
bool isNative;
|
||||
};
|
||||
|
||||
enum EffectorPhysicalType {
|
||||
EPT_Instant,
|
||||
EPT_Projectile,
|
||||
EPT_Missile,
|
||||
EPT_AimedMissile,
|
||||
EPT_Beam,
|
||||
};
|
||||
|
||||
enum EffectorGraphicType {
|
||||
EGT_Sprite,
|
||||
EGT_Beam,
|
||||
EGT_Line
|
||||
};
|
||||
|
||||
enum EffectorEfficiencyMode {
|
||||
EEM_Normal,
|
||||
EEM_Reload_Partial,
|
||||
EEM_Duration_Partial,
|
||||
EEM_Reload,
|
||||
EEM_Duration,
|
||||
};
|
||||
|
||||
struct EffectorSkin {
|
||||
EffectorGraphicType graphicType;
|
||||
double graphicSize;
|
||||
double length;
|
||||
std::string trailMatID;
|
||||
const render::RenderState* trailMat;
|
||||
Color trailStart, trailEnd, color;
|
||||
|
||||
std::string def_material;
|
||||
const render::RenderState* material;
|
||||
|
||||
std::string def_impact;
|
||||
const scene::ParticleSystemDesc* impact;
|
||||
|
||||
float fire_pitch_variance;
|
||||
std::vector<std::string> fire_sound_names;
|
||||
std::vector<const resource::Sound*> fire_sounds;
|
||||
|
||||
std::string def_impact_sound;
|
||||
const resource::Sound* impact_sound;
|
||||
|
||||
EffectorSkin();
|
||||
};
|
||||
|
||||
class EffectorDef {
|
||||
public:
|
||||
unsigned index;
|
||||
std::string name;
|
||||
std::unordered_map<std::string, unsigned> valueNames;
|
||||
|
||||
Formula* range, *lifetime, *tracking, *speed, *spread, *capTarget;
|
||||
Formula* fireArc, *targetTolerance, *fireTolerance;
|
||||
|
||||
std::string def_algorithm;
|
||||
std::string def_activation;
|
||||
std::string def_canTarget;
|
||||
std::string def_autoTarget;
|
||||
std::string def_onTrigger;
|
||||
|
||||
TargetAlgorithm algorithm;
|
||||
EffectorActivation activation;
|
||||
asIScriptFunction* onTrigger;
|
||||
|
||||
std::vector<TargetWeighter> canTargetWeighters;
|
||||
Formula* canTarget;
|
||||
std::vector<TargetWeighter> autoTargetWeighters;
|
||||
Formula* autoTarget;
|
||||
|
||||
//Whether the projectile should hit a target according to physical behaviors, or only the chosen target
|
||||
bool physicalImpact;
|
||||
bool passthroughInvalid;
|
||||
|
||||
bool pierces;
|
||||
float recoverTime;
|
||||
|
||||
EffectorEfficiencyMode efficiencyMode;
|
||||
EffectorPhysicalType physicalType;
|
||||
double physicalSize;
|
||||
|
||||
std::unordered_map<std::string, unsigned> skinNames;
|
||||
|
||||
unsigned valueCount;
|
||||
struct ValueDesc {
|
||||
Formula* defaultValue;
|
||||
|
||||
ValueDesc() : defaultValue(nullptr) {}
|
||||
};
|
||||
std::vector<ValueDesc> values;
|
||||
|
||||
unsigned stateCount;
|
||||
std::vector<Formula*> arguments;
|
||||
std::vector<Formula*> triggerArguments;
|
||||
|
||||
const EffectDef* effect;
|
||||
std::vector<Formula*> effectValues;
|
||||
|
||||
std::vector<EffectorSkin> skins;
|
||||
|
||||
void triggerGraphics(Object* obj, EffectorTarget& targ, const Effector* effector, double* time = 0, vec2d* direction = 0, float efficiency = 1.f, double tOffset = 0) const;
|
||||
EffectorDef();
|
||||
};
|
||||
|
||||
enum TargetFlags {
|
||||
TF_Target = 0,
|
||||
TF_Preference = 0x1,
|
||||
TF_Group = 0x2,
|
||||
TF_Firing = 0x4,
|
||||
TF_Retarget = 0x8,
|
||||
TF_TrackingProgress = 0x10,
|
||||
TF_ClearTracking = 0x20,
|
||||
TF_WithinFireTolerance = 0x40,
|
||||
};
|
||||
|
||||
struct EffectorTarget {
|
||||
Object* target;
|
||||
unsigned flags;
|
||||
unsigned char hits;
|
||||
vec3d tracking;
|
||||
};
|
||||
|
||||
class Effector {
|
||||
mutable threads::atomic_int refs;
|
||||
public:
|
||||
const Design* inDesign;
|
||||
unsigned subsysIndex;
|
||||
unsigned effectorIndex;
|
||||
mutable unsigned effectorId;
|
||||
|
||||
unsigned skinIndex;
|
||||
|
||||
vec3d relativePosition;
|
||||
vec3d turretAngle;
|
||||
double relativeSize;
|
||||
bool enabled;
|
||||
|
||||
const EffectorDef& type;
|
||||
double* values;
|
||||
double range, lifetime, tracking, speed, spread;
|
||||
double fireArc, targetTolerance, fireTolerance;
|
||||
unsigned capTarget;
|
||||
unsigned stateOffset;
|
||||
Effect effect;
|
||||
|
||||
Effector(const EffectorDef& def);
|
||||
~Effector();
|
||||
void initValues();
|
||||
|
||||
void load(SaveFile& file);
|
||||
void save(SaveFile& file) const;
|
||||
|
||||
static const Effector* receiveUpdate(net::Message& msg);
|
||||
void sendUpdate(net::Message& msg) const;
|
||||
void sendDestruction(net::Message& msg) const;
|
||||
|
||||
void grab() const;
|
||||
void drop() const;
|
||||
|
||||
bool isInRange(Object* obj, Object* target, bool considerArc = true) const;
|
||||
bool canTarget(Object* obj, Object* target) const;
|
||||
bool autoTarget(Object* obj, Object* target) const;
|
||||
double getTargetWeight(Object* obj, Object* target) const;
|
||||
|
||||
void trigger(Object* obj, EffectorTarget& targ, float efficiency, double tOffset = 0) const;
|
||||
void triggerEffect(Object* obj, Object* target, const vec3d& impactOffset, float efficiency, float partiality, double delay = 0.0) const;
|
||||
void update(Object* obj, double time, double* states, EffectorTarget& target, float efficiency, bool holdFire = false) const;
|
||||
|
||||
void setRelativePosition(vec2u hex, const HullDef* hull, vec3d direction);
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
Effector(net::Message& msg);
|
||||
};
|
||||
|
||||
void clearEffectorDefinitions();
|
||||
void loadEffectorDefinitions(const std::string& filename);
|
||||
unsigned getEffectorDefinitionCount();
|
||||
const EffectorDef* getEffectorDefinition(const std::string& name);
|
||||
const EffectorDef* getEffectorDefinition(unsigned index);
|
||||
void bindEffectorHooks(bool shadow = false);
|
||||
void bindEffectorResources();
|
||||
|
||||
extern std::unordered_map<unsigned, const Effector*> effectorMap;
|
||||
void registerEffector(const Effector* eff);
|
||||
void unregisterEffector(const Effector* eff);
|
||||
const Effector* getEffector(unsigned id);
|
||||
void clearEffectors();
|
||||
|
||||
void saveEffectors(SaveFile& file);
|
||||
void loadEffectors(SaveFile& file);
|
||||
void postLoadEffectors();
|
||||
@@ -0,0 +1,305 @@
|
||||
#include "effector_functions.h"
|
||||
#include "obj/object.h"
|
||||
#include "util/random.h"
|
||||
#include "threads.h"
|
||||
#include "empire.h"
|
||||
#include "obj/blueprint.h"
|
||||
#include "assert.h"
|
||||
#include "main/logging.h"
|
||||
|
||||
#ifndef TARGET_SINGLE_TARGET_DEPTH
|
||||
#define TARGET_SINGLE_TARGET_DEPTH 3
|
||||
#endif
|
||||
|
||||
//* Algorithms
|
||||
struct SingleTargetFinder {
|
||||
const Effector* eff;
|
||||
EffectorTarget* efftarg;
|
||||
Object* obj;
|
||||
Object* best;
|
||||
double maxWeight;
|
||||
|
||||
bool result(Object* targ) {
|
||||
//Ignore ourselves
|
||||
if(targ == obj)
|
||||
return false;
|
||||
|
||||
//Ignore objects out of range
|
||||
if(!eff->isInRange(obj, targ))
|
||||
return false;
|
||||
|
||||
double weight = eff->getTargetWeight(obj, targ);
|
||||
|
||||
//Weights under zero mean to ignore this object
|
||||
if(weight <= 0.0)
|
||||
return false;
|
||||
|
||||
//Modify weight with targeting preference
|
||||
if(efftarg->target) {
|
||||
if(efftarg->flags & TF_Group) {
|
||||
if(efftarg->target->group != targ->group)
|
||||
weight /= 10.0;
|
||||
} else if(efftarg->flags & TF_Preference) {
|
||||
if(efftarg->target != targ)
|
||||
weight /= 10.0;
|
||||
}
|
||||
}
|
||||
|
||||
//Weights of at least one mean to immediately target this
|
||||
if(weight >= 1.0) {
|
||||
best = targ;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Store the object with the highest weight, so
|
||||
//we have something to target if nothing is
|
||||
//randomed.
|
||||
if(weight > maxWeight) {
|
||||
best = targ;
|
||||
maxWeight = weight;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static Object* SingleTarget(const Effector* eff, Object* obj, EffectorTarget* efftarg) {
|
||||
SingleTargetFinder finder;
|
||||
finder.efftarg = efftarg;
|
||||
finder.eff = eff;
|
||||
finder.obj = obj;
|
||||
finder.best = 0;
|
||||
finder.maxWeight = 0;
|
||||
|
||||
obj->findTargets(finder, TARGET_SINGLE_TARGET_DEPTH, Object::RANDOMIZE_TARGETS);
|
||||
return finder.best;
|
||||
}
|
||||
|
||||
//* Weighters
|
||||
static double NotOurs(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj->owner == targ->owner)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double Ours(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj->owner != targ->owner)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double isEnemy(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ->owner && obj->owner != targ->owner && targ->owner->valid()) {
|
||||
if(obj->owner && (obj->owner->hostileMask & targ->owner->mask) != 0)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static double isDamageable(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ->getFlag(objNoDamage))
|
||||
return 0.0;
|
||||
else
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double isAttackable(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj->owner == targ->owner || targ->owner == nullptr || !targ->owner->valid())
|
||||
return 1.0;
|
||||
return isEnemy(eff, obj, targ, arg);
|
||||
}
|
||||
|
||||
static double hasDamagedBlueprint(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj != nullptr && obj->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)obj) + obj->type->blueprintOffset);
|
||||
return blueprint->currentHP < blueprint->design->totalHP - 0.0001 ? 1.0 : 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double isType(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if((void*)targ->type != arg)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double Distance(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
return obj->position.distanceTo(targ->position);
|
||||
}
|
||||
|
||||
double hasTag(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ != nullptr && targ->isValid() && targ->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)targ) + targ->type->blueprintOffset);
|
||||
if(!blueprint || !blueprint->design)
|
||||
return 0.0;
|
||||
return blueprint->design->hasTag((int)(size_t)arg) ? 1.0 : 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static double targRadius(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ == nullptr)
|
||||
return 1.0;
|
||||
return targ->radius;
|
||||
}
|
||||
|
||||
static double originRadius(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj == nullptr)
|
||||
return 1.0;
|
||||
return obj->radius;
|
||||
}
|
||||
|
||||
static double sizeDifference(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj == nullptr || targ == nullptr)
|
||||
return 1.0;
|
||||
if(obj->radius > targ->radius)
|
||||
return obj->radius / targ->radius;
|
||||
else
|
||||
return targ->radius / obj->radius;
|
||||
}
|
||||
|
||||
//* Activation
|
||||
static EffectorActivationType Always(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
return EAT_Activate;
|
||||
}
|
||||
|
||||
static EffectorActivationType Timed(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance)) {
|
||||
if(states[0] > 0.0)
|
||||
states[0] = std::max(states[0] - time, 0.0);
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
static EffectorActivationType VariableTimed(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance)) {
|
||||
if(states[0] > 0.0)
|
||||
states[0] = std::max(states[0] - time, 0.0);
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
if(targ.target)
|
||||
states[0] = args[0] * randomd(1.0 - args[1], 1.0 + args[1]);
|
||||
else
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
static EffectorActivationType StaggeredTimed(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance)) {
|
||||
double stagger = args[1] * args[0] * (-eff->relativePosition.x + 1.0) * 0.5;
|
||||
states[0] = std::max(states[0] - time, stagger);
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
//Fires arg[1] shots at arg[0] second intervals, then reloads over arg[2] seconds
|
||||
static EffectorActivationType Magazine(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance) || states[1] <= 0.0) {
|
||||
if(states[0] > 0.0) {
|
||||
states[0] = states[0] - time;
|
||||
if(states[0] <= 0.0) {
|
||||
states[1] = args[1];
|
||||
states[0] = 0.0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
states[1] = args[1];
|
||||
}
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
states[1] -= 1.0;
|
||||
if(states[1] <= 0.000001)
|
||||
states[0] = args[2];
|
||||
else
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
//Build maps
|
||||
decltype(TargetAlgorithms) makeAlgoList() {
|
||||
decltype(TargetAlgorithms) list;
|
||||
|
||||
list["SingleTarget"] = SingleTarget;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
decltype(TargetWeighters) makeWeighterList() {
|
||||
decltype(TargetWeighters) list;
|
||||
|
||||
list["NotOurs"] = NotOurs;
|
||||
list["Ours"] = Ours;
|
||||
list["isEnemy"] = isEnemy;
|
||||
list["isAttackable"] = isAttackable;
|
||||
list["hasDamagedBlueprint"] = hasDamagedBlueprint;
|
||||
list["isDamageable"] = isDamageable;
|
||||
list["targRadius"] = targRadius;
|
||||
list["originRadius"] = originRadius;
|
||||
list["sizeDifference"] = sizeDifference;
|
||||
list["Distance"] = Distance;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
decltype(EffectorActivation) makeActivationList() {
|
||||
decltype(EffectorActivation) list;
|
||||
|
||||
auto addFunc = [&](std::string name, nativeEffectorActivation f, unsigned stateCount, unsigned argCount)
|
||||
{
|
||||
auto& cb = list[name];
|
||||
cb.func = f;
|
||||
cb.stateCount = stateCount;
|
||||
cb.argCount = argCount;
|
||||
};
|
||||
|
||||
addFunc("Always", Always, 0, 0);
|
||||
addFunc("Timed", Timed, 1, 1);
|
||||
addFunc("VariableTimed", VariableTimed, 1, 2);
|
||||
addFunc("StaggeredTimed", StaggeredTimed, 1, 2);
|
||||
addFunc("Magazine", Magazine, 2, 3);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
umap<std::string, nativeTargetAlgorithm> TargetAlgorithms = makeAlgoList();
|
||||
umap<std::string, nativeTargetWeighter> TargetWeighters = makeWeighterList();
|
||||
umap<std::string, ActivationCB> EffectorActivation = makeActivationList();
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "design/effector.h"
|
||||
#include "compat/misc.h"
|
||||
|
||||
extern umap<std::string, nativeTargetAlgorithm> TargetAlgorithms;
|
||||
extern umap<std::string, nativeTargetWeighter> TargetWeighters;
|
||||
|
||||
struct ActivationCB {
|
||||
nativeEffectorActivation func;
|
||||
unsigned stateCount, argCount;
|
||||
};
|
||||
|
||||
extern umap<std::string, ActivationCB> EffectorActivation;
|
||||
|
||||
double isType(const Effector* eff, Object* obj, Object* targ, void* arg);
|
||||
double hasTag(const Effector* eff, Object* obj, Object* targ, void* arg);
|
||||
@@ -0,0 +1,912 @@
|
||||
#include "design/hull.h"
|
||||
#include "str_util.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/references.h"
|
||||
#include "compat/misc.h"
|
||||
#include "design/design.h"
|
||||
#include "design/subsystem.h"
|
||||
#include "design/subsystem.h"
|
||||
#include "threads.h"
|
||||
#include "vec3.h"
|
||||
#include "line3d.h"
|
||||
#include <algorithm>
|
||||
#include "files.h"
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static std::vector<Shipset*> shipsets;
|
||||
static umap<std::string, unsigned> shipsetIndices;
|
||||
|
||||
static std::vector<HullDef*> hullDefinitions;
|
||||
static umap<std::string, unsigned> hullIndices;
|
||||
|
||||
static unsigned computedHulls = 0;
|
||||
void computeHulls(unsigned amount) {
|
||||
unsigned offset = randomi(0, hullDefinitions.size()-1);
|
||||
for(unsigned n = 0, cnt = hullDefinitions.size(); n < cnt; ++n) {
|
||||
if(hullDefinitions[(offset+n)%cnt]->calculateImpacts()) {
|
||||
++computedHulls;
|
||||
if(computedHulls >= hullDefinitions.size())
|
||||
return;
|
||||
--amount;
|
||||
if(amount == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isFinishedComputingHulls() {
|
||||
return computedHulls >= hullDefinitions.size();
|
||||
}
|
||||
|
||||
unsigned getHullCount() {
|
||||
return (unsigned)hullDefinitions.size();
|
||||
}
|
||||
|
||||
const HullDef* getHullDefinition(unsigned id) {
|
||||
if(id >= hullDefinitions.size())
|
||||
return 0;
|
||||
return hullDefinitions[id];
|
||||
}
|
||||
|
||||
const HullDef* getHullDefinition(const std::string& name) {
|
||||
auto it = hullIndices.find(name);
|
||||
if(it == hullIndices.end())
|
||||
return 0;
|
||||
return hullDefinitions[it->second];
|
||||
}
|
||||
|
||||
HullDef::HullDef()
|
||||
: id(-1), background(0), material(0), mesh(0),
|
||||
iconSheet(0), iconIndex(0), gridSize(16, 8), backgroundScale(1.0), modelScale(1.0),
|
||||
active(gridSize), exterior(gridSize), activeCount(0), exteriorCount(0),
|
||||
minSize(1.0), maxSize(-1.0), baseHull(this), shape(nullptr), special(false)
|
||||
{
|
||||
active.clear(false);
|
||||
exterior.clear(false);
|
||||
}
|
||||
|
||||
HullDef::HullDef(const HullDef& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
void clearHullDefinitions() {
|
||||
foreach(it, hullDefinitions) {
|
||||
auto* hull = *it;
|
||||
hull->baseHull = nullptr;
|
||||
hull->drop();
|
||||
}
|
||||
hullDefinitions.clear();
|
||||
hullIndices.clear();
|
||||
computedHulls = 0;
|
||||
}
|
||||
|
||||
void loadHullDefinitions(const std::string& filename) {
|
||||
std::vector<HullDef*> defs;
|
||||
readHullDefinitions(filename, defs);
|
||||
|
||||
foreach(it, defs) {
|
||||
(*it)->calculateDist();
|
||||
(*it)->calculateExterior();
|
||||
(*it)->id = (unsigned)hullDefinitions.size();
|
||||
hullDefinitions.push_back(*it);
|
||||
hullIndices[(*it)->ident] = (*it)->id;
|
||||
}
|
||||
}
|
||||
|
||||
bool meshIntersect(const Mesh& mesh, const line3dd& line, vec3d& output) {
|
||||
double closestDist = 1e100;
|
||||
vec3d closestIntersect;
|
||||
|
||||
for(unsigned i = 0, cnt = mesh.faces.size(); i < cnt; ++i) {
|
||||
const Mesh::Face& face = mesh.faces[i];
|
||||
|
||||
vec3d v1 = vec3d(mesh.vertices[face.a].position);
|
||||
vec3d v2 = vec3d(mesh.vertices[face.b].position);
|
||||
vec3d v3 = vec3d(mesh.vertices[face.c].position);
|
||||
|
||||
vec3d point;
|
||||
if(line.intersectTriangle(v1, v2, v3, point)) {
|
||||
double dist = line.start.distanceToSQ(point);
|
||||
if(dist < closestDist) {
|
||||
closestIntersect = point;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!closestIntersect.zero()) {
|
||||
output = closestIntersect;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HullDef::calculateImpacts() {
|
||||
if(!impacts.empty())
|
||||
return false;
|
||||
if(!this->mesh)
|
||||
return true;
|
||||
const Mesh& mesh = this->mesh->getMesh();
|
||||
if(mesh.faces.empty())
|
||||
return false;
|
||||
|
||||
//double t = devices.driver->getAccurateTime();
|
||||
|
||||
std::vector<vec3d> lineImpacts;
|
||||
lineImpacts.resize(8 * 64);
|
||||
impacts.resize(8 * 64);
|
||||
|
||||
//Trace the impact lines
|
||||
for(unsigned n = 0; n < 8; ++n) {
|
||||
double theta = (double)n / 8.0 * pi;
|
||||
for(unsigned i = 0; i < 64; ++i) {
|
||||
double phi = (double)i / 64.0 * twopi;
|
||||
|
||||
double st = sin(theta);
|
||||
vec3d start = vec3d(
|
||||
st * cos(phi),
|
||||
cos(theta),
|
||||
st * sin(phi)
|
||||
);
|
||||
|
||||
line3dd ray(start, vec3d(0.0));
|
||||
|
||||
vec3d point;
|
||||
if(meshIntersect(mesh, ray, point))
|
||||
lineImpacts[n*64 + i] = point;
|
||||
else
|
||||
lineImpacts[n*64 + i] = vec3d();
|
||||
}
|
||||
}
|
||||
|
||||
//Pre-calculate the closest impact points for angles
|
||||
for(unsigned n = 0; n < 8; ++n) {
|
||||
double theta = (double)n / 8.0 * pi;
|
||||
for(unsigned i = 0; i < 64; ++i) {
|
||||
double phi = (double)i / 64.0 * twopi;
|
||||
|
||||
double st = sin(theta);
|
||||
vec3d point = vec3d(
|
||||
st * cos(phi),
|
||||
cos(theta),
|
||||
st * sin(phi)
|
||||
);
|
||||
|
||||
double d = 1e80;
|
||||
vec3d imp;
|
||||
for(unsigned j = 0, jcnt = lineImpacts.size(); j < jcnt; ++j) {
|
||||
double dist = lineImpacts[j].distanceToSQ(point);
|
||||
if(dist < d) {
|
||||
d = dist;
|
||||
imp = lineImpacts[j];
|
||||
}
|
||||
}
|
||||
|
||||
impacts[n*64 + i] = imp;
|
||||
}
|
||||
}
|
||||
|
||||
//double tend = devices.driver->getAccurateTime();
|
||||
//print("Impacts %s - %gms", ident.c_str(), (tend-t)*1000.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
vec3d HullDef::getImpact(const vec3d& offset, double radius, bool constant) const {
|
||||
if(baseHull != nullptr && baseHull != this)
|
||||
return baseHull->getImpact(offset, radius);
|
||||
|
||||
if(impacts.size() == 0)
|
||||
return offset;
|
||||
|
||||
vec3d uoffset = offset / radius;
|
||||
|
||||
double theta = std::fmod(twopi + acos(uoffset.y / uoffset.getLength()), twopi);
|
||||
double phi = std::fmod(twopi + atan2(uoffset.z, uoffset.x), twopi);
|
||||
|
||||
unsigned row = (unsigned)floor(theta / pi * 8.0 + 0.5) % 8;
|
||||
unsigned index;
|
||||
if(constant)
|
||||
index = ((unsigned)floor(phi / twopi * 64.0 + 0.5)) % 64;
|
||||
else
|
||||
index = ((unsigned)floor(phi / twopi * 64.0 + 0.5) + randomi(-1,1)) % 64;
|
||||
|
||||
vec3d imp = impacts[row*64 + index];
|
||||
if(imp.zero())
|
||||
return offset;
|
||||
|
||||
return imp * radius;
|
||||
}
|
||||
|
||||
vec3d HullDef::getClosestImpact(const vec3d& offset) const {
|
||||
if(baseHull != nullptr && baseHull != this)
|
||||
return baseHull->getClosestImpact(offset);
|
||||
|
||||
if(impacts.size() == 0)
|
||||
return offset;
|
||||
|
||||
double d = 1e80;
|
||||
vec3d imp;
|
||||
for(unsigned j = 0, jcnt = impacts.size(); j < jcnt; ++j) {
|
||||
double dist = impacts[j].distanceToSQ(offset);
|
||||
if(dist < d) {
|
||||
d = dist;
|
||||
imp = impacts[j];
|
||||
}
|
||||
}
|
||||
|
||||
return imp;
|
||||
}
|
||||
|
||||
void HullDef::calculateExterior() {
|
||||
//Top & Bottom Lines
|
||||
for(unsigned i = 0, cnt = gridSize.x; i < cnt; ++i) {
|
||||
if(i % 2 == 0) {
|
||||
calculateExterior(vec2u(i, 0), HEX_DownLeft);
|
||||
calculateExterior(vec2u(i, 0), HEX_Down);
|
||||
calculateExterior(vec2u(i, 0), HEX_DownRight);
|
||||
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_Up);
|
||||
}
|
||||
else {
|
||||
calculateExterior(vec2u(i, 0), HEX_Down);
|
||||
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_UpLeft);
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_Up);
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_UpRight);
|
||||
}
|
||||
}
|
||||
|
||||
//Right & Left Lines
|
||||
for(unsigned i = 0, cnt = gridSize.y; i < cnt; ++i) {
|
||||
calculateExterior(vec2u(0, i), HEX_DownRight);
|
||||
calculateExterior(vec2u(0, i), HEX_UpRight);
|
||||
|
||||
calculateExterior(vec2u(gridSize.x-1, i), HEX_DownLeft);
|
||||
calculateExterior(vec2u(gridSize.x-1, i), HEX_UpLeft);
|
||||
}
|
||||
}
|
||||
|
||||
void HullDef::calculateExterior(vec2u pos, unsigned direction) {
|
||||
unsigned mask = (1<<((direction+3)%6));
|
||||
do {
|
||||
if((mask & flagExteriorFaux) && !(exterior[pos] & (flagExteriorFaux | flagExteriorPass)) && active[pos])
|
||||
break;
|
||||
exterior[pos] |= mask;
|
||||
if(active[pos]) {
|
||||
if(exterior[pos] & flagExteriorPass)
|
||||
continue;
|
||||
if(exterior[pos] & flagExteriorFaux) {
|
||||
mask |= flagExteriorFaux;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(exterior.advance(pos, (HexGridAdjacency)direction));
|
||||
}
|
||||
|
||||
void HullDef::calculateDist() {
|
||||
if(shape == nullptr || shape->format != FMT_RGBA)
|
||||
return;
|
||||
|
||||
if(shapeMapped)
|
||||
return;
|
||||
|
||||
//First, make it binary. This is a distance map, so every active
|
||||
//pixel should be 0 and every inactive should be full distance.
|
||||
for(unsigned x = 0; x < shape->width; ++x) {
|
||||
for(unsigned y = 0; y < shape->height; ++y) {
|
||||
Color& col = shape->get_rgba(x, y);
|
||||
if(col.a != 0)
|
||||
col.a = 0;
|
||||
else
|
||||
col.a = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
//Calculate distances
|
||||
for(unsigned x = 0; x < shape->width; ++x) {
|
||||
for(unsigned y = 0; y < shape->height; ++y) {
|
||||
if(shape->get_rgba(x, y).a == 0)
|
||||
calculateDist(vec2u(x,y), 0);
|
||||
}
|
||||
}
|
||||
|
||||
saveImage(shape, shapeMap.c_str());
|
||||
}
|
||||
|
||||
void HullDef::calculateDist(vec2u pos, int dist) {
|
||||
assert(dist <= shape->get_rgba(pos.x, pos.y).a);
|
||||
shape->get_rgba(pos.x, pos.y).a = std::min(dist, 255);
|
||||
|
||||
int wmod = std::max(512 / (int)shape->width, 1);
|
||||
int hmod = std::max(512 / (int)shape->height, 1);
|
||||
|
||||
if(pos.x > 0) {
|
||||
if(shape->get_rgba(pos.x-1, pos.y).a > dist+wmod)
|
||||
calculateDist(vec2u(pos.x-1, pos.y), dist+wmod);
|
||||
}
|
||||
if(pos.x < shape->width-1) {
|
||||
if(shape->get_rgba(pos.x+1, pos.y).a > dist+wmod)
|
||||
calculateDist(vec2u(pos.x+1, pos.y), dist+wmod);
|
||||
}
|
||||
if(pos.y > 0) {
|
||||
if(shape->get_rgba(pos.x, pos.y-1).a > dist+hmod)
|
||||
calculateDist(vec2u(pos.x, pos.y-1), dist+hmod);
|
||||
}
|
||||
if(pos.y < shape->height-1) {
|
||||
if(shape->get_rgba(pos.x, pos.y+1).a > dist+hmod)
|
||||
calculateDist(vec2u(pos.x, pos.y+1), dist+hmod);
|
||||
}
|
||||
}
|
||||
|
||||
double HullDef::getMatchDistance(const vec2d& pos) const {
|
||||
if(shape == nullptr)
|
||||
return 128.0;
|
||||
return shape->getTexel(pos.x, pos.y).a;
|
||||
}
|
||||
|
||||
double HullDef::getMatchDistance(void* descPtr) const {
|
||||
auto& desc = *(Design::Descriptor*)descPtr;
|
||||
double dist = 0.0;
|
||||
|
||||
vec2u grid = desc.gridSize;
|
||||
unsigned count = grid.x * grid.y;
|
||||
uint8_t* cache;
|
||||
if(count < 2500)
|
||||
cache = (uint8_t*)alloca(count * sizeof(uint8_t));
|
||||
else
|
||||
cache = (uint8_t*)malloc(count * sizeof(uint8_t));
|
||||
memset(cache, 0, count * sizeof(uint8_t));
|
||||
|
||||
for(size_t i = 0, cnt = desc.systems.size(); i < cnt; ++i) {
|
||||
auto& sys = desc.systems[i];
|
||||
for(size_t j = 0, jcnt = sys.hexes.size(); j < jcnt; ++j) {
|
||||
vec2u hex = sys.hexes[j];
|
||||
if(hex.x < grid.x && hex.y < grid.y) {
|
||||
unsigned index = hex.y * grid.x + hex.x;
|
||||
cache[index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned x = 0; x < grid.x; ++x) {
|
||||
for(unsigned y = 0; y < grid.y; ++y) {
|
||||
vec2d pctPos = HexGrid<>::getEffectivePosition(vec2u(x, y));
|
||||
pctPos.x += 0.75 * 0.5;
|
||||
pctPos.y += 0.5;
|
||||
pctPos.x /= ((double)grid.x) * 0.75;
|
||||
pctPos.y /= (double)grid.y;
|
||||
|
||||
double d = getMatchDistance(pctPos);
|
||||
|
||||
unsigned index = y * grid.x + x;
|
||||
if(cache[index])
|
||||
dist += d*d;
|
||||
else
|
||||
dist += 0.5 * (255.0 - d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(count >= 2500)
|
||||
free(cache);
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
bool HullDef::checkConnected() {
|
||||
HexGrid<bool> connected(active.width, active.height);
|
||||
connected.clear(false);
|
||||
|
||||
bool found = false;
|
||||
for(unsigned x = 0; x < active.width && !found; ++x) {
|
||||
for(unsigned y = 0; y < active.height && !found; ++y) {
|
||||
if(active.get(x,y)) {
|
||||
fillConnected(connected, vec2u(x,y));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned x = 0; x < active.width; ++x) {
|
||||
for(unsigned y = 0; y < active.height; ++y) {
|
||||
if(active.get(x,y) && !connected.get(x,y))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HullDef::fillConnected(HexGrid<bool>& connected, vec2u pos) {
|
||||
connected[pos] = true;
|
||||
for(unsigned i = 0; i < 6; ++i) {
|
||||
vec2u hex = pos;
|
||||
if(active.advance(hex, (HexGridAdjacency)i)) {
|
||||
if(active[hex] && !connected[hex])
|
||||
fillConnected(connected, hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HullDef::isExterior(const vec2u& hex) const {
|
||||
if(!exterior.valid(hex))
|
||||
return false;
|
||||
return exterior.get(hex) & 0x00ffffff;
|
||||
}
|
||||
|
||||
bool HullDef::isExteriorInDirection(const vec2u& hex, unsigned dir) const {
|
||||
if(!exterior.valid(hex))
|
||||
return false;
|
||||
return exterior.get(hex) & (1<<dir);
|
||||
}
|
||||
|
||||
void readHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls) {
|
||||
HullDef* def = 0;
|
||||
int x = 0, y = 0;
|
||||
|
||||
DataHandler datahandler;
|
||||
datahandler.lineHandler([&](std::string& line) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
if(y >= def->gridSize.height) {
|
||||
error("Error: Too many rows for hull '%s'.", def->ident.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
x = 0;
|
||||
line = trim(line, "\r\n\t");
|
||||
unsigned cnt = (unsigned)line.size();
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
if(x >= def->gridSize.width) {
|
||||
error("Error: Too many characters on row for hull '%s'.", def->ident.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
bool& active = def->active.get(x, y);
|
||||
int& exterior = def->exterior.get(x, y);
|
||||
|
||||
switch(line[i]) {
|
||||
case '-':
|
||||
active = false;
|
||||
exterior = 0;
|
||||
++x;
|
||||
break;
|
||||
case 'X':
|
||||
case 'x':
|
||||
active = true;
|
||||
exterior = 0;
|
||||
++def->activeCount;
|
||||
++x;
|
||||
break;
|
||||
case '#':
|
||||
active = true;
|
||||
exterior = 0xff;
|
||||
++def->activeCount;
|
||||
++def->exteriorCount;
|
||||
++x;
|
||||
break;
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
error("Error: Invalid character '%c' on row for hull '%s'.", line[i], def->ident.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++y;
|
||||
});
|
||||
|
||||
datahandler("Hull", [&](std::string& value) {
|
||||
def = new HullDef();
|
||||
def->ident = value;
|
||||
def->name = "__"+def->ident+"__";
|
||||
hulls.push_back(def);
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
});
|
||||
|
||||
datahandler("Name", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->name = devices.locale.localize(value);
|
||||
});
|
||||
|
||||
datahandler("Tags", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
split(value, def->tags, ',', true);
|
||||
for(size_t i = 0, cnt = def->tags.size(); i < cnt; ++i)
|
||||
def->numTags.insert(getSysTagIndex(def->tags[i], true));
|
||||
});
|
||||
|
||||
datahandler("Subsystem", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->subsystems.push_back(value);
|
||||
});
|
||||
|
||||
datahandler("Background", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
const render::RenderState* bg = &devices.library.getMaterial(value);
|
||||
if(!bg) {
|
||||
error("(Hull %s): Error: Unknown material %s.", def->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->background = bg;
|
||||
def->backgroundName = value;
|
||||
});
|
||||
|
||||
datahandler("BackgroundScale", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->backgroundScale = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("ModelScale", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->modelScale = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("Material", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
const render::RenderState* mat = &devices.library.getMaterial(value);
|
||||
if(!mat) {
|
||||
error("(Hull %s): Error: Unknown material %s.", def->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->materialName = value;
|
||||
def->material = mat;
|
||||
});
|
||||
|
||||
datahandler("Model", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
const render::RenderMesh* mesh = &devices.library.getMesh(value);
|
||||
if(!mesh) {
|
||||
if(devices.render)
|
||||
error("(Hull %s): Error: Unknown model %s.", def->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->meshName = value;
|
||||
def->mesh = mesh;
|
||||
});
|
||||
|
||||
datahandler("Shape", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
std::string fname = devices.mods.resolve(value);
|
||||
def->shapeMap = fname+".map.png";
|
||||
if(fileExists(def->shapeMap)) {
|
||||
def->shape = loadImage(def->shapeMap.c_str());
|
||||
def->shapeMapped = true;
|
||||
}
|
||||
else {
|
||||
def->shape = loadImage(fname.c_str());
|
||||
def->shapeMapped = false;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("GridSize", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
std::vector<std::string> args;
|
||||
split(value, args, ',');
|
||||
|
||||
if(args.size() != 2) {
|
||||
error("(Hull %s): Error: Invalid grid size specification.", def->ident.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->gridSize = vec2i(toNumber<int>(args[0]), toNumber<int>(args[1]));
|
||||
|
||||
def->active.resize(def->gridSize);
|
||||
def->active.clear(false);
|
||||
|
||||
def->exterior.resize(def->gridSize);
|
||||
def->exterior.clear(0);
|
||||
});
|
||||
|
||||
datahandler("GridOffset", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
std::vector<std::string> args;
|
||||
split(value, args, ',');
|
||||
|
||||
if(args.size() != 4) {
|
||||
error("(Hull %s): Error: Invalid grid offset specification.", def->ident.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->gridOffset = recti(toNumber<int>(args[0]), toNumber<int>(args[1]),
|
||||
toNumber<int>(args[2]), toNumber<int>(args[3]));
|
||||
});
|
||||
|
||||
datahandler("GuiIcon", [&](std::string& value) {
|
||||
if(def) {
|
||||
def->guiIcon = devices.library.getSprite(value);
|
||||
def->guiIconName = value;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("FleetIcon", [&](std::string& value) {
|
||||
if(def) {
|
||||
def->fleetIcon = devices.library.getSprite(value);
|
||||
def->fleetIconName = value;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("IconSheet", [&](std::string& value) {
|
||||
if(def) {
|
||||
def->iconSheet = &devices.library.getSpriteSheet(value);
|
||||
def->iconName = value;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("IconIndex", [&](std::string& value) {
|
||||
if(def)
|
||||
def->iconIndex = toNumber<unsigned>(value);
|
||||
});
|
||||
|
||||
datahandler("MinSize", [&](std::string& value) {
|
||||
if(def)
|
||||
def->minSize = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("MaxSize", [&](std::string& value) {
|
||||
if(def)
|
||||
def->maxSize = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("Special", [&](std::string& value) {
|
||||
if(def)
|
||||
def->special = toBool(value);
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
bool HullDef::hasTag(const std::string& tag) const {
|
||||
auto it = std::find(tags.begin(), tags.end(), tag);
|
||||
return it != tags.end();
|
||||
}
|
||||
|
||||
void writeHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls) {
|
||||
std::ofstream file(filename);
|
||||
foreach(it, hulls) {
|
||||
const HullDef* def = *it;
|
||||
file << "Hull: " << def->ident << "\n";
|
||||
file << "\tName: " << def->name << "\n";
|
||||
file << "\tBackground: " << def->backgroundName << "\n";
|
||||
if(def->backgroundScale != 1.0)
|
||||
file << "\tBackgroundScale: " << def->backgroundScale << "\n";
|
||||
file << "\tMaterial: " << def->materialName << "\n";
|
||||
file << "\tModel: " << def->meshName << "\n";
|
||||
file << "\tGuiIcon: " << def->guiIconName << "\n";
|
||||
if(!def->fleetIconName.empty())
|
||||
file << "\tFleetIcon: " << def->fleetIconName << "\n";
|
||||
file << "\tIconSheet: " << def->iconName << "\n";
|
||||
file << "\tIconIndex: " << def->iconIndex << "\n";
|
||||
file << "\n";
|
||||
|
||||
if(!def->tags.empty()) {
|
||||
file << "\tTags: ";
|
||||
bool first = true;
|
||||
for(auto t = def->tags.begin(), tend = def->tags.end(); t != tend; ++t) {
|
||||
if(!first)
|
||||
file << ", ";
|
||||
first = false;
|
||||
file << *t;
|
||||
}
|
||||
file << "\n\n";
|
||||
}
|
||||
|
||||
file << "\tGridSize: " << def->gridSize.x << ", " << def->gridSize.y << "\n";
|
||||
file << "\tGridOffset: " << def->gridOffset.topLeft.x << "," << def->gridOffset.topLeft.y;
|
||||
file << ", " << def->gridOffset.botRight.x << "," << def->gridOffset.botRight.y << "\n";
|
||||
file << "\n";
|
||||
|
||||
for(int y = 0; y < def->gridSize.y; ++y) {
|
||||
file << "\t";
|
||||
|
||||
for(int x = 0; x < def->gridSize.x; ++x) {
|
||||
if(x != 0)
|
||||
file << " ";
|
||||
if(def->active.get(x, y)) {
|
||||
if(def->exterior.get(x, y)) {
|
||||
file << "#";
|
||||
}
|
||||
else {
|
||||
file << "X";
|
||||
}
|
||||
}
|
||||
else {
|
||||
file << "-";
|
||||
}
|
||||
}
|
||||
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
file << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
unsigned Shipset::getHullCount() const {
|
||||
return hulls.size();
|
||||
}
|
||||
|
||||
ShipSkin* Shipset::getSkin(const std::string& name) const {
|
||||
auto it = skins.find(name);
|
||||
if(it == skins.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Shipset::~Shipset() {
|
||||
for(auto it = skins.begin(); it != skins.end(); ++it)
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
const HullDef* Shipset::getHull(unsigned index) const {
|
||||
if(index >= hulls.size())
|
||||
return nullptr;
|
||||
return hulls[index];
|
||||
}
|
||||
|
||||
const HullDef* Shipset::getHull(const std::string& ident) const {
|
||||
for(auto i = hulls.begin(), end = hulls.end(); i != end; ++i)
|
||||
if((*i)->ident == ident)
|
||||
return *i;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Shipset::hasHull(const HullDef* hull) const {
|
||||
for(auto i = hulls.begin(), end = hulls.end(); i != end; ++i)
|
||||
if(*i == hull)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void loadShipset(const std::string& filename) {
|
||||
Shipset* set = nullptr;
|
||||
ShipSkin* skin = nullptr;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
datahandler("Shipset", [&](std::string& value) {
|
||||
if(shipsetIndices.find(value) != shipsetIndices.end())
|
||||
error("Duplicate Shipset ID: %s", value.c_str());
|
||||
set = new Shipset();
|
||||
set->ident = value;
|
||||
set->available = true;
|
||||
set->id = (unsigned)shipsets.size();
|
||||
shipsetIndices[value] = set->id;
|
||||
shipsets.push_back(set);
|
||||
});
|
||||
|
||||
datahandler("Name", [&](std::string& value) {
|
||||
if(set)
|
||||
set->name = value;
|
||||
});
|
||||
|
||||
datahandler("DLC", [&](std::string& value) {
|
||||
if(set)
|
||||
set->dlc = value;
|
||||
});
|
||||
|
||||
datahandler("Available", [&](std::string& value) {
|
||||
if(set)
|
||||
set->available = toBool(value, true);
|
||||
});
|
||||
|
||||
datahandler("Hull", [&](std::string& value) {
|
||||
if(set) {
|
||||
auto* hull = getHullDefinition(value);
|
||||
if(hull)
|
||||
set->hulls.push_back(hull);
|
||||
else
|
||||
error("Could not find hull %s for shipset %s", value.c_str(), set->ident.c_str());
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Skin", [&](std::string& value) {
|
||||
if(set) {
|
||||
skin = new ShipSkin();
|
||||
skin->ident = value;
|
||||
set->skins[value] = skin;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Model", [&](std::string& value) {
|
||||
if(skin && set) {
|
||||
const render::RenderMesh* mesh = &devices.library.getMesh(value);
|
||||
if(!mesh) {
|
||||
if(devices.render)
|
||||
error("(%s Ship Skin %s): Error: Unknown model %s.", set->ident.c_str(), skin->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
skin->mesh = mesh;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Material", [&](std::string& value) {
|
||||
if(skin && set) {
|
||||
const render::RenderState* mat = &devices.library.getMaterial(value);
|
||||
if(!mat) {
|
||||
if(devices.render)
|
||||
error("(%s Ship Skin %s): Error: Unknown material %s.", set->ident.c_str(), skin->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
skin->material = mat;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Icon", [&](std::string& value) {
|
||||
if(skin && set) {
|
||||
skin->icon = devices.library.getSprite(value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
void initAllShipset() {
|
||||
auto* set = new Shipset();
|
||||
set->ident = "ALL";
|
||||
set->available = false;
|
||||
set->id = (unsigned)shipsets.size();
|
||||
|
||||
for(size_t i = 0, cnt = shipsets.size(); i < cnt; ++i) {
|
||||
auto* other = shipsets[i];
|
||||
if(!other->available)
|
||||
continue;
|
||||
for(size_t n = 0, ncnt = other->hulls.size(); n < ncnt; ++n)
|
||||
set->hulls.push_back(other->hulls[n]);
|
||||
}
|
||||
|
||||
shipsetIndices[set->ident] = set->id;
|
||||
shipsets.push_back(set);
|
||||
}
|
||||
|
||||
void clearShipsets() {
|
||||
shipsets.clear(); shipsets.shrink_to_fit();
|
||||
shipsetIndices.clear();
|
||||
}
|
||||
|
||||
unsigned getShipsetCount() {
|
||||
return (unsigned)shipsets.size();
|
||||
}
|
||||
|
||||
const Shipset* getShipset(unsigned id) {
|
||||
if(id >= (unsigned)shipsets.size())
|
||||
return nullptr;
|
||||
return shipsets[id];
|
||||
}
|
||||
|
||||
const Shipset* getShipset(const std::string& ident) {
|
||||
auto i = shipsetIndices.find(ident);
|
||||
if(i == shipsetIndices.end())
|
||||
return nullptr;
|
||||
return shipsets[i->second];
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "util/hex_grid.h"
|
||||
#include "util/refcount.h"
|
||||
#include "vec2.h"
|
||||
#include "rect.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
class Shipset;
|
||||
class HullDef : public AtomicRefCounted {
|
||||
public:
|
||||
static const unsigned flagExteriorPass = 1 << 31;
|
||||
static const unsigned flagExteriorFaux = 1 << 30;
|
||||
|
||||
unsigned id;
|
||||
std::string ident;
|
||||
std::string name;
|
||||
std::string backgroundName;
|
||||
std::string meshName;
|
||||
std::string materialName;
|
||||
std::string iconName;
|
||||
std::string guiIconName;
|
||||
std::string fleetIconName;
|
||||
std::vector<std::string> tags;
|
||||
std::vector<std::string> subsystems;
|
||||
std::unordered_set<int> numTags;
|
||||
const render::RenderState* background;
|
||||
const render::RenderState* material;
|
||||
const render::RenderMesh* mesh;
|
||||
const render::SpriteSheet* iconSheet;
|
||||
render::Sprite guiIcon;
|
||||
render::Sprite fleetIcon;
|
||||
unsigned iconIndex;
|
||||
vec2i gridSize;
|
||||
recti gridOffset;
|
||||
double backgroundScale;
|
||||
double modelScale;
|
||||
HexGrid<bool> active;
|
||||
HexGrid<int> exterior;
|
||||
unsigned activeCount;
|
||||
unsigned exteriorCount;
|
||||
double minSize;
|
||||
double maxSize;
|
||||
mutable heldPointer<const HullDef> baseHull;
|
||||
Image* shape;
|
||||
std::string shapeMap;
|
||||
bool shapeMapped;
|
||||
bool special;
|
||||
|
||||
HullDef();
|
||||
HullDef(const HullDef& other);
|
||||
|
||||
std::vector<vec3d> impacts;
|
||||
bool calculateImpacts();
|
||||
vec3d getImpact(const vec3d& offset, double radius, bool constant = false) const;
|
||||
vec3d getClosestImpact(const vec3d& offset) const;
|
||||
|
||||
void calculateDist();
|
||||
void calculateDist(vec2u pos, int dist);
|
||||
void calculateExterior();
|
||||
void calculateExterior(vec2u pos, unsigned direction);
|
||||
|
||||
bool checkConnected();
|
||||
void fillConnected(HexGrid<bool>& connected, vec2u hex);
|
||||
|
||||
double getMatchDistance(const vec2d& pos) const;
|
||||
double getMatchDistance(void* desc) const;
|
||||
|
||||
bool isExterior(const vec2u& hex) const;
|
||||
bool isExteriorInDirection(const vec2u& hex, unsigned dir) const;
|
||||
|
||||
bool hasTag(const std::string& tag) const;
|
||||
};
|
||||
|
||||
struct ShipSkin {
|
||||
std::string ident;
|
||||
const render::RenderState* material;
|
||||
const render::RenderMesh* mesh;
|
||||
render::Sprite icon;
|
||||
};
|
||||
|
||||
class Shipset : public AtomicRefCounted {
|
||||
public:
|
||||
unsigned id;
|
||||
std::vector<const HullDef*> hulls;
|
||||
bool available;
|
||||
std::string ident, name;
|
||||
std::string dlc;
|
||||
|
||||
std::unordered_map<std::string,ShipSkin*> skins;
|
||||
|
||||
~Shipset();
|
||||
ShipSkin* getSkin(const std::string& name) const;
|
||||
|
||||
bool hasHull(const HullDef* hull) const;
|
||||
|
||||
unsigned getHullCount() const;
|
||||
const HullDef* getHull(unsigned index) const;
|
||||
const HullDef* getHull(const std::string& ident) const;
|
||||
};
|
||||
|
||||
void loadHullDefinitions(const std::string& filename);
|
||||
void clearHullDefinitions();
|
||||
|
||||
void readHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls);
|
||||
void writeHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls);
|
||||
|
||||
unsigned getHullCount();
|
||||
const HullDef* getHullDefinition(unsigned id);
|
||||
const HullDef* getHullDefinition(const std::string& ident);
|
||||
|
||||
void computeHulls(unsigned amount);
|
||||
bool isFinishedComputingHulls();
|
||||
|
||||
void loadShipset(const std::string& filename);
|
||||
void initAllShipset();
|
||||
void clearShipsets();
|
||||
|
||||
unsigned getShipsetCount();
|
||||
const Shipset* getShipset(unsigned id);
|
||||
const Shipset* getShipset(const std::string& ident);
|
||||
@@ -0,0 +1,749 @@
|
||||
#include "projectiles.h"
|
||||
#include "line3d.h"
|
||||
#include "main/references.h"
|
||||
#include "processing.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "obj/object.h"
|
||||
#include "empire.h"
|
||||
#include "design/effector.h"
|
||||
#include "threads.h"
|
||||
#include "scene/node.h"
|
||||
#include "scene/particle_system.h"
|
||||
#include "memory/AllocOnlyPool.h"
|
||||
#include "threads.h"
|
||||
#include "obj/lock.h"
|
||||
#include "scene/animation/anim_projectile.h"
|
||||
#include "util/save_file.h"
|
||||
#include "design/design.h"
|
||||
#include "obj/blueprint.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "ISound.h"
|
||||
#include "ISoundDevice.h"
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
|
||||
memory::AllocOnlyPool<Projectile,threads::Mutex> projPool(8192);
|
||||
|
||||
void initNewThread();
|
||||
void cleanupThread();
|
||||
|
||||
struct ProjEffect : public ObjectMessage {
|
||||
Object* source;
|
||||
const Effector* effector;
|
||||
vec3d relImpact;
|
||||
float effectiveness;
|
||||
float partiality;
|
||||
float delay;
|
||||
|
||||
ProjEffect(const Effector* fctr, Object* target, Object* src, vec3d relImpactPt, float eff, float part, float del) : ObjectMessage(target), source(src), effector(fctr), relImpact(relImpactPt), effectiveness(eff), partiality(part), delay(del) {
|
||||
source->grab();
|
||||
effector->grab();
|
||||
}
|
||||
|
||||
~ProjEffect() {
|
||||
object->drop();
|
||||
source->drop();
|
||||
effector->drop();
|
||||
}
|
||||
|
||||
void process() override {
|
||||
effector->triggerEffect(source, object, relImpact, effectiveness, partiality, delay);
|
||||
}
|
||||
};
|
||||
|
||||
struct ProjImpactEffect : public scene::NodeEvent {
|
||||
const scene::ParticleSystemDesc* system;
|
||||
vec3d from, vel;
|
||||
float scale;
|
||||
float delay;
|
||||
|
||||
ProjImpactEffect(scene::Node* parentNode, const scene::ParticleSystemDesc* sys, const vec3d& From, const vec3d& Vel, float Scale, float Delay) : NodeEvent(parentNode), system(sys), from(From), vel(Vel), scale(Scale), delay(Delay) {
|
||||
}
|
||||
|
||||
void process() override {
|
||||
vec3d pos;
|
||||
quaterniond rot;
|
||||
if(node) {
|
||||
vec3d d = (from - node->abs_position).normalized();
|
||||
pos = (from - node->abs_position);
|
||||
rot = quaterniond::fromImpliedTransform(vec3d::front(), d);
|
||||
}
|
||||
else {
|
||||
pos = from;
|
||||
}
|
||||
auto* pSys = scene::playParticleSystem(system, node, pos, rot, vel, scale, delay);
|
||||
if(pSys)
|
||||
pSys->drop();
|
||||
}
|
||||
|
||||
~ProjImpactEffect() {
|
||||
}
|
||||
};
|
||||
|
||||
double clamp(double v, double low, double high) {
|
||||
if(v <= low)
|
||||
return low;
|
||||
else if(v >= high)
|
||||
return high;
|
||||
else
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline double sqr(double x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
//Look for a new objec hostile to the owner
|
||||
Object* findTarget(Empire* owner, const vec3d& position, double radius, const void* ofType = nullptr) {
|
||||
if(!owner)
|
||||
return nullptr;
|
||||
|
||||
Object* obj = nullptr;
|
||||
|
||||
devices.physics->findInBox(AABBoxd::fromCircle(position, radius), [&obj,owner,&position,radius,ofType](const PhysicsItem& item) {
|
||||
if(obj)
|
||||
return;
|
||||
if(item.type == PIT_Object) {
|
||||
Object* target = item.object;
|
||||
if(!target->isVisibleTo(owner) || !target->isValid())
|
||||
return;
|
||||
if(ofType && target->type != ofType)
|
||||
return;
|
||||
if(position.distanceToSQ(target->position) > (radius + target->radius) * (radius + target->radius))
|
||||
return;
|
||||
|
||||
obj = target;
|
||||
obj->grab();
|
||||
}
|
||||
}, owner->hostileMask);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool Projectile::tick(double time) {
|
||||
//Rotate to track the target
|
||||
if(type == PT_Missile) {
|
||||
if(target && target->isValid()) {
|
||||
if(recover <= 0.f) {
|
||||
vec3d dir = (target->position - position).normalized();
|
||||
vec3d projImpact = target->position + dir;
|
||||
if(target->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)target) + target->type->blueprintOffset);
|
||||
const Design* dsg = blueprint->design;
|
||||
|
||||
if(dsg != nullptr) {
|
||||
vec3d impactOffset = target->rotation.inverted() * dir;
|
||||
projImpact = target->rotation * dsg->hull->getImpact(impactOffset, target->radius, true) + target->position;
|
||||
}
|
||||
}
|
||||
|
||||
dir = (projImpact - position).normalized();
|
||||
double speed = velocity.getLength();
|
||||
vec3d vdir = velocity / speed;
|
||||
|
||||
double angle = acos(clamp(vdir.dot(dir),-1,1)), rot = tracking * time;
|
||||
if(angle < rot) {
|
||||
velocity = dir * speed;
|
||||
}
|
||||
else if(angle >= pi * 0.9) {
|
||||
quaterniond dirq = quaterniond::fromImpliedTransform(vec3d::front(), dir);
|
||||
quaterniond vdirq = quaterniond::fromImpliedTransform(vec3d::front(), vdir);
|
||||
quaterniond result = vdirq.slerp(dirq, rot / angle);
|
||||
velocity = (result * vec3d::front()).normalized(speed);
|
||||
}
|
||||
else {//if(angle < 0.05) {
|
||||
//Lerp is a good enough approximation
|
||||
// -- It really is not :( stupid missiles can't even turn 180 degrees
|
||||
velocity = vdir.interpolate(dir, rot / angle).normalized(speed);
|
||||
}
|
||||
|
||||
if(graphics) {
|
||||
auto* anim = (scene::ProjectileAnim*)graphics->animator.ptr;
|
||||
anim->velocity = velocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(source && source->isValid()) {
|
||||
if(target) {
|
||||
auto* prevTarget = target;
|
||||
target = findTarget(source->owner, target->position, 50.0, target->type);
|
||||
prevTarget->drop();
|
||||
}
|
||||
else {
|
||||
target = findTarget(source->owner, position, 50.0);
|
||||
}
|
||||
|
||||
//If we didn't quickly find a target, we die twice as fast
|
||||
if(target == 0 && !effector->type.pierces)
|
||||
lifetime *= 0.5f;
|
||||
}
|
||||
else {
|
||||
//Become a dummy when the target is already dead
|
||||
//Also live only half the remaining time
|
||||
lifetime *= 0.5f;
|
||||
if(target) {
|
||||
target->drop();
|
||||
target = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(type == PT_Beam) {
|
||||
//Beams can't fire when the source is dead
|
||||
if(!source->isValid())
|
||||
return true;
|
||||
|
||||
if(target) {
|
||||
if(target->isValid()) {
|
||||
if(tracking > 0) {
|
||||
vec3d targDir = (target->position - (source->position + position)).normalize();
|
||||
double range = velocity.getLength();
|
||||
|
||||
vec3d curDir = velocity / range;
|
||||
|
||||
double dot = std::max(std::min(targDir.dot(curDir), 1.0), -1.0);
|
||||
double angDiff = acos(dot);
|
||||
double track = tracking * time;
|
||||
|
||||
if(angDiff <= track)
|
||||
velocity = targDir * range;
|
||||
else
|
||||
velocity = curDir.slerp(targDir,track/angDiff) * range;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Our current target is dead, find a new one
|
||||
auto* prevTarget = target;
|
||||
if(source)
|
||||
target = findTarget(source->owner, target->position, 50.0, target->type);
|
||||
else
|
||||
target = 0;
|
||||
prevTarget->drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line3dd line(position, vec3d());
|
||||
|
||||
if(type != PT_Beam) {
|
||||
line.end = position + velocity * time;
|
||||
}
|
||||
else {
|
||||
line.start += source->position;
|
||||
line.end = line.start + velocity;
|
||||
}
|
||||
|
||||
Object* other = 0;
|
||||
vec3d impactPt;
|
||||
|
||||
double prevDistSQ = 0;
|
||||
double projScale = scale;
|
||||
|
||||
//If our line still collides with our last impacted object, we can check for only intervening objects
|
||||
if(lastImpact && lastImpact->isValid()) {
|
||||
vec3d closePt = line.getClosestPoint(lastImpact->position,false);
|
||||
double distSQ = closePt.distanceToSQ(lastImpact->position);
|
||||
double width = lastImpact->radius + scale;
|
||||
if(distSQ <= width * width) {
|
||||
other = lastImpact;
|
||||
other->grab();
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
if(!effector || !effector->type.pierces)
|
||||
line.end = impactPt;
|
||||
prevDistSQ = closePt.distanceToSQ(line.start);
|
||||
}
|
||||
}
|
||||
|
||||
if(mode != PM_OnlyHitsTarget) {
|
||||
if(mode == PM_PassthroughInvalid) {
|
||||
const Effector* eff = effector;
|
||||
Object* src = source;
|
||||
devices.physics->findInBox(AABBoxd(line, scale), [&other,&line,&prevDistSQ,&impactPt,projScale,eff,src](const PhysicsItem& item) {
|
||||
//if(item.type != PIT_Object)
|
||||
// return;
|
||||
Object* obj = item.object;
|
||||
vec3d closePt = line.getClosestPoint(obj->position,false);
|
||||
double distSQ = closePt.distanceToSQ(obj->position);
|
||||
double width = obj->radius + projScale;
|
||||
if(distSQ <= width * width && (!eff || eff->canTarget(src, obj))) {
|
||||
double objDistSQ = closePt.distanceToSQ(line.start);
|
||||
if(!other || objDistSQ < prevDistSQ) {
|
||||
if(other)
|
||||
other->drop();
|
||||
other = obj;
|
||||
other->grab();
|
||||
prevDistSQ = objDistSQ;
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
}
|
||||
}
|
||||
}, source->owner ? (source->owner->hostileMask | 0x1) : ~0x0);
|
||||
}
|
||||
else {
|
||||
devices.physics->findInBox(AABBoxd(line, scale), [&other,&line,&prevDistSQ,&impactPt,projScale](const PhysicsItem& item) {
|
||||
//if(item.type != PIT_Object)
|
||||
// return;
|
||||
Object* obj = item.object;
|
||||
vec3d closePt = line.getClosestPoint(obj->position,false);
|
||||
double distSQ = closePt.distanceToSQ(obj->position);
|
||||
double width = obj->radius + projScale;
|
||||
if(distSQ <= width * width) {
|
||||
double objDistSQ = closePt.distanceToSQ(line.start);
|
||||
if(!other || objDistSQ < prevDistSQ) {
|
||||
if(other)
|
||||
other->drop();
|
||||
other = obj;
|
||||
other->grab();
|
||||
prevDistSQ = objDistSQ;
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
}
|
||||
}
|
||||
}, source->owner ? (source->owner->hostileMask | 0x1) : ~0x0);
|
||||
}
|
||||
}
|
||||
else if(target && target->isValid()){
|
||||
vec3d closePt = line.getClosestPoint(target->position,false);
|
||||
double distSQ = closePt.distanceToSQ(target->position);
|
||||
double width = target->radius + projScale;
|
||||
if(distSQ <= width * width) {
|
||||
if(other)
|
||||
other->drop();
|
||||
other = target;
|
||||
other->grab();
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
}
|
||||
}
|
||||
|
||||
if(other != nullptr && other->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)other) + other->type->blueprintOffset);
|
||||
const Design* dsg = blueprint->design;
|
||||
|
||||
if(dsg != nullptr) {
|
||||
vec3d impactOffset = impactPt - other->position;
|
||||
impactOffset = other->rotation.inverted() * impactOffset;
|
||||
impactPt = other->rotation * dsg->hull->getImpact(impactOffset, other->radius, type == PT_Beam) + other->position;
|
||||
}
|
||||
}
|
||||
|
||||
if(recover > 0.f) {
|
||||
if(other && other == lastImpact) {
|
||||
other->drop();
|
||||
other = nullptr;
|
||||
}
|
||||
recover -= time;
|
||||
if(recover <= 0.f)
|
||||
recover = 0.f;
|
||||
}
|
||||
|
||||
if(other) {
|
||||
if(impact)
|
||||
*impact = impactPt;
|
||||
if(type == PT_Beam || effector->type.pierces) {
|
||||
other->grab();
|
||||
if(lastImpact)
|
||||
lastImpact->drop();
|
||||
lastImpact = other;
|
||||
}
|
||||
|
||||
auto* player = Empire::getPlayerEmpire();
|
||||
|
||||
if(effector) {
|
||||
if(player && other->isVisibleTo(player)) {
|
||||
if(auto* sfx = effector->type.skins[effector->skinIndex].impact_sound) {
|
||||
if(sfx->loaded && !audio::disableSFX) {
|
||||
auto* sound = devices.sound->play3D(sfx->source, snd_vec(other->position), false, true);
|
||||
if(sound) {
|
||||
if(source)
|
||||
sound->setVolume((float)((scale + 1.0) / (scale + 7.0)));
|
||||
sound->setPitch((float)randomd(0.95,1.05));
|
||||
float dist = other->position.distanceTo(devices.render->cam_pos);
|
||||
float lo = dist / (dist + scale * 500.0);
|
||||
if(lo > 0.05)
|
||||
sound->setLowPass(lo);
|
||||
sound->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockGroup* lockGroup = other->lockGroup;
|
||||
float delay = 0.f;
|
||||
if(effector && lockGroup) {
|
||||
if(type == PT_Missile && missileData) {
|
||||
double speed = velocity.getLength();
|
||||
delay = impactPt.distanceTo(position) / speed * 1.5f;
|
||||
}
|
||||
|
||||
if(effector->type.skins[effector->skinIndex].impact && (!player || other->isVisibleTo(player)))
|
||||
scene::queueNodeEvent(new ProjImpactEffect(other->node, effector->type.skins[effector->skinIndex].impact, impactPt, other->velocity, (float)sqrt(effector->relativeSize * (source ? source->radius : 1.0)), delay));
|
||||
if(!devices.network->isClient)
|
||||
lockGroup->addMessage(new ProjEffect(effector, other, source, impactPt - other->position, efficiency, type == PT_Beam ? (float)time : 1.f, delay));
|
||||
else
|
||||
other->drop();
|
||||
}
|
||||
else {
|
||||
other->drop();
|
||||
}
|
||||
|
||||
if(type != PT_Beam) {
|
||||
if(effector && effector->type.pierces) {
|
||||
recover = effector->type.recoverTime;
|
||||
if(target && other == target) {
|
||||
target->drop();
|
||||
target = nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(type == PT_Missile && missileData) {
|
||||
auto& data = **missileData;
|
||||
double gameTime = devices.driver->getGameTime();
|
||||
double speed = velocity.getLength();
|
||||
|
||||
data.lastUpdate = gameTime;
|
||||
data.aliveUntil = gameTime + delay;
|
||||
data.pos = impactPt;
|
||||
data.vel = vec3f((impactPt - position).normalized(speed));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(impact) {
|
||||
*impact = line.end;
|
||||
}
|
||||
|
||||
if(type != PT_Beam) {
|
||||
position = line.end;
|
||||
if(type == PT_Missile && missileData) {
|
||||
auto& data = **missileData;
|
||||
data.lastUpdate = devices.driver->getGameTime();
|
||||
data.pos = position;
|
||||
data.vel = vec3f(velocity);
|
||||
}
|
||||
}
|
||||
lifetime -= (float)time;
|
||||
return lifetime <= 0;
|
||||
}
|
||||
|
||||
void* Projectile::operator new(size_t size) {
|
||||
return projPool.alloc();
|
||||
}
|
||||
|
||||
void Projectile::operator delete(void* p) {
|
||||
return projPool.dealloc((Projectile*)p);
|
||||
}
|
||||
|
||||
Projectile::Projectile(ProjType Type) : lastTick(devices.driver->getGameTime()), source(0), target(0), lastImpact(0), graphics(0), type(Type), impact(0), efficiency(1.f), endNotice(0), mode(PM_Normal), recover(0.f) {
|
||||
}
|
||||
|
||||
Projectile::~Projectile() {
|
||||
if(graphics) {
|
||||
graphics->markForDeletion();
|
||||
graphics->drop();
|
||||
}
|
||||
|
||||
if(type != PT_Missile) {
|
||||
if(endNotice) {
|
||||
**endNotice = true;
|
||||
endNotice->drop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(missileData) {
|
||||
if((*missileData)->aliveUntil < 0)
|
||||
(*missileData)->aliveUntil = 0;
|
||||
missileData->drop();
|
||||
}
|
||||
}
|
||||
|
||||
if(source)
|
||||
source->drop();
|
||||
if(target)
|
||||
target->drop();
|
||||
if(lastImpact)
|
||||
lastImpact->drop();
|
||||
if(effector)
|
||||
effector->drop();
|
||||
}
|
||||
|
||||
extern double frameTime_s;
|
||||
Projectile* Projectile::load(SaveFile& file) {
|
||||
const Effector* efftr = nullptr;
|
||||
|
||||
unsigned char empID;
|
||||
int dsgId;
|
||||
unsigned subsysIndex, effectorIndex, effId;
|
||||
if((bool)file) {
|
||||
empID = file;
|
||||
if(empID == INVALID_EMPIRE)
|
||||
return 0;
|
||||
|
||||
dsgId = file;
|
||||
subsysIndex = file;
|
||||
effectorIndex = file;
|
||||
|
||||
Empire* dsgOwner = Empire::getEmpireByID(empID);
|
||||
const Design* dsg = dsgOwner->getDesign(dsgId);
|
||||
if(dsg == nullptr || subsysIndex >= dsg->subsystems.size() || effectorIndex >= dsg->subsystems[subsysIndex].type->effectors.size()) {
|
||||
//throw "Invalid projectile effector.";
|
||||
// Just cancel the projectile later so we can continue the load.
|
||||
}
|
||||
else {
|
||||
efftr = &dsg->subsystems[subsysIndex].effectors[effectorIndex]; //No refcounting on design effectors
|
||||
}
|
||||
}
|
||||
else {
|
||||
effId = file;
|
||||
efftr = getEffector(effId); //Reference is transfered to projectile
|
||||
}
|
||||
|
||||
//Find effector
|
||||
Projectile* proj = new Projectile((ProjType)file.read<unsigned char>());
|
||||
proj->effector = efftr;
|
||||
|
||||
//Load data
|
||||
file >> proj->lastTick;
|
||||
file >> proj->lifetime;
|
||||
proj->source = file.readExistingObject();
|
||||
proj->target = file.readExistingObject();
|
||||
file >> proj->position;
|
||||
file >> proj->velocity;
|
||||
file >> proj->scale;
|
||||
file >> proj->tracking;
|
||||
file >> proj->efficiency;
|
||||
|
||||
//Actually screw this, these things cause nothing but problems
|
||||
delete proj;
|
||||
return 0;
|
||||
|
||||
if(!proj->source || !proj->effector) {
|
||||
//This can happen if the object that fired the projectile
|
||||
//was already destroyed when the save occured. We have no way
|
||||
//of instantiating these or their effects at the moment, because
|
||||
//we know absolutely nothing about the source.
|
||||
|
||||
//TODO: Preserve these in some way, they can still impact and matter
|
||||
//quite a bit.
|
||||
delete proj;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//TODO: The effects are non-trivial, figure out how to restore them
|
||||
//Create graphics
|
||||
/*proj->graphics = proj->effector->type.createGraphics(proj->effector, proj->scale);
|
||||
if(!proj->graphics) {
|
||||
delete proj;
|
||||
throw "Invalid projectile effector graphics.";
|
||||
}
|
||||
|
||||
proj->graphics->position = proj->position;
|
||||
proj->graphics->rebuildTransformation();
|
||||
proj->graphics->animator = new scene::ProjectileAnim(proj->velocity, 1.f);
|
||||
|
||||
registerProjectile(proj);
|
||||
proj->graphics->queueReparent(devices.scene);
|
||||
return proj;*/
|
||||
registerProjectile(proj);
|
||||
return proj;
|
||||
}
|
||||
|
||||
void Projectile::save(SaveFile& file) {
|
||||
if(effector && effector->effectorId != 0) {
|
||||
file << false;
|
||||
file << effector->effectorId;
|
||||
}
|
||||
else {
|
||||
file << true;
|
||||
if(!effector || !effector->inDesign || !effector->inDesign->owner) {
|
||||
file << INVALID_EMPIRE;
|
||||
return;
|
||||
}
|
||||
|
||||
file << effector->inDesign->owner->id;
|
||||
file << effector->inDesign->id;
|
||||
file << effector->subsysIndex;
|
||||
file << effector->effectorIndex;
|
||||
}
|
||||
|
||||
file << (unsigned char)type;
|
||||
file << lastTick;
|
||||
file << lifetime;
|
||||
file << source;
|
||||
file << target;
|
||||
file << position;
|
||||
file << velocity;
|
||||
file << scale;
|
||||
file << tracking;
|
||||
file << efficiency;
|
||||
}
|
||||
|
||||
threads::Mutex projAddLock, projfillLock;
|
||||
threads::Signal activeProjThreads;
|
||||
threads::atomic_int pull_index, push_index, processed_count;
|
||||
std::vector<double> projThreadTimes;
|
||||
|
||||
std::vector<Projectile*>* source = new std::vector<Projectile*>, *dest = new std::vector<Projectile*>;
|
||||
std::vector<Projectile*> queuedProjs;
|
||||
|
||||
static void fillProjectiles() {
|
||||
dest->resize(push_index);
|
||||
|
||||
if(!queuedProjs.empty()) {
|
||||
projAddLock.lock();
|
||||
dest->insert(dest->end(), queuedProjs.begin(), queuedProjs.end());
|
||||
queuedProjs.clear();
|
||||
projAddLock.release();
|
||||
}
|
||||
|
||||
std::swap(source, dest);
|
||||
dest->resize(source->size());
|
||||
|
||||
processed_count = 0;
|
||||
push_index = 0;
|
||||
pull_index = 0;
|
||||
}
|
||||
|
||||
volatile bool ProjectilesActive = false;
|
||||
volatile bool EndProjectiles = false;
|
||||
volatile bool PauseProjectiles = false;
|
||||
volatile bool ProjectilesPaused = false;
|
||||
class ProcessProjectiles : public processing::Action {
|
||||
bool host, active;
|
||||
public:
|
||||
ProcessProjectiles(bool Host) : host(Host), active(true) {
|
||||
}
|
||||
|
||||
~ProcessProjectiles() {
|
||||
//This happens when the game is ending. We may not complete an entire cycle at the right time.
|
||||
if(host)
|
||||
ProjectilesActive = false;
|
||||
else if(active)
|
||||
activeProjThreads.signalDown();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
if(host && activeProjThreads.check(0)) {
|
||||
if(EndProjectiles)
|
||||
return true;
|
||||
ProjectilesActive = true;
|
||||
|
||||
if(PauseProjectiles) {
|
||||
ProjectilesActive = false;
|
||||
ProjectilesPaused = true;
|
||||
return false;
|
||||
}
|
||||
else if(ProjectilesPaused) {
|
||||
ProjectilesPaused = false;
|
||||
}
|
||||
else {
|
||||
fillProjectiles();
|
||||
}
|
||||
|
||||
if(!source->empty()) {
|
||||
activeProjThreads.signal(4);
|
||||
for(unsigned i = 0; i < 4; ++i)
|
||||
processing::queueAction(new ProcessProjectiles(false));
|
||||
}
|
||||
else {
|
||||
ProjectilesActive = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double curTime = devices.driver->getGameTime();
|
||||
unsigned tickMax = 1000;
|
||||
while(tickMax--) {
|
||||
int index = pull_index++;
|
||||
if(index >= (int)source->size()) {
|
||||
if(host) {
|
||||
ProjectilesActive = false;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
active = false;
|
||||
activeProjThreads.signalDown();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Take the next projectile, tick it, and put it on the output stack (unless it needs deleted)
|
||||
Projectile* proj = source->at(index);
|
||||
double t = curTime - proj->lastTick;
|
||||
|
||||
if(t < 0.125) {
|
||||
int outIndex = push_index++;
|
||||
dest->at(outIndex) = proj;
|
||||
}
|
||||
else {
|
||||
if(!proj->tick(t)) {
|
||||
proj->lastTick = curTime;
|
||||
int outIndex = push_index++;
|
||||
dest->at(outIndex) = proj;
|
||||
}
|
||||
else {
|
||||
delete proj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void registerProjectile(Projectile* proj) {
|
||||
projAddLock.lock();
|
||||
queuedProjs.push_back(proj);
|
||||
projAddLock.release();
|
||||
}
|
||||
|
||||
void initProjectiles() {
|
||||
EndProjectiles = false;
|
||||
PauseProjectiles = false;
|
||||
processing::queueAction(new ProcessProjectiles(true));
|
||||
}
|
||||
|
||||
void stopProjectiles() {
|
||||
EndProjectiles = true;
|
||||
while(ProjectilesActive)
|
||||
threads::sleep(1);
|
||||
activeProjThreads.wait(0);
|
||||
source->clear(); source->shrink_to_fit();
|
||||
dest->clear(); dest->shrink_to_fit();
|
||||
for(auto i = queuedProjs.begin(), end = queuedProjs.end(); i != end; ++i)
|
||||
delete *i;
|
||||
queuedProjs.clear();
|
||||
pull_index = 0;
|
||||
push_index = 0;
|
||||
processed_count = 0;
|
||||
}
|
||||
|
||||
void saveProjectiles(SaveFile& file) {
|
||||
unsigned cnt = (unsigned)(queuedProjs.size() + push_index);
|
||||
file << cnt;
|
||||
|
||||
cnt = (unsigned)queuedProjs.size();
|
||||
for(unsigned i = 0; i < cnt; ++i)
|
||||
queuedProjs[i]->save(file);
|
||||
|
||||
cnt = (unsigned)push_index;
|
||||
for(unsigned i = 0; i < cnt; ++i)
|
||||
(*dest)[i]->save(file);
|
||||
}
|
||||
|
||||
void loadProjectiles(SaveFile& file) {
|
||||
unsigned cnt = file;
|
||||
for(unsigned i = 0; i < cnt; ++i)
|
||||
Projectile::load(file);
|
||||
}
|
||||
|
||||
void pauseProjectiles() {
|
||||
PauseProjectiles = true;
|
||||
if(!ProjectilesActive)
|
||||
return;
|
||||
activeProjThreads.wait(0);
|
||||
while(!ProjectilesPaused && ProjectilesActive)
|
||||
threads::sleep(1);
|
||||
}
|
||||
|
||||
void resumeProjectiles() {
|
||||
PauseProjectiles = false;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "threads.h"
|
||||
#include <stddef.h>
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
struct MissileData {
|
||||
vec3d pos;
|
||||
double lastUpdate;
|
||||
vec3f vel;
|
||||
double aliveUntil;
|
||||
};
|
||||
|
||||
class Effector;
|
||||
class Object;
|
||||
class SaveFile;
|
||||
|
||||
enum ProjType {
|
||||
PT_Bullet,
|
||||
PT_Beam,
|
||||
PT_Missile
|
||||
};
|
||||
|
||||
enum ProjMode {
|
||||
PM_Normal,
|
||||
PM_OnlyHitsTarget,
|
||||
PM_PassthroughInvalid
|
||||
};
|
||||
|
||||
struct Projectile {
|
||||
//NOTE: Velocity is reused to specify a beam's target
|
||||
vec3d position, velocity;
|
||||
|
||||
double lastTick;
|
||||
|
||||
vec3d* impact;
|
||||
const Effector* effector;
|
||||
Object* source, *target, *lastImpact;
|
||||
scene::Node* graphics;
|
||||
union {
|
||||
threads::SharedData<bool>* endNotice;
|
||||
threads::SharedData<MissileData>* missileData;
|
||||
};
|
||||
|
||||
float lifetime;
|
||||
float scale;
|
||||
|
||||
//radians per second of turning
|
||||
float tracking;
|
||||
|
||||
//efficiency of the subsystem
|
||||
float efficiency;
|
||||
|
||||
//recovery time in which nothing can be hit
|
||||
float recover;
|
||||
|
||||
ProjType type;
|
||||
ProjMode mode;
|
||||
|
||||
//Advances projectile and performs collision
|
||||
//Returns true if the projectile is dead
|
||||
bool tick(double time);
|
||||
|
||||
Projectile(ProjType Type);
|
||||
~Projectile();
|
||||
|
||||
static Projectile* load(SaveFile& file);
|
||||
void save(SaveFile& file);
|
||||
|
||||
void* operator new(size_t size);
|
||||
void operator delete(void*);
|
||||
};
|
||||
|
||||
void registerProjectile(Projectile* proj);
|
||||
void initProjectiles();
|
||||
void stopProjectiles();
|
||||
|
||||
void saveProjectiles(SaveFile& file);
|
||||
void loadProjectiles(SaveFile& file);
|
||||
|
||||
void pauseProjectiles();
|
||||
void resumeProjectiles();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,398 @@
|
||||
#pragma once
|
||||
#include "design/effect.h"
|
||||
#include "design/effector.h"
|
||||
#include "vec2.h"
|
||||
#include "util/formula.h"
|
||||
#include "util/basic_type.h"
|
||||
#include "compat/misc.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include "util/hex_grid.h"
|
||||
|
||||
class SaveFile;
|
||||
|
||||
#ifndef MODIFY_STAGE_MAXARGS
|
||||
#define MODIFY_STAGE_MAXARGS 8
|
||||
#endif
|
||||
|
||||
enum SysVariableType {
|
||||
SVT_SubsystemVariable,
|
||||
SVT_HexVariable,
|
||||
SVT_ShipVariable,
|
||||
};
|
||||
|
||||
enum TemplateCondition {
|
||||
TC_Tag,
|
||||
TC_Modifier,
|
||||
TC_Variable,
|
||||
TC_HexVariable,
|
||||
TC_ShipVariable,
|
||||
TC_Subsystem,
|
||||
|
||||
TC_NOT = 1<<20,
|
||||
};
|
||||
|
||||
class Design;
|
||||
class Subsystem;
|
||||
class HullDef;
|
||||
class SubsystemDef {
|
||||
public:
|
||||
struct Variable {
|
||||
Formula* formula;
|
||||
std::string str_formula;
|
||||
std::string name;
|
||||
int index;
|
||||
int globalId;
|
||||
SysVariableType type;
|
||||
bool dependent;
|
||||
};
|
||||
|
||||
struct Effect {
|
||||
const EffectDef* type;
|
||||
std::vector<Formula*> values;
|
||||
std::vector<std::string> str_values;
|
||||
};
|
||||
|
||||
struct Effector {
|
||||
const EffectorDef* type;
|
||||
bool enabled;
|
||||
unsigned skinIndex;
|
||||
std::string skinName;
|
||||
std::vector<Formula*> values;
|
||||
std::vector<std::string> str_values;
|
||||
};
|
||||
|
||||
struct StateDesc {
|
||||
BasicTypes type;
|
||||
Formula* formula;
|
||||
std::string str_formula;
|
||||
};
|
||||
|
||||
struct HookDesc {
|
||||
std::string name;
|
||||
std::vector<std::string> str_args;
|
||||
std::vector<Formula*> formulas;
|
||||
mutable std::vector<double> argValues;
|
||||
|
||||
HookDesc() {}
|
||||
HookDesc(const std::string& str);
|
||||
};
|
||||
|
||||
struct ModifyStage {
|
||||
unsigned index;
|
||||
int stage;
|
||||
int umodifid;
|
||||
std::unordered_map<std::string, int> argumentNames;
|
||||
std::vector<std::pair<int, std::string>> str_variables;
|
||||
std::vector<std::pair<int, std::string>> str_hexVariables;
|
||||
std::vector<std::pair<int, std::string>> str_shipVariables;
|
||||
std::unordered_map<int, Formula*> variables;
|
||||
std::unordered_map<int, Formula*> hexVariables;
|
||||
std::unordered_map<int, Formula*> shipVariables;
|
||||
|
||||
ModifyStage() : stage(0), umodifid(-1), index(0) {}
|
||||
|
||||
void applyVariables(Subsystem* sys) const;
|
||||
void applyHexVariables(Subsystem* sys, int hexIndex) const;
|
||||
void applyShipVariables(Design* dsg, Subsystem* sys) const;
|
||||
};
|
||||
|
||||
struct AppliedStage {
|
||||
const ModifyStage* stage;
|
||||
float arguments[MODIFY_STAGE_MAXARGS];
|
||||
Formula* formulas[MODIFY_STAGE_MAXARGS];
|
||||
|
||||
void clear() {
|
||||
for(unsigned i = 0; i < MODIFY_STAGE_MAXARGS; ++i) {
|
||||
if(formulas[i]) {
|
||||
delete formulas[i];
|
||||
formulas[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppliedStage() {
|
||||
memset(this, 0, sizeof(AppliedStage));
|
||||
}
|
||||
};
|
||||
|
||||
struct ShipModifier {
|
||||
AppliedStage stage;
|
||||
std::string modifyName;
|
||||
std::vector<std::string> str_arguments;
|
||||
std::vector<std::pair<int,std::string>> conditions;
|
||||
};
|
||||
|
||||
struct Assert {
|
||||
std::string str_formula;
|
||||
Formula* formula;
|
||||
std::string message;
|
||||
bool fatal;
|
||||
bool unique;
|
||||
};
|
||||
|
||||
struct ModuleDesc {
|
||||
int index;
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string description;
|
||||
Color color;
|
||||
|
||||
std::string umodident;
|
||||
int umodid;
|
||||
|
||||
std::string def_onEnable;
|
||||
std::string def_onDisable;
|
||||
|
||||
asIScriptFunction* scr_onEnable;
|
||||
asIScriptFunction* scr_onDisable;
|
||||
|
||||
std::vector<ModifyStage> modifiers;
|
||||
std::vector<ModifyStage> uniqueModifiers;
|
||||
|
||||
std::vector<AppliedStage> appliedStages;
|
||||
std::vector<std::string> str_appliedStages;
|
||||
|
||||
std::vector<AppliedStage> uniqueAppliedStages;
|
||||
std::vector<std::string> str_uniqueAppliedStages;
|
||||
|
||||
std::vector<AppliedStage> hexAppliedStages;
|
||||
std::vector<std::string> str_hexAppliedStages;
|
||||
|
||||
std::vector<SubsystemDef::ShipModifier> adjacentModifiers;
|
||||
std::vector<SubsystemDef::Effect> effects;
|
||||
|
||||
std::unordered_set<std::string> tags;
|
||||
std::unordered_set<int> numTags;
|
||||
std::unordered_map<int, std::vector<std::string>> tagValues;
|
||||
|
||||
bool hasTag(const std::string& tag) const;
|
||||
bool hasTag(int index) const;
|
||||
const std::string& getTagValue(int index, unsigned num = 0) const;
|
||||
unsigned getTagValueCount(int index) const;
|
||||
bool hasTagValue(int index, const std::string& value) const;
|
||||
|
||||
std::string def_onCheckErrors;
|
||||
asIScriptFunction* scr_onCheckErrors;
|
||||
|
||||
bool onCheckErrors(Design* design, Subsystem* sys, const vec2u& hex) const;
|
||||
|
||||
std::vector<HookDesc> hooks;
|
||||
std::vector<Assert> asserts;
|
||||
|
||||
std::string spriteMat;
|
||||
render::Sprite sprite;
|
||||
int drawMode;
|
||||
|
||||
bool required;
|
||||
bool unique;
|
||||
bool vital;
|
||||
bool defaultUnlock;
|
||||
|
||||
void onEnable(EffectEvent& evt, const vec2u& position) const;
|
||||
void onDisable(EffectEvent& evt, const vec2u& position) const;
|
||||
|
||||
ModuleDesc()
|
||||
: index(-1), scr_onEnable(0), scr_onDisable(0),
|
||||
drawMode(0), required(false), unique(false), vital(false),
|
||||
defaultUnlock(false), scr_onCheckErrors(nullptr) {
|
||||
}
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string id;
|
||||
int index;
|
||||
int ordering;
|
||||
int damageOrder;
|
||||
|
||||
int elevation;
|
||||
Color baseColor;
|
||||
Color typeColor;
|
||||
|
||||
std::string hexMat;
|
||||
std::string picMat;
|
||||
render::Sprite picture;
|
||||
|
||||
std::vector<int> variableIndices;
|
||||
std::vector<SubsystemDef::Variable> variables;
|
||||
std::vector<int> hexVariableIndices;
|
||||
std::vector<SubsystemDef::Variable> hexVariables;
|
||||
std::vector<int> shipVariableIndices;
|
||||
std::vector<SubsystemDef::Variable> shipVariables;
|
||||
|
||||
std::vector<SubsystemDef::Effect> effects;
|
||||
std::vector<SubsystemDef::Effector> effectors;
|
||||
std::vector<SubsystemDef::StateDesc> states;
|
||||
std::vector<SubsystemDef::ModuleDesc*> modules;
|
||||
std::vector<SubsystemDef::ShipModifier> shipModifiers;
|
||||
std::vector<SubsystemDef::ShipModifier> postModifiers;
|
||||
std::vector<SubsystemDef::ShipModifier> adjacentModifiers;
|
||||
std::unordered_map<std::string, int> moduleIndices;
|
||||
const SubsystemDef::ModuleDesc* defaultModule;
|
||||
const SubsystemDef::ModuleDesc* coreModule;
|
||||
|
||||
std::vector<ModifyStage*> modifiers;
|
||||
std::unordered_map<std::string, ModifyStage*> modifierIds;
|
||||
std::vector<Assert> asserts;
|
||||
|
||||
uset<std::string> tags;
|
||||
std::vector<std::string> hullTags;
|
||||
|
||||
uset<int> numTags;
|
||||
std::unordered_map<int, std::vector<std::string>> tagValues;
|
||||
|
||||
void finalize();
|
||||
bool hasTag(const std::string& tag) const;
|
||||
bool hasTag(int index) const;
|
||||
const std::string& getTagValue(int index, unsigned num = 0) const;
|
||||
unsigned getTagValueCount(int index) const;
|
||||
bool hasTagValue(int index, const std::string& value) const;
|
||||
|
||||
std::string def_onCheckErrors;
|
||||
asIScriptFunction* scr_onCheckErrors;
|
||||
|
||||
std::vector<HookDesc> hooks;
|
||||
|
||||
bool onCheckErrors(Design* design, Subsystem* sys) const;
|
||||
bool canUseOn(const HullDef* hull) const;
|
||||
bool hasHullTag(const std::string& tag) const;
|
||||
|
||||
bool hasCore;
|
||||
bool isContiguous;
|
||||
bool exteriorCore;
|
||||
bool defaultUnlock;
|
||||
bool isHull;
|
||||
bool isApplied;
|
||||
bool hexLimitArc;
|
||||
bool passExterior;
|
||||
bool fauxExterior;
|
||||
bool alwaysTakeDamage;
|
||||
|
||||
SubsystemDef();
|
||||
~SubsystemDef();
|
||||
};
|
||||
|
||||
extern int SV_Size, HV_Resistance, HV_HP, ShV_HexSize;
|
||||
extern umap<std::string, int> subsystemIndices;
|
||||
extern umap<std::string, int> variableIndices;
|
||||
extern umap<std::string, int> hexVariableIndices;
|
||||
extern umap<std::string, int> shipVariableIndices;
|
||||
|
||||
void clearSubsystemDefinitions();
|
||||
void loadSubsystemDefinitions(const std::string& filename);
|
||||
const SubsystemDef* getSubsystemDef(const std::string& name);
|
||||
const SubsystemDef* getSubsystemDef(int id);
|
||||
int getSubsystemDefCount();
|
||||
void enumerateVariables(std::function<void(const std::string&,int)>);
|
||||
int getVariableIndex(const std::string& name);
|
||||
const std::string& getVariableId(int index);
|
||||
void enumerateHexVariables(std::function<void(const std::string&,int)>);
|
||||
int getHexVariableIndex(const std::string& name);
|
||||
const std::string& getHexVariableId(int index);
|
||||
void enumerateShipVariables(std::function<void(const std::string&,int)>);
|
||||
unsigned getShipVariableCount();
|
||||
int getShipVariableIndex(const std::string& name);
|
||||
const std::string& getShipVariableId(int index);
|
||||
void enumerateSysTags(std::function<void(const std::string&,int)>);
|
||||
int getSysTagIndex(const std::string& name, bool create = false);
|
||||
|
||||
Formula* parseFormula(const std::string& str, const SubsystemDef* def = 0, const SubsystemDef::ModifyStage* modifier = 0);
|
||||
|
||||
void bindSubsystemMaterials();
|
||||
void bindSubsystemHooks();
|
||||
void finalizeSubsystems();
|
||||
void executeSubsystemTemplates();
|
||||
|
||||
class Blueprint;
|
||||
struct SubsystemEvent {
|
||||
const Subsystem* subsystem;
|
||||
const Design* design;
|
||||
Object* obj;
|
||||
Blueprint* blueprint;
|
||||
void* data;
|
||||
float efficiency;
|
||||
float partiality;
|
||||
};
|
||||
|
||||
class asIScriptFunction;
|
||||
class asIScriptObject;
|
||||
class SaveMessage;
|
||||
class Subsystem {
|
||||
public:
|
||||
const SubsystemDef* type;
|
||||
std::vector<vec2u> hexes;
|
||||
std::vector<const SubsystemDef::ModuleDesc*> modules;
|
||||
std::vector<SubsystemDef::ShipModifier> adjacentModifiers;
|
||||
std::vector<std::vector<SubsystemDef::ShipModifier>> hexAdjacentModifiers;
|
||||
std::vector<int> moduleCounts;
|
||||
vec2u core;
|
||||
vec3d direction;
|
||||
int exteriorHexes;
|
||||
bool hasErrors;
|
||||
|
||||
static asIScriptFunction* ScriptInitFunction;
|
||||
static asIScriptFunction* ScriptHookFunctions[EH_COUNT];
|
||||
|
||||
Effector* effectors;
|
||||
float* variables;
|
||||
float* baseVariables;
|
||||
float* hexVariables;
|
||||
float* hexBaseVariables;
|
||||
BasicType* defaults;
|
||||
unsigned stateOffset;
|
||||
unsigned effectorOffset;
|
||||
unsigned dataOffset;
|
||||
|
||||
std::vector<Effect> effects;
|
||||
std::vector<std::vector<unsigned>> hexEffects;
|
||||
|
||||
std::vector<asIScriptObject*> hookClasses;
|
||||
void addHook(Design* design, const SubsystemDef::HookDesc& desc);
|
||||
|
||||
const Design* inDesign;
|
||||
unsigned index;
|
||||
|
||||
int getModuleCount(int index);
|
||||
|
||||
float* variable(int index);
|
||||
const float* variable(int index) const;
|
||||
float* hexVariable(int index, int hexIndex);
|
||||
const float* hexVariable(int index, int hexIndex) const;
|
||||
|
||||
Subsystem();
|
||||
Subsystem(const SubsystemDef& def);
|
||||
Subsystem(SaveFile& file);
|
||||
~Subsystem();
|
||||
void save(SaveFile& file) const;
|
||||
void postLoad(Design* design);
|
||||
|
||||
void init(const SubsystemDef& def);
|
||||
void init(SaveFile& file);
|
||||
|
||||
void initVariables(Design* design);
|
||||
void initEffects(Design* design);
|
||||
void initLinks(Design* design);
|
||||
void evaluatePost(Design* design);
|
||||
void evaluateAsserts(Design* design);
|
||||
void skinEffectors(Empire& emp);
|
||||
void applyAdjacencies(Design* design);
|
||||
|
||||
DamageEventStatus damage(DamageEvent& event, const vec2u& position) const;
|
||||
DamageEventStatus globalDamage(DamageEvent& event, vec2u& position, vec2d& endPoint) const;
|
||||
bool hasGlobalDamage() const;
|
||||
|
||||
void ownerChange(EffectEvent& event, Empire* prevEmpire, Empire* newEmpire) const;
|
||||
void call(EffectHook hook, EffectEvent& event) const;
|
||||
void tick(EffectEvent& event) const;
|
||||
|
||||
void save(EffectEvent& event, SaveMessage& msg) const;
|
||||
void load(EffectEvent& event, SaveMessage& msg) const;
|
||||
|
||||
void markConnected(HexGrid<bool>& grid, vec2u hex);
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
Subsystem(net::Message& msg);
|
||||
};
|
||||
Reference in New Issue
Block a user