-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attributes system improvements #683
Conversation
After a lot of time, I cant find a way to improve the the copypaste |
Issues:
|
Should I move attack stance inside a formation attribute? This attribute will also store patrol and follow target command's information. |
Any comments? |
You can also take a look at #642. Some of it is about attributes and position. I'm trying to record, serialize, transfer and replay them on the client. That PR has the quickest possible hacks to be able to do that. Maybe you've got an idea how implementing record/replay has become simpler with your changes (or how to make it simpler, or how to approach the record/replay correctly). |
Sorry it takes so long, I'll have a look tomorrow hopefully. |
On a sidenote (as university and exams keep me busy..): The shared attributes are the ones provided by nyan, the per-unit attributes are engine features and stored like we do currently. |
Still the nyan should define the existence of a unshared attribute (eg. formation, resource, damaged) or somehow connect shared with unshared (eg. speed->formation, worker->resource, hitpoints->damaged). EDIT: I thought I more weak connection between nyan and attributes was intended (eg. life: 10 -> hitpoints, damaged, armor) (am I correct?) |
My idea for that was to create nyan files that ship with the openage engine so that the c++ code can assume that members of those objects (from the shipped # this file is shipped with the engine
Ability():
pass
MoveAbility(Ability):
speed : float;
GatherAbility(Ability):
max_weight : float; # maxium weight of resources to carry
UnitType():
hp : int # every unit has hp
abilities : set(Ability) # additionally, it can do many other things
Game():
create_units : set((int, UnitType)) = {} This means that each nyan- # this file is then loaded at runtime
VillagerMovement(MoveAbility):
speed = 1.0
VillagerGathering(GatherAbility):
max_weight = 20.0
Villager(UnitType):
hp = 40
abilities |= {VillagerMovement, VillagerGathering}
# registration patche
Register<Game>():
create_units |= {(20, Villager)} Now, let's assume we implemented the following in openage: // our attribute system and unit handling that is done per-actual-unit
// abilities with their attributes are used for per-unit storage of abilities that this unit posesses.
enum class ability_t {
gather
};
class Ability {
virtual ability_t get_type() const = 0;
virtual void tick(Unit *) = 0;
nyan::Object *type;
};
class GatherAbility : public Ability {
// we can very likely optimize this function with template fuckery...
ability_t get_type() const override { return ability_t::gather; }
void tick(Unit *unit) override {
// check if we have to go home
int max_weight = this->type.get<int>("max_weight");
if (max_weight > this->current_carry_weight) {
// you get the point...
unit->go_to_nearest_dropsite();
return;
}
// here, deduct the resources from the gathering spot.
// etcetc
// this->current_target.deduct(blablabla)
// this->current_carry_weight += roflroflrofl;
}
float current_carry_weight = 0;
}
std::unique_ptr<Ability> create_ability(nyan::Object *ability_type) {
if (ability_type->is_child_of("GatherAbility")) {
return std::make_unique<GatherAbility>(ability_type);
}
return nullptr;
}
/**
* This is the C++ object tied to the nyan object "UnitType".
* "UnitType" stores shared attributes (e.g. "maximum hp")
*/
class Unit {
Unit(nyan::Object *type)
:
type{type} {}
template <typename T> Ability *get_ability() {
return this->abilities[T].get();
}
void add_ability(std::unique_ptr<Ability> ability) {
this->abilities.insert({ability.get_type(), std::move(ability)});
}
/** this links to the nyan object that stores the unit type */
nyan::Object *type;
/**
* each unit stores its damage and it dies,
* when damage > max_hp
*/
int damage;
/** each unit always has a position in the world */
coord::phys3 position;
/**
* Additional per-unit abilities and their storage that are added at runtime by nyan.
*/
std::unordered_map<attr_t, std::unique_ptr<Ability>> abilities;
};
// ... add the unit to the simulation and nyan gives it the damage attribute ...
void init() {
nyan::Database db{};
db.load_files(engine_nyanfiles);
db.load_files(user_mods);
// this is normally invoked in a more intelligent way, like for each mod
// that was loaded.
db.apply_patch("Register");
Simulation sim;
// now, the "InitialUnits.units" contains the villager (it was added by "Register")
// this is used for "game initialization"
for (auto &creation_tuple : db.query("Game").get<set, nyan::tuple<int, nyan::Object*>>("create_units")) {
int amount = creation_tuple.get<0>();
nyan::Object *type = creation_tuple.get<1>();
Unit unit{type};
for (auto &ability_type : type.get<set, nyan::Object*>()) {
// the ability may require per-unit storage, which is created here.
std::unique_ptr<Ability> ability = create_ability(ability_type);
if (attribute.get()) {
unit.add_ability(std::move(ability));
}
}
sim.add(std::move(unit));
}
// loop forever, with the callback below
sim.loop_forever(loop);
}
void loop(Simulation *sim) {
for (auto &unit : sim->get_all_units()) {
nyan::Object *unit_type = unit.type;
// test if the type is a nyan-child of unit. which it very likely is :)
if (unit_type->is_child_of("Unit")) {
int villager_hp = unit_type->get<int>("hp");
log::log(info << "villager has max hp: " << villager_hp);
if (unit->damage > villager_hp) {
unit->die();
}
for (auto &ability : unit->abilities) {
ability->tick(unit);
}
} else {
// wtf internal error
}
}
} |
Sorry, the example not got a bit more advanced than it was initially, but this is probably the first time that the dynamic interaction between nyan and c++ was ever documented. Hope it helps :) |
So the next steps would be:
(I think we should have this kind of task list all over the place) |
We have to do the transition slowly, so your shared attributes are mainly the nyan placeholder. But to simplify the transition, it would be nice if we could start the separation, if needed with placeholders, right now. We can't do the switch instantly because it is too complicated and we slowly have to design the nyan tree structure and engine-shipped definitions. I'm doing my master's thesis about nyan this summer, so it will be done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All in all, I think we should go forward and merge this as it is an improvement to the attribute system which does not seem to complicate things for the future, instead even simplifies them (with the shared and unshared containers).
pls move the attributes class to separate files, then it's good i think. sorry for the delay...
libopenage/unit/attribute.h
Outdated
* Contains a group of attributes. | ||
* Can contain only one attribute of each type. | ||
*/ | ||
class Attributes { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would put the attributes class in a separate file attributes.cpp,h
.
Moved to separate file and rebased. |
👍 |
Attributes system improvements goals:
copy method (???)attr_map_t
into a proper class (Attributes
?)attributes.cpp
(?)reinitialise
instead ofreinitialise
in order to keep the unshared attributes (eg. life)Actions attribute calls optimizemove has/get attribute in constructor (call once and store) (?)move basic operations inside attributesNotes
attributes.h
:unit_type.h
,unit.h
,action.h
Attributes
methods:Bugs found (and solved) on the way