12#include <simplesquirrel/simplesquirrel.hpp>
26 logicalWidth_ =
width;
31 float vx,
float vy,
float vz) {
32 origin_ = glm::vec3(
ox,
oy, oz);
33 axisU_ = glm::vec3(ux, uy, uz);
34 axisV_ = glm::vec3(
vx,
vy,
vz);
38 const glm::vec3
normal = glm::cross(axisU_, axisV_);
41 if (std::abs(denominator) < 1e-6f)
return false;
42 const float t = glm::dot(
normal, origin_ - glm::vec3(
ox,
oy, oz)) / denominator;
43 if (
t < 0.f)
return false;
45 const glm::vec3
relative = resultWorld_ - origin_;
46 const float uu = glm::dot(axisU_, axisU_);
47 const float uv = glm::dot(axisU_, axisV_);
48 const float vv = glm::dot(axisV_, axisV_);
49 const float determinant = uu * vv -
uv *
uv;
50 if (std::abs(determinant) < 1e-8f || logicalWidth_ == 0.f || logicalHeight_ == 0.f)
52 const float wu = glm::dot(
relative, axisU_);
53 const float wv = glm::dot(
relative, axisV_);
54 const float u = (wu * vv - wv *
uv) / determinant;
55 const float v = (wv * uu - wu *
uv) / determinant;
56 resultLogicalX_ = logicalX_ +
u * logicalWidth_;
57 resultLogicalY_ = logicalY_ +
v * logicalHeight_;
62 if (logicalWidth_ == 0.f || logicalHeight_ == 0.f)
return false;
63 const float u = (
x - logicalX_) / logicalWidth_;
64 const float v = (
y - logicalY_) / logicalHeight_;
67 resultWorld_ = origin_ + axisU_ *
u + axisV_ *
v;
75bool parseDefinition(
Value o, std::unordered_map<std::string, CardDefinition> &defs) {
76 std::string
id = o.getString(
"id");
77 if (
id.empty())
return false;
80 def.
name = o.getString(
"name",
id);
81 def.
kind = o.getString(
"kind",
"creature");
82 def.
cost = o.getInt(
"cost");
83 def.
attack = o.getInt(
"attack");
84 def.
health = o.getInt(
"health");
85 def.
tags = o.getStringArray(
"tags");
86 auto tint = o.getFloatArray(
"tint");
93T *resolve(
const ecs::EntityHandle &
h) {
94 return static_cast<T *
>(ecs::try_get(
h));
97bool ownsCard(
const std::vector<ecs::EntityHandle> &handles,
const CardData &card) {
98 const auto live = ecs::handle_of(
const_cast<CardData *
>(&card));
99 return std::any_of(handles.begin(), handles.end(), [&live](
const auto &
handle) {
100 return handle.table == live.table && handle.type == live.type && handle.id == live.id &&
101 handle.generation == live.generation;
105void destroyHandles(std::vector<ecs::EntityHandle> &hs) {
107 if (ecs::Entity *e = ecs::try_get(
h)) ecs::DestroyEntity(e);
113void registerCppEntityClassForScript(
const ssq::Class &
cls) {
116 sq_pushobject(
vm, out.getRaw());
117 ecs::Table *
table = ecs::current();
118 if (table ==
nullptr) {
122 ecs::IComponentManager &cm =
table->getOrCreateManager<T>();
123 auto *reg = cm.getOrCreateRegistryComponentBuffer<T>();
124 std::vector<ecs::IComponentBuffer *> stack;
125 stack.push_back(reg);
126 while (!stack.empty()) {
127 ecs::IComponentBuffer *buf = stack.back();
129 auto *
r =
dynamic_cast<ecs::IRegistryComponentBuffer *
>(buf);
131 for (uint32_t i = 0; i <
r->entity_count(); ++i) {
132 ecs::Entity *ent =
r->entity_at(i);
133 if (ent !=
nullptr && ecs::is_entity_visible(ent)) {
134 ssq::detail::pushByPtr<T>(
vm,
static_cast<T *
>(ent));
135 sq_arrayappend(
vm, -2);
139 if (buf->children !=
nullptr) stack.push_back(buf->children);
140 if (buf->next !=
nullptr) stack.push_back(buf->next);
151Card &Card::operator=(Card &&) noexcept = default;
154 clearGameplayControls();
155 destroyHandles(hands_);
156 destroyHandles(decks_);
157 destroyHandles(zones_);
158 destroyHandles(cards_);
159 activeDeck_ =
nullptr;
160 activeConfig_ =
nullptr;
165 if (instanceId.empty() || ownerId.empty() || hand ==
nullptr)
168 "publishGameplay needs an instance id, an owner id and a hand",
"instanceId"));
171 if (!instance.has_value() || !owner.has_value())
174 "instance and owner ids must be canonical persistent ids",
"instanceId"));
175 if (!gameplay_) gameplay_ = std::make_unique<CardControl>(*
this);
185 if (!instance.has_value())
192 if (gameplay_) gameplay_->clear();
198 std::vector<std::string> result;
199 if (!gameplay_)
return result;
200 for (
const auto &instance : gameplay_->instances()) result.push_back(instance.format());
210 if (!doc.
valid())
return 0;
213 if (
root.isArray()) {
214 for (
size_t i = 0; i <
root.size(); ++i)
215 if (parseDefinition(
root.at(i), defs_)) ++
n;
216 }
else if (
root.isObject()) {
217 if (parseDefinition(
root, defs_)) ++
n;
219 if (
n > 0) definitionViewDirty_ =
true;
225 definitionView_.clear();
226 definitionViewDirty_ =
false;
232 if (definitionViewDirty_) {
233 definitionView_.clear();
234 definitionView_.reserve(defs_.size());
235 for (
const auto &[
id, definition] : defs_) definitionView_.push_back(
id);
236 std::sort(definitionView_.begin(), definitionView_.end());
237 definitionViewDirty_ =
false;
239 return index >= 0 && index < static_cast<int>(definitionView_.size())
240 ? definitionView_[
static_cast<size_t>(
index)]
247 int cost,
int attack,
int health,
float tintR,
float tintG,
float tintB) {
248 if (
id.empty() ||
cost < 0 || attack < 0 || health < 0)
return false;
252 definition.kind =
kind.empty() ?
"creature" :
kind;
253 definition.cost =
cost;
254 definition.attack = attack;
255 definition.health = health;
256 definition.tint = glm::vec3(std::clamp(tintR, 0.f, 1.f), std::clamp(tintG, 0.f, 1.f),
257 std::clamp(tintB, 0.f, 1.f));
258 defs_[
id] = std::move(definition);
259 definitionViewDirty_ =
true;
264 if (defs_.erase(
id) == 0)
return false;
265 definitionViewDirty_ =
true;
270 auto it = defs_.find(
id);
271 if (it == defs_.end())
277 it->second.playCondition = std::move(
condition);
282 auto it = defs_.find(
id);
283 return it == defs_.end() ? nullptr : &it->second;
288 const auto *definition = findDef(
const_cast<CardData *
>(card)->identity()->definitionId);
294 if (!ownsCard(cards_, card))
296 "card does not belong to this Card facade",
"card"));
301 if (!ownsCard(cards_, card))
303 "card does not belong to this Card facade",
"card"));
309 if (!ownsCard(cards_, card))
314 return card.effects()->values.apply(definition,
subject);
318 std::size_t settled = 0;
319 for (
const auto &
handle : cards_) {
320 auto *card = resolve<CardData>(
handle);
321 if (card ==
nullptr || card->effects()->values.count() == 0)
continue;
322 auto updated = card->effects()->values.advance(simulationStep);
324 auto result = std::move(
updated).takeValue();
326 static_cast<double>(card->effects()->values.target().health));
328 settled += result.settled;
337 std::string transactionId) {
338 if (!ownsCard(cards_, card))
341 const auto *definition = findDef(card.identity()->definitionId);
342 if (definition ==
nullptr)
347 request.definition = definition;
348 request.playerAccount = &playerAccount;
349 request.composition = std::move(composition);
350 request.transactionId = std::move(transactionId);
355 const auto *
d = findDef(
id);
356 return d ?
d->name : std::string{};
360 const auto *
d = findDef(
id);
361 return d ?
d->kind : std::string{};
365 const auto *
d = findDef(
id);
366 return d ?
d->cost : 0;
370 const auto *
d = findDef(
id);
371 return d ?
d->attack : 0;
375 const auto *
d = findDef(
id);
376 return d ?
d->health : 0;
380 const auto *
d = findDef(
id);
381 return d ?
d->tint.r : 0.f;
385 const auto *
d = findDef(
id);
386 return d ?
d->tint.g : 0.f;
390 const auto *
d = findDef(
id);
391 return d ?
d->tint.b : 0.f;
399 auto cfg = std::make_unique<LayoutConfig>();
401 configs_.push_back(std::move(cfg));
402 if (!activeConfig_) activeConfig_ = raw;
408 if (!
d)
return nullptr;
410 c->identity()->id = defId +
"#" + std::to_string(nextInstance_++);
411 c->identity()->definitionId = defId;
412 c->identity()->name =
d->name;
413 c->identity()->kind =
d->kind;
414 c->stats()->cost =
d->cost;
415 c->stats()->attack =
d->attack;
416 c->stats()->health =
d->health;
417 c->visual()->tint =
d->tint;
420 attributes.ignore(
"Card::newCard could not seed canonical attributes");
427 target.ignore(
"Card::newCard could not seed the effect target");
432 cards_.push_back(ecs::handle_of(
c));
438 decks_.push_back(ecs::handle_of(
d));
445 auto *R =
z->rect().operator->();
452 zones_.push_back(ecs::handle_of(
z));
458 h->meta()->config = cfg;
459 hands_.push_back(ecs::handle_of(
h));
464 auto mapper = std::make_unique<CardPlaneMapper>();
466 planeMappers_.push_back(std::move(mapper));
475 if (cfg) activeConfig_ = cfg;
483 if (
index < 0 ||
static_cast<size_t>(
index) >= hands_.size())
return nullptr;
484 return resolve<Hand>(hands_[
static_cast<size_t>(
index)]);
488 for (
const auto &
h : hands_) {
489 Hand *hand = resolve<Hand>(
h);
490 if (hand && hand->meta()->owner == owner)
return hand;
498 if (
index < 0 ||
static_cast<size_t>(
index) >= zones_.size())
return nullptr;
499 return resolve<Zone>(zones_[
static_cast<size_t>(
index)]);
506 if (!
h || !activeDeck_)
return nullptr;
508 if (!
c)
return nullptr;
510 c->layout()->x = activeConfig_->
deckX;
511 c->layout()->y = activeConfig_->
deckY;
519 presentation_.clear();
520 presentation_.reserve(cards_.size());
521 for (
const auto &
handle : cards_) {
524 const auto *identity = card->identity().operator->();
525 const auto *
layout = card->layout().operator->();
526 const auto *
state = card->state().operator->();
527 const auto *visual = card->visual().operator->();
528 presentation_.push_back({identity->id,
529 identity->definitionId,
543 return static_cast<int>(presentation_.size());
549 if (
index < 0 ||
static_cast<size_t>(
index) >= presentation_.size())
return nullptr;
550 return &presentation_[
static_cast<size_t>(
index)];
558 std::vector<Zone *> zonePtrs;
559 zonePtrs.reserve(zones_.size());
560 for (
auto &
h : zones_) {
561 if (
Zone *
z = resolve<Zone>(
h)) zonePtrs.push_back(
z);
563 for (
auto &
h : hands_) {
564 if (
Hand *hand = resolve<Hand>(
h)) hand->update(dt, mx, my, down, zonePtrs, events_);
569 if (!builtInVisuals_)
571 if (activeConfig_ && activeConfig_->
showZones) {
572 for (
auto &
h : zones_) {
573 if (
Zone *
z = resolve<Zone>(
h))
z->render(gfx,
true);
576 for (
auto &
h : hands_) {
577 if (
Hand *hand = resolve<Hand>(
h)) hand->render(gfx);
581void Card::beginTargeting(
const std::string &
sourceId,
float x,
float y) {
582 targetingActive_ =
true;
583 targetingValid_ =
false;
585 targetingId_.clear();
586 targetingStartX_ = targetingX_ =
x;
587 targetingStartY_ = targetingY_ =
y;
590void Card::updateTargeting(
float x,
float y,
const std::string &
targetId,
bool valid) {
591 if (!targetingActive_)
596 targetingValid_ =
valid;
599void Card::cancelTargeting() {
600 targetingActive_ =
false;
601 targetingValid_ =
false;
602 targetingSource_.clear();
603 targetingId_.clear();
606void Card::renderTargeting(graphics::Graphics *gfx) {
607 if (!gfx || !targetingActive_)
609 const float dx = targetingX_ - targetingStartX_;
610 const float dy = targetingY_ - targetingStartY_;
614 constexpr float radiansToDegrees = 57.2957795131f;
615 const float angle = std::atan2(
dy,
dx) * radiansToDegrees;
617 : graphics::
Color(1.f, 0.12f, 0.06f, 0.9f);
618 gfx->drawSolidRectRotated(targetingStartX_ +
dx * 0.5f, targetingStartY_ +
dy * 0.5f,
620 gfx->drawSolidRectRotated(targetingX_ - std::cos((
angle - 28.f) / radiansToDegrees) * 12.f,
621 targetingY_ - std::sin((
angle - 28.f) / radiansToDegrees) * 12.f,
623 gfx->drawSolidRectRotated(targetingX_ - std::cos((
angle + 28.f) / radiansToDegrees) * 12.f,
624 targetingY_ - std::sin((
angle + 28.f) / radiansToDegrees) * 12.f,
629 if (!activeDeck_ || !activeConfig_)
return;
631 const int c = activeDeck_->
count();
633 const float w = cfg.
cardW * 0.9f;
634 const float h = cfg.
cardH * 0.9f;
635 for (
int i = 0; i < 3 && i <
c; ++i) {
636 const float off =
static_cast<float>(i) * 3.f;
641 glm::vec4(0.95f, 0.9f, 0.8f, 1.f));
648void Card::clearEvents() { events_.clear(); }
650int Card::getEventCount()
const {
return static_cast<int>(events_.size()); }
652std::string Card::getEventType(
int index)
const {
653 if (
index < 0 ||
static_cast<size_t>(
index) >= events_.size())
return {};
654 return events_[
static_cast<size_t>(
index)].
type;
657std::string Card::getEventHand(
int index)
const {
658 if (
index < 0 ||
static_cast<size_t>(
index) >= events_.size())
return {};
659 return events_[
static_cast<size_t>(
index)].hand;
662std::string Card::getEventZone(
int index)
const {
663 if (
index < 0 ||
static_cast<size_t>(
index) >= events_.size())
return {};
664 return events_[
static_cast<size_t>(
index)].zoneId;
667std::string Card::getEventCardId(
int index)
const {
668 if (
index < 0 ||
static_cast<size_t>(
index) >= events_.size())
return {};
669 return events_[
static_cast<size_t>(
index)].cardId;
672std::string Card::getEventReason(
int index)
const {
673 if (
index < 0 ||
static_cast<size_t>(
index) >= events_.size())
return {};
674 return events_[
static_cast<size_t>(
index)].reason;
681void Card::expose(ssq::Table &table) {
682 const auto vm =
table.getHandle();
687 auto cfgCls =
table.addClass<LayoutConfig>(
688 "LayoutConfig", std::function<LayoutConfig *()>([]() -> LayoutConfig * {
return nullptr; }),
false);
689 cfgCls.addFunc(
"getCardW", [](LayoutConfig *
c) ->
float {
return c ?
c->cardW : 0.f; });
690 cfgCls.addFunc(
"setCardW", [](LayoutConfig *
c,
float v) {
if (
c)
c->cardW =
v; });
691 cfgCls.addFunc(
"getCardH", [](LayoutConfig *
c) ->
float {
return c ?
c->cardH : 0.f; });
692 cfgCls.addFunc(
"setCardH", [](LayoutConfig *
c,
float v) {
if (
c)
c->cardH =
v; });
693 cfgCls.addFunc(
"getSpacing", [](LayoutConfig *
c) ->
float {
return c ?
c->spacing : 0.f; });
694 cfgCls.addFunc(
"setSpacing", [](LayoutConfig *
c,
float v) {
if (
c)
c->spacing =
v; });
695 cfgCls.addFunc(
"getHandX", [](LayoutConfig *
c) ->
float {
return c ?
c->handX : 0.f; });
696 cfgCls.addFunc(
"setHandX", [](LayoutConfig *
c,
float v) {
if (
c)
c->handX =
v; });
697 cfgCls.addFunc(
"getHandY", [](LayoutConfig *
c) ->
float {
return c ?
c->handY : 0.f; });
698 cfgCls.addFunc(
"setHandY", [](LayoutConfig *
c,
float v) {
if (
c)
c->handY =
v; });
699 cfgCls.addFunc(
"getArcHeight", [](LayoutConfig *
c) ->
float {
return c ?
c->arcHeight : 0.f; });
700 cfgCls.addFunc(
"setArcHeight", [](LayoutConfig *
c,
float v) {
if (
c)
c->arcHeight =
v; });
701 cfgCls.addFunc(
"getRotationAngle", [](LayoutConfig *
c) ->
float {
return c ?
c->rotationAngle : 0.f; });
702 cfgCls.addFunc(
"setRotationAngle", [](LayoutConfig *
c,
float v) {
if (
c)
c->rotationAngle =
v; });
703 cfgCls.addFunc(
"getHoverRotation", [](LayoutConfig *
c) ->
bool {
return c ?
c->hoverRotation :
false; });
704 cfgCls.addFunc(
"setHoverRotation", [](LayoutConfig *
c,
bool v) {
if (
c)
c->hoverRotation =
v; });
705 cfgCls.addFunc(
"getHoverScale", [](LayoutConfig *
c) ->
float {
return c ?
c->hoverScale : 0.f; });
706 cfgCls.addFunc(
"setHoverScale", [](LayoutConfig *
c,
float v) {
if (
c)
c->hoverScale =
v; });
707 cfgCls.addFunc(
"getHoverLift", [](LayoutConfig *
c) ->
float {
return c ?
c->hoverLift : 0.f; });
708 cfgCls.addFunc(
"setHoverLift", [](LayoutConfig *
c,
float v) {
if (
c)
c->hoverLift =
v; });
709 cfgCls.addFunc(
"getHoverSpeed", [](LayoutConfig *
c) ->
float {
return c ?
c->hoverSpeed : 0.f; });
710 cfgCls.addFunc(
"setHoverSpeed", [](LayoutConfig *
c,
float v) {
if (
c)
c->hoverSpeed =
v; });
711 cfgCls.addFunc(
"getMotionSpeed", [](LayoutConfig *
c) ->
float {
return c ?
c->motionSpeed : 0.f; });
712 cfgCls.addFunc(
"setMotionSpeed", [](LayoutConfig *
c,
float v) {
if (
c)
c->motionSpeed =
v; });
713 cfgCls.addFunc(
"getDisabledAlpha", [](LayoutConfig *
c) ->
float {
return c ?
c->disabledAlpha : 0.f; });
714 cfgCls.addFunc(
"setDisabledAlpha", [](LayoutConfig *
c,
float v) {
if (
c)
c->disabledAlpha =
v; });
715 cfgCls.addFunc(
"getShowZones", [](LayoutConfig *
c) ->
bool {
return c ?
c->showZones :
false; });
716 cfgCls.addFunc(
"setShowZones", [](LayoutConfig *
c,
bool v) {
if (
c)
c->showZones =
v; });
717 cfgCls.addFunc(
"getDeckX", [](LayoutConfig *
c) ->
float {
return c ?
c->deckX : 0.f; });
718 cfgCls.addFunc(
"setDeckX", [](LayoutConfig *
c,
float v) {
if (
c)
c->deckX =
v; });
719 cfgCls.addFunc(
"getDeckY", [](LayoutConfig *
c) ->
float {
return c ?
c->deckY : 0.f; });
720 cfgCls.addFunc(
"setDeckY", [](LayoutConfig *
c,
float v) {
if (
c)
c->deckY =
v; });
721 cfgCls.addFunc(
"getDragThreshold", [](LayoutConfig *
c) ->
float {
return c ?
c->dragThreshold : 0.f; });
722 cfgCls.addFunc(
"setDragThreshold", [](LayoutConfig *
c,
float v) {
if (
c)
c->dragThreshold =
v; });
724 auto mapperCls =
table.addClass<CardPlaneMapper>(
725 "CardPlaneMapper", std::function<CardPlaneMapper *()>([]() -> CardPlaneMapper * {
739 auto cardCls =
table.addClass<CardData>(
740 "CardData", std::function<CardData *()>([]() -> CardData * {
return nullptr; }),
false);
741 registerCppEntityClassForScript<CardData>(cardCls);
742 cardCls.addFunc(
"getId", [](CardData *
c) -> std::string {
return c ?
c->identity()->
id :
std::
string{}; });
743 cardCls.addFunc(
"getInstanceId", [](CardData *
c) -> std::string {
744 return c ?
c->identity()->id : std::string{};
746 cardCls.addFunc(
"getDefinitionId", [](CardData *
c) -> std::string {
747 return c ?
c->identity()->definitionId : std::string{};
749 cardCls.addFunc(
"setId", [](CardData *
c,
const std::string &
v) {
if (
c)
c->identity()->id =
v; });
750 cardCls.addFunc(
"getName", [](CardData *
c) -> std::string {
return c ?
c->identity()->name : std::string{}; });
751 cardCls.addFunc(
"setName", [](CardData *
c,
const std::string &
v) {
if (
c)
c->identity()->name =
v; });
752 cardCls.addFunc(
"getKind", [](CardData *
c) -> std::string {
return c ?
c->identity()->kind : std::string{}; });
753 cardCls.addFunc(
"setKind", [](CardData *
c,
const std::string &
v) {
if (
c)
c->identity()->kind =
v; });
754 cardCls.addFunc(
"getCost", [](CardData *
c) ->
int {
return c ?
c->stats()->cost : 0; });
755 cardCls.addFunc(
"setCost", [](CardData *
c,
int v) {
if (
c)
c->stats()->cost =
v; });
756 cardCls.addFunc(
"getAttack", [](CardData *
c) ->
int {
return c ?
c->stats()->attack : 0; });
757 cardCls.addFunc(
"setAttack", [](CardData *
c,
int v) {
if (
c)
c->stats()->attack =
v; });
758 cardCls.addFunc(
"getHealth", [](CardData *
c) ->
int {
return c ?
c->stats()->health : 0; });
759 cardCls.addFunc(
"setHealth", [](CardData *
c,
int v) {
if (
c)
c->stats()->health =
v; });
760 cardCls.addFunc(
"configureSettlementRulesJson", [
vm](CardData*
value,
const std::string& json) {
770 value->effects()->values.configureSettlementRules(rules.value()));
772 cardCls.addFunc(
"isFaceUp", [](CardData *
c) ->
bool {
return c ?
c->visual()->faceUp :
true; });
773 cardCls.addFunc(
"setFaceUp", [](CardData *
c,
bool v) {
if (
c)
c->visual()->faceUp =
v; });
774 cardCls.addFunc(
"isDisabled", [](CardData *
c) ->
bool {
return c ?
c->visual()->disabled :
false; });
775 cardCls.addFunc(
"setDisabled", [](CardData *
c,
bool v) {
if (
c)
c->visual()->disabled =
v; });
776 cardCls.addFunc(
"getState", [](CardData *
c) -> std::string {
779 cardCls.addFunc(
"setState", [](CardData *
c,
const std::string &
v) {
781 auto *st =
c->state().operator->();
791 cardCls.addFunc(
"getTintR", [](CardData *
c) ->
float {
return c ?
c->visual()->tint.r : 0.f; });
792 cardCls.addFunc(
"getTintG", [](CardData *
c) ->
float {
return c ?
c->visual()->tint.g : 0.f; });
793 cardCls.addFunc(
"getTintB", [](CardData *
c) ->
float {
return c ?
c->visual()->tint.b : 0.f; });
794 cardCls.addFunc(
"setTint", [](CardData *
c,
float r,
float g,
float b) {
795 if (
c)
c->visual()->tint = glm::vec3(
r,
g,
b);
797 cardCls.addFunc(
"setArt", [](CardData *
c, graphics::Texture *
t) {
if (
c)
c->visual()->texture =
t; });
798 cardCls.addFunc(
"getArt", [](CardData *
c) -> graphics::Texture * {
return c ?
c->visual()->texture :
nullptr; });
799 cardCls.addFunc(
"getX", [](CardData *
c) ->
float {
return c ?
c->layout()->x : 0.f; });
800 cardCls.addFunc(
"getY", [](CardData *
c) ->
float {
return c ?
c->layout()->y : 0.f; });
801 cardCls.addFunc(
"getW", [](CardData *
c) ->
float {
return c ?
c->layout()->w : 0.f; });
802 cardCls.addFunc(
"getH", [](CardData *
c) ->
float {
return c ?
c->layout()->h : 0.f; });
803 cardCls.addFunc(
"getAngle", [](CardData *
c) ->
float {
return c ?
c->layout()->angle : 0.f; });
804 cardCls.addFunc(
"getScale", [](CardData *
c) ->
float {
return c ?
c->layout()->scale : 0.f; });
805 cardCls.addFunc(
"getAlpha", [](CardData *
c) ->
float {
return c ?
c->layout()->alpha : 0.f; });
806 cardCls.addFunc(
"isHovered", [](CardData *
c) ->
bool {
return c ?
c->state()->hovered :
false; });
807 cardCls.addFunc(
"isDragging", [](CardData *
c) ->
bool {
return c ?
c->state()->dragging :
false; });
808 cardCls.addFunc(
"hit", [](CardData *
c,
float px,
float py) ->
bool {
return c &&
c->hit(
px,
py); });
809 cardCls.addFunc(
"describe", [](CardData *
c) -> std::string {
return c ?
c->describe() : std::string{}; });
813 "Deck", std::function<Deck *()>([]() -> Deck * {
return nullptr; }),
false);
814 registerCppEntityClassForScript<Deck>(deckCls);
826 "Zone", std::function<Zone *()>([]() -> Zone * {
return nullptr; }),
false);
827 registerCppEntityClassForScript<Zone>(zoneCls);
828 zoneCls.addFunc(
"getId", [](Zone *
z) -> std::string {
return z ?
z->rect()->
id :
std::
string{}; });
829 zoneCls.addFunc(
"setId", [](Zone *
z,
const std::string &
v) {
if (
z)
z->rect()->id =
v; });
830 zoneCls.addFunc(
"getLabel", [](Zone *
z) -> std::string {
return z ?
z->rect()->label : std::string{}; });
831 zoneCls.addFunc(
"setLabel", [](Zone *
z,
const std::string &
v) {
if (
z)
z->rect()->label =
v; });
832 zoneCls.addFunc(
"getX", [](Zone *
z) ->
float {
return z ?
z->rect()->x : 0.f; });
833 zoneCls.addFunc(
"getY", [](Zone *
z) ->
float {
return z ?
z->rect()->y : 0.f; });
834 zoneCls.addFunc(
"getW", [](Zone *
z) ->
float {
return z ?
z->rect()->w : 0.f; });
835 zoneCls.addFunc(
"getH", [](Zone *
z) ->
float {
return z ?
z->rect()->h : 0.f; });
836 zoneCls.addFunc(
"setRect", [](Zone *
z,
float x,
float y,
float w,
float h) {
838 auto *
R =
z->rect().operator->();
839 R->x =
x;
R->y =
y;
R->w =
w;
R->h =
h;
841 zoneCls.addFunc(
"getColorR", [](Zone *
z) ->
float {
return z ?
z->rect()->color.r : 0.f; });
842 zoneCls.addFunc(
"getColorG", [](Zone *
z) ->
float {
return z ?
z->rect()->color.g : 0.f; });
843 zoneCls.addFunc(
"getColorB", [](Zone *
z) ->
float {
return z ?
z->rect()->color.b : 0.f; });
844 zoneCls.addFunc(
"setColor", [](Zone *
z,
float r,
float g,
float b) {
845 if (
z)
z->rect()->color = glm::vec3(
r,
g,
b);
847 zoneCls.addFunc(
"getAlpha", [](Zone *
z) ->
float {
return z ?
z->rect()->alpha : 0.f; });
848 zoneCls.addFunc(
"setAlpha", [](Zone *
z,
float v) {
if (
z)
z->rect()->alpha =
v; });
849 zoneCls.addFunc(
"isEnabled", [](Zone *
z) ->
bool {
return z ?
z->rect()->enabled :
false; });
850 zoneCls.addFunc(
"setEnabled", [](Zone *
z,
bool v) {
if (
z)
z->rect()->enabled =
v; });
851 zoneCls.addFunc(
"addAcceptKind", [](Zone *
z,
const std::string &k) {
if (
z)
z->filter()->acceptKinds.push_back(k); });
852 zoneCls.addFunc(
"clearAcceptKinds", [](Zone *
z) {
if (
z)
z->filter()->acceptKinds.clear(); });
853 zoneCls.addFunc(
"accepts", [](Zone *
z, CardData *
c) ->
bool {
return z &&
z->accepts(
c); });
854 zoneCls.addFunc(
"contains", [](Zone *
z,
float px,
float py) ->
bool {
return z &&
z->contains(
px,
py); });
855 zoneCls.addFunc(
"render", [](Zone *
z, graphics::Graphics *gfx,
bool showLabel) {
856 if (
z)
z->render(gfx, showLabel);
861 "Hand", std::function<Hand *()>([]() -> Hand * {
return nullptr; }),
false);
862 registerCppEntityClassForScript<Hand>(handCls);
863 handCls.addFunc(
"getOwner", [](Hand *
h) -> std::string {
return h ?
h->meta()->owner :
std::
string{}; });
864 handCls.addFunc(
"setOwner", [](Hand *
h,
const std::string &
v) {
if (
h)
h->meta()->owner =
v; });
865 handCls.addFunc(
"getConfig", [](Hand *
h) -> LayoutConfig * {
return h ?
h->meta()->config :
nullptr; });
866 handCls.addFunc(
"setConfig", [](Hand *
h, LayoutConfig *
c) {
if (
h)
h->meta()->config =
c; });
867 handCls.addFunc(
"isFaceDown", [](Hand *
h) ->
bool {
return h ?
h->meta()->faceDown :
false; });
868 handCls.addFunc(
"setFaceDown", [](Hand *
h,
bool v) {
if (
h)
h->meta()->faceDown =
v; });
869 handCls.addFunc(
"isPeek", [](Hand *
h) ->
bool {
return h ?
h->meta()->peek :
false; });
870 handCls.addFunc(
"setPeek", [](Hand *
h,
bool v) {
if (
h)
h->meta()->peek =
v; });
871 handCls.addFunc(
"isInteractive", [](Hand *
h) ->
bool {
return h ?
h->meta()->interactive :
true; });
872 handCls.addFunc(
"setInteractive", [](Hand *
h,
bool v) {
if (
h)
h->meta()->interactive =
v; });
880 handCls.addFunc(
"render", [](Hand *
h, graphics::Graphics *gfx) {
if (
h)
h->render(gfx); });
882 auto snapshotCls =
table.addClass<CardPresentationSnapshot>(
883 "CardPresentationSnapshot",
884 std::function<CardPresentationSnapshot *()>([]() -> CardPresentationSnapshot * {
887 snapshotCls.addFunc(
"getInstanceId", [](CardPresentationSnapshot *
s) {
return s ?
s->instanceId : std::string{}; });
888 snapshotCls.addFunc(
"getDefinitionId", [](CardPresentationSnapshot *
s) {
return s ?
s->definitionId : std::string{}; });
889 snapshotCls.addFunc(
"getState", [](CardPresentationSnapshot *
s) {
return s ?
s->state : std::string{}; });
890 snapshotCls.addFunc(
"getX", [](CardPresentationSnapshot *
s) {
return s ?
s->x : 0.f; });
891 snapshotCls.addFunc(
"getY", [](CardPresentationSnapshot *
s) {
return s ?
s->y : 0.f; });
892 snapshotCls.addFunc(
"getW", [](CardPresentationSnapshot *
s) {
return s ?
s->width : 0.f; });
893 snapshotCls.addFunc(
"getH", [](CardPresentationSnapshot *
s) {
return s ?
s->height : 0.f; });
894 snapshotCls.addFunc(
"getAngle", [](CardPresentationSnapshot *
s) {
return s ?
s->angle : 0.f; });
895 snapshotCls.addFunc(
"getScale", [](CardPresentationSnapshot *
s) {
return s ?
s->scale : 0.f; });
896 snapshotCls.addFunc(
"getAlpha", [](CardPresentationSnapshot *
s) {
return s ?
s->alpha : 0.f; });
897 snapshotCls.addFunc(
"isHovered", [](CardPresentationSnapshot *
s) {
return s &&
s->hovered; });
898 snapshotCls.addFunc(
"isDragging", [](CardPresentationSnapshot *
s) {
return s &&
s->dragging; });
899 snapshotCls.addFunc(
"isDisabled", [](CardPresentationSnapshot *
s) {
return s &&
s->disabled; });
900 snapshotCls.addFunc(
"isFaceUp", [](CardPresentationSnapshot *
s) {
return s &&
s->faceUp; });
903void Card::expose(ssq::Class &
cls) {
941 cls.addFunc(
"beginTargeting", &Card::beginTargeting);
942 cls.addFunc(
"updateTargeting", &Card::updateTargeting);
943 cls.addFunc(
"cancelTargeting", &Card::cancelTargeting);
944 cls.addFunc(
"renderTargeting", &Card::renderTargeting);
945 cls.addFunc(
"isTargeting", &Card::isTargeting);
946 cls.addFunc(
"isTargetValid", &Card::isTargetValid);
947 cls.addFunc(
"getTargetSource", &Card::getTargetSource);
948 cls.addFunc(
"getTargetId", &Card::getTargetId);
950 cls.addFunc(
"clearEvents", &Card::clearEvents);
951 cls.addFunc(
"getEventCount", &Card::getEventCount);
952 cls.addFunc(
"getEventType", &Card::getEventType);
953 cls.addFunc(
"getEventHand", &Card::getEventHand);
954 cls.addFunc(
"getEventZone", &Card::getEventZone);
955 cls.addFunc(
"getEventCardId", &Card::getEventCardId);
956 cls.addFunc(
"getEventReason", &Card::getEventReason);
961 cls.addFunc(
"publishGameplay", [
vm =
cls.getHandle()](
Card *self,
const std::string &instanceId,
962 const std::string &ownerId, Hand *hand) {
963 ssq::Table result(vm);
964 if (self == nullptr || hand == nullptr) {
965 result.set(
"ok", false);
966 result.set(
"message", std::string(
"publishGameplay needs the card module and a hand"));
969 const auto published = self->publishGameplay(instanceId, ownerId, hand,
nullptr);
970 result.set(
"ok", published.ok());
971 result.set(
"message", published.ok() ? std::string(
"published") : published.
status().describe());
974 cls.addFunc(
"unpublishGameplay", [
vm =
cls.getHandle()](
Card *self,
const std::string &instanceId) {
975 ssq::Table result(
vm);
978 : self->unpublishGameplay(instanceId);
979 result.set(
"ok", unpublished.ok());
980 result.set(
"message", unpublished.ok() ? std::string(
"unpublished") : unpublished.
status().describe());
Selective canonical attributes for card combat values.
struct SQVM * HSQUIRRELVM
eve::resource::CostSpec cost
eve::resource::IResourceAccount * account
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
#define Module_IMPL(ModuleName, newExpr)
std::array< std::uint64_t, kPixelChunkSize *kPixelChunkSize > updated
RoadLaneDirection direction
The single Squirrel projection for common Result, Status and Value.
Stable reference to a runtime gameplay subject.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Strong, domain-neutral reference to a runtime subject.
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
The canonical owning dynamic value used by data-facing protocols.
static eve::Result< void > ensure(CardData &card)
Bind and seed the card's selected attributes exactly once.
static constexpr std::string_view healthAttribute
static eve::Result< double > read(CardData &card, std::string_view attribute)
Read a selected final card attribute and refresh compatibility fields.
static eve::Result< void > setBase(CardData &card, std::string_view attribute, double value)
Set a selected canonical base and refresh the legacy projection.
单张卡牌:ECS 实体,数据拆成 Identity / Stats / Visual / Layout / State。
static CardData * createCard()
创建并触摸全部组件。
Maps a logical card canvas to a 3D parallelogram without depending on graphics.
float getWorldZ() const
Return the most recently mapped world Z coordinate.
void setLogicalRect(float x, float y, float width, float height)
Configure the logical rectangle represented by the world plane.
bool mapLayout(float x, float y)
Map a logical point to the configured world plane.
float getLogicalY() const
Return the most recently mapped logical Y coordinate.
bool mapRay(float ox, float oy, float oz, float dx, float dy, float dz)
Intersect a world ray and store its logical and world hit coordinates.
float getWorldY() const
Return the most recently mapped world Y coordinate.
void setPlane(float ox, float oy, float oz, float ux, float uy, float uz, float vx, float vy, float vz)
Configure world origin plus full-width U and full-height V vectors.
float getLogicalX() const
Return the most recently mapped logical X coordinate.
float getWorldX() const
Return the most recently mapped world X coordinate.
static decision::ConditionResult evaluate(const CardData *card, const CardDefinition &definition, const decision::Condition &condition, CardPlayConditionQueries queries={})
Evaluate a card definition's play condition against one instance.
static eve::Result< eve::transaction::TransactionReceipt > play(CardPlayRequest request)
Execute condition, movement, card effect and Mana as one transaction.
卡牌模块入口(eve.Card):定义注册、对象工厂与每帧 update/render。
Card()
Declared out of line so the incomplete CardControl member needs no deleter here.
int getCardDefinitionCost(const std::string &id)
Returns the card definition cost.
float getCardDefinitionTintB(const std::string &id)
Returns the card definition tint b.
int zoneCount() const
Zone count.
void renderDeck(graphics::Graphics *gfx)
Renders deck.
LayoutConfig * getConfig() const
Returns the config.
CardData * drawCard(const std::string &handOwner)
从牌库抽一张牌加入指定手牌;无牌时返回 nullptr。
eve::Result< void > setCardPlayCondition(const std::string &id, decision::Condition condition)
Replace the side-effect-free condition required to play a definition.
void setBuiltInVisuals(bool enabled)
Enable or disable the built-in card visuals while retaining layout and interaction.
decision::ConditionResult evaluatePlay(const CardData *card, CardPlayConditionQueries queries={}) const
Evaluate a card's pure play condition through the shared protocol.
int getPresentationCount() const
Return the number of entries in the most recently captured buffer.
int registerCardsFromJson(const std::string &json)
从 JSON 注册卡牌类型;返回成功注册数量。
void render(graphics::Graphics *gfx)
绘制手牌/落牌区(与牌库)。
int handCount() const
Hand count.
bool getBuiltInVisuals() const
Return whether Card::render draws the built-in card faces.
bool hasCardDefinition(const std::string &id)
卡牌类型查询。
LayoutConfig * newConfig()
工厂:对象由 ECS 表持有,脚本持有的是非拥有句柄。
void update(float dt, float mx, float my, bool down)
每帧更新布局与交互。
eve::Result< void > setCardAttribute(CardData &card, std::string_view attribute, double value) const
Set one canonical combat attribute through the Card facade.
std::vector< std::string > gameplayInstances() const
已发布的玩法实例标识(发布顺序)。
Hand * newHand(LayoutConfig *cfg)
创建一个手牌布局。
eve::Result< effects::EffectHandle > applyEffect(CardData &card, const CardEffectDefinition &definition, eve::SubjectRef subject) const
Apply a domain card effect to the card-owned lifecycle component.
float getCardDefinitionTintG(const std::string &id)
Returns the card definition tint g.
void setConfig(LayoutConfig *cfg)
游戏状态:当前布局、手牌、落牌区、牌库。
eve::Result< eve::transaction::TransactionReceipt > play(CardData &card, eve::resource::IResourceAccount &playerAccount, CardPlayComposition composition={}, std::string transactionId={})
Play a card through the facade's condition, container and payment transaction.
int getCardDefinitionHealth(const std::string &id)
Returns the card definition health.
eve::Result< void > unpublishGameplay(const std::string &instanceId)
取消发布一个实例;该实例未发布(或 id 非法)时返回诊断。
eve::Result< void > publishGameplay(const std::string &instanceId, const std::string &ownerId, Hand *hand, eve::resource::IResourceAccount *account=nullptr)
把一副手牌发布到共享玩法协议(eve_gameplay / MCP)。
Hand * findHand(const std::string &owner) const
Finds hand.
void clearGameplayControls()
取消发布本模块持有的全部玩法实例。
CardPlaneMapper * newPlaneMapper()
Create a reusable logical-card-canvas to 3D plane mapper.
int gameplayControlCount() const
已发布的玩法实例数量。
Hand * getHand(int index) const
Returns the hand.
int getCardDefinitionCount()
已注册卡牌类型数量。
Deck * getDeck() const
Returns the deck.
bool removeCardDefinition(const std::string &id)
Remove one definition by stable identifier.
int getCardDefinitionAttack(const std::string &id)
Returns the card definition attack.
std::string getCardDefinitionId(int index) const
Return a definition ID in deterministic sorted order.
int capturePresentation()
Capture all live cards into a contiguous presentation snapshot buffer.
eve::Result< std::size_t > step(const eve::SimulationStep &step)
Advance all card-owned effects and project current health.
bool setCardDefinition(const std::string &id, const std::string &name, const std::string &kind, int cost, int attack, int health, float tintR, float tintG, float tintB)
Insert or replace one definition for project-owned authoring tools.
float getCardDefinitionTintR(const std::string &id)
Returns the card definition tint r.
Deck * newDeck()
Creates a deck. @ownership Caller deletes unless documented otherwise.
std::string getCardDefinitionName(const std::string &id)
void clearCardDefinitions()
清空全部卡牌类型定义。
Zone * getZone(int index) const
Returns the zone.
Zone * newZone(const std::string &id, const std::string &label, float x, float y, float w, float h)
创建一个落牌区。
CardData * newCard(const std::string &defId)
Creates a card. @ownership Caller deletes unless documented otherwise.
CardPresentationSnapshot * getPresentation(int index)
Return an entry from the most recently captured buffer, or nullptr.
std::string getCardDefinitionKind(const std::string &id)
Returns the card definition kind.
eve::Result< double > getCardAttribute(CardData &card, std::string_view attribute) const
Read one canonical combat attribute through the Card facade.
CardData * get(int index)
CardData * peek()
查看栈顶卡牌(不弹出)。
void push(CardData *c)
将卡牌压入栈顶(末尾)。
CardData * draw()
弹出栈顶卡牌(末尾);空牌库返回 nullptr。
static Deck * createDeck()
创建并触摸 Membership。
int count()
牌库数量/是否为空/按下标取牌。
手牌:扇形布局 + 悬浮 + 拖拽 + 落区判定 + 渲染。
CardData * get(int index)
static Hand * createHand()
创建并触摸全部组件。
bool removeCard(CardData *c)
CardData * pick(float px, float py)
命中检测:返回 (px,py) 处最上层卡牌。
void addCard(CardData *c)
手牌增删与查询。
CardData * find(const std::string &id)
落牌区(手牌区 / 出牌区 / 弃牌区),用于拖放命中判定。
static Zone * createZone()
创建并触摸组件。
Explain one condition evaluation in a UI- and log-friendly form.
static ConditionResult failed(ConditionReasonCode reason, Value evidence={}, Value details=Value::Object{})
Construct a rejected result with a stable reason and explanation.
Immutable condition AST node.
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Font * getFont() const
Returns the font.
EVENGINE_API_FOUNDATION public API.
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
EVENGINE_API_FOUNDATION public API.
Provider-neutral resource account contract.
static eve::Result< SettlementRuleSet > fromJson(std::string_view json)
Decode and compile a strict versioned JSON rule document without mutating an existing rule set.
void renderCardBack(graphics::Graphics *gfx, float x, float y, float w, float h, float angle, float a)
绘制牌背。
const char * cardStateName(CardState state)
状态枚举的字符串名(用于脚本/调试)。
void printCentered(graphics::Graphics *gfx, const std::string &text, float cx, float cy, float scale, const glm::vec4 &color)
渲染辅助(也供 Zone / Deck 复用)。
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
std::unordered_map< std::string, SkillDefinition > & table()
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
void registerCppEntityView(const ssq::Class &cls, CppEntityViewFn fn)
Registers cpp entity view.
glm::vec4 Color
Render-neutral RGBA color shared by graphics-facing modules.
One deterministic fixed-step emitted by SimulationClock.
卡牌类型定义(registerCardsFromJson 注册)。
std::vector< std::string > tags
Definition tags available to HasTag nodes.
Strong card definition projected into the common effect definition.
Mutable card combat state changed by the card effect executor.
Optional cross-domain operations that belong to one card play.
Read-only extension points used while evaluating a card play.
Complete caller-owned input for one atomic card play.
Immutable presentation data captured from one card for external 2D/3D adapters.
UiCard 风格的布局配置(脚本可实时修改,对应 UiCard 的 Configs 面板)。