mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-17 04:31:37 -04:00
initial item service implementation
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class fopAc_ac_c;
|
||||
|
||||
namespace dusk::mods {
|
||||
|
||||
uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver);
|
||||
uint8_t item_check_tagged(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver);
|
||||
|
||||
uint8_t item_check_chest(uint8_t boxNo, uint8_t itemNo, fopAc_ac_c* chest);
|
||||
uint8_t item_check_boss(uint8_t itemNo, fopAc_ac_c* boss);
|
||||
uint8_t item_check_freestanding(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* item);
|
||||
uint8_t item_check_poe(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* poe);
|
||||
uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver);
|
||||
uint8_t item_check_bug(uint8_t insectId, uint8_t itemNo, fopAc_ac_c* agitha);
|
||||
uint8_t item_check_sky_character(uint8_t itemNo, fopAc_ac_c* statue);
|
||||
|
||||
uint32_t item_give_tag(const char* name);
|
||||
uint32_t item_give_tag_chest(uint8_t boxNo);
|
||||
uint32_t item_give_tag_boss();
|
||||
uint32_t item_give_tag_freestanding(uint8_t bitNo);
|
||||
uint32_t item_give_tag_poe(uint8_t bitNo);
|
||||
uint32_t item_give_tag_shop(uint8_t itemNo);
|
||||
uint32_t item_give_tag_bug(uint8_t insectId);
|
||||
uint32_t item_give_tag_sky_character();
|
||||
|
||||
void item_check_enqueue(const char* name, uint8_t itemNo);
|
||||
void item_check_enqueue_poe(uint8_t bitNo, uint8_t itemNo);
|
||||
|
||||
void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver);
|
||||
|
||||
bool item_give_queue_dispatching();
|
||||
uint32_t item_give_queue_take_tag();
|
||||
|
||||
} // namespace dusk::mods
|
||||
@@ -0,0 +1,294 @@
|
||||
#include "item.hpp"
|
||||
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "dusk/mods/svc/item.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "d/d_com_inf_game.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods {
|
||||
namespace {
|
||||
|
||||
aurora::Module Log{"dusk::mods::item_checks"};
|
||||
|
||||
struct CheckOverride {
|
||||
std::string name;
|
||||
uint8_t itemNo = 0;
|
||||
};
|
||||
|
||||
struct CheckResolver {
|
||||
ItemCheckHandle handle = 0;
|
||||
std::string name;
|
||||
ItemCheckResolveFn fn = nullptr;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
struct ModItemChecks {
|
||||
std::vector<CheckOverride> overrides;
|
||||
std::vector<CheckResolver> resolvers;
|
||||
};
|
||||
|
||||
struct PendingResolve {
|
||||
LoadedMod* mod = nullptr;
|
||||
bool fixedValue = false;
|
||||
uint8_t itemNo = 0;
|
||||
ItemCheckResolveFn fn = nullptr;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
std::unordered_map<LoadedMod*, ModItemChecks> s_modChecks;
|
||||
std::unordered_set<std::string> s_warnedCollisions;
|
||||
ItemCheckHandle s_nextCheckHandle = 1;
|
||||
|
||||
const char* current_stage_name() {
|
||||
const char* stageName = dComIfGp_getStartStageName();
|
||||
return stageName != nullptr ? stageName : "";
|
||||
}
|
||||
|
||||
std::string chest_check_name(uint8_t boxNo) {
|
||||
return fmt::format("chest:{}:{}", current_stage_name(), boxNo);
|
||||
}
|
||||
|
||||
std::string boss_check_name() {
|
||||
return fmt::format("boss:{}", current_stage_name());
|
||||
}
|
||||
|
||||
std::string freestanding_check_name(uint8_t bitNo) {
|
||||
return fmt::format("freestanding:{}:{}", current_stage_name(), bitNo);
|
||||
}
|
||||
|
||||
std::string poe_check_name(uint8_t bitNo) {
|
||||
return fmt::format("poe:{}:{}", current_stage_name(), bitNo);
|
||||
}
|
||||
|
||||
std::string shop_check_name(uint8_t itemNo) {
|
||||
return fmt::format("shop:{}:{}", current_stage_name(), itemNo);
|
||||
}
|
||||
|
||||
std::string bug_check_name(uint8_t insectId) {
|
||||
return fmt::format("bug:{}", insectId);
|
||||
}
|
||||
|
||||
std::string sky_character_check_name() {
|
||||
return fmt::format("skychar:{}:{}", current_stage_name(), dStage_roomControl_c::getStayNo());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
|
||||
if (name == nullptr || *name == '\0' || s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
|
||||
// Callbacks may change registrations, so copy the applicable chain before invoking one.
|
||||
std::vector<PendingResolve> resolves;
|
||||
LoadedMod* previousOverrideOwner = nullptr;
|
||||
for (auto& mod : ModLoader::instance().mods()) {
|
||||
if (!mod.active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto modIt = s_modChecks.find(&mod);
|
||||
if (modIt == s_modChecks.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& checkOverride : modIt->second.overrides) {
|
||||
if (checkOverride.name != name) {
|
||||
continue;
|
||||
}
|
||||
if (previousOverrideOwner != nullptr && s_warnedCollisions.emplace(name).second) {
|
||||
Log.warn("check '{}' is overridden by [{}] and [{}]; [{}] wins by load order", name,
|
||||
previousOverrideOwner->metadata.id, mod.metadata.id, mod.metadata.id);
|
||||
}
|
||||
previousOverrideOwner = &mod;
|
||||
resolves.push_back({.mod = &mod, .fixedValue = true, .itemNo = checkOverride.itemNo});
|
||||
}
|
||||
|
||||
for (const auto& resolver : modIt->second.resolvers) {
|
||||
if (resolver.name.empty() || resolver.name == name) {
|
||||
resolves.push_back({.mod = &mod, .fn = resolver.fn, .userData = resolver.userData});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemCheckInfo info{
|
||||
.name = name,
|
||||
.giver_actor = giver,
|
||||
.vanilla_item = itemNo,
|
||||
.current_item = itemNo,
|
||||
};
|
||||
for (const auto& resolve : resolves) {
|
||||
if (!resolve.mod->active) {
|
||||
continue;
|
||||
}
|
||||
if (resolve.fixedValue) {
|
||||
info.current_item = resolve.itemNo;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t resolvedItem = info.current_item;
|
||||
try {
|
||||
if (resolve.fn(resolve.mod->context.get(), &info, &resolvedItem, resolve.userData)) {
|
||||
info.current_item = resolvedItem;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
fail_mod(*resolve.mod, MOD_ERROR,
|
||||
fmt::format("Exception in item check resolver for '{}': {}", name, e.what()));
|
||||
} catch (...) {
|
||||
fail_mod(*resolve.mod, MOD_ERROR,
|
||||
fmt::format("Unknown exception in item check resolver for '{}'", name));
|
||||
}
|
||||
}
|
||||
return info.current_item;
|
||||
}
|
||||
|
||||
uint8_t item_check_chest(uint8_t boxNo, uint8_t itemNo, fopAc_ac_c* chest) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = chest_check_name(boxNo);
|
||||
return item_check(name.c_str(), itemNo, chest);
|
||||
}
|
||||
|
||||
uint8_t item_check_boss(uint8_t itemNo, fopAc_ac_c* boss) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = boss_check_name();
|
||||
return item_check(name.c_str(), itemNo, boss);
|
||||
}
|
||||
|
||||
uint8_t item_check_freestanding(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* item) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = freestanding_check_name(bitNo);
|
||||
return item_check(name.c_str(), itemNo, item);
|
||||
}
|
||||
|
||||
uint8_t item_check_poe(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* poe) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = poe_check_name(bitNo);
|
||||
return item_check(name.c_str(), itemNo, poe);
|
||||
}
|
||||
|
||||
uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = shop_check_name(itemNo);
|
||||
return item_check(name.c_str(), itemNo, giver);
|
||||
}
|
||||
|
||||
uint8_t item_check_bug(uint8_t insectId, uint8_t itemNo, fopAc_ac_c* agitha) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = bug_check_name(insectId);
|
||||
return item_check(name.c_str(), itemNo, agitha);
|
||||
}
|
||||
|
||||
uint8_t item_check_sky_character(uint8_t itemNo, fopAc_ac_c* statue) {
|
||||
if (s_modChecks.empty()) {
|
||||
return itemNo;
|
||||
}
|
||||
const auto name = sky_character_check_name();
|
||||
return item_check(name.c_str(), itemNo, statue);
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_chest(uint8_t boxNo) {
|
||||
return item_give_tag(chest_check_name(boxNo).c_str());
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_boss() {
|
||||
return item_give_tag(boss_check_name().c_str());
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_freestanding(uint8_t bitNo) {
|
||||
return item_give_tag(freestanding_check_name(bitNo).c_str());
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_poe(uint8_t bitNo) {
|
||||
return item_give_tag(poe_check_name(bitNo).c_str());
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_shop(uint8_t itemNo) {
|
||||
return item_give_tag(shop_check_name(itemNo).c_str());
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_bug(uint8_t insectId) {
|
||||
return item_give_tag(bug_check_name(insectId).c_str());
|
||||
}
|
||||
|
||||
uint32_t item_give_tag_sky_character() {
|
||||
return item_give_tag(sky_character_check_name().c_str());
|
||||
}
|
||||
|
||||
void item_check_enqueue_poe(uint8_t bitNo, uint8_t itemNo) {
|
||||
item_check_enqueue(poe_check_name(bitNo).c_str(), itemNo);
|
||||
}
|
||||
|
||||
namespace svc {
|
||||
|
||||
ModResult item_check_set_override(LoadedMod& mod, const char* name, uint8_t itemNo) {
|
||||
auto& checks = s_modChecks[&mod];
|
||||
for (auto& checkOverride : checks.overrides) {
|
||||
if (checkOverride.name == name) {
|
||||
checkOverride.itemNo = itemNo;
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
checks.overrides.push_back({.name = name, .itemNo = itemNo});
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult item_check_clear_override(LoadedMod& mod, const char* name) {
|
||||
const auto modIt = s_modChecks.find(&mod);
|
||||
if (modIt == s_modChecks.end()) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto removed = std::erase_if(modIt->second.overrides,
|
||||
[&](const auto& checkOverride) { return checkOverride.name == name; });
|
||||
return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ModResult item_check_add_resolver(LoadedMod& mod, const char* name, ItemCheckResolveFn fn,
|
||||
void* userData, ItemCheckHandle& outHandle) {
|
||||
auto& resolver = s_modChecks[&mod].resolvers.emplace_back();
|
||||
resolver.handle = s_nextCheckHandle++;
|
||||
resolver.name = name != nullptr ? name : "";
|
||||
resolver.fn = fn;
|
||||
resolver.userData = userData;
|
||||
outHandle = resolver.handle;
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult item_check_remove_resolver(LoadedMod& mod, ItemCheckHandle handle) {
|
||||
const auto modIt = s_modChecks.find(&mod);
|
||||
if (modIt == s_modChecks.end()) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto removed = std::erase_if(
|
||||
modIt->second.resolvers, [&](const auto& resolver) { return resolver.handle == handle; });
|
||||
return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
void item_checks_remove_mod(LoadedMod& mod) {
|
||||
s_modChecks.erase(&mod);
|
||||
s_warnedCollisions.clear();
|
||||
}
|
||||
|
||||
} // namespace svc
|
||||
} // namespace dusk::mods
|
||||
@@ -0,0 +1,343 @@
|
||||
#include "item.hpp"
|
||||
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "dusk/mods/svc/item.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_item.h"
|
||||
#include "d/d_item_data.h"
|
||||
#include "f_op/f_op_actor_mng.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <deque>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods {
|
||||
namespace {
|
||||
|
||||
aurora::Module Log{"dusk::mods::item_gives"};
|
||||
|
||||
// deque keeps previously returned c_str pointers valid if a callback interns another name.
|
||||
std::deque<std::string> s_giveNames;
|
||||
std::unordered_map<std::string, uint32_t> s_giveNameIds;
|
||||
|
||||
const char* item_give_name(uint32_t tag) {
|
||||
if (tag == 0 || tag > s_giveNames.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return s_giveNames[tag - 1].c_str();
|
||||
}
|
||||
|
||||
struct GiveObserver {
|
||||
ItemGiveHandle handle = 0;
|
||||
ItemGiveObserveFn fn = nullptr;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
struct PendingObserver {
|
||||
LoadedMod* mod = nullptr;
|
||||
ItemGiveObserveFn fn = nullptr;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
std::unordered_map<LoadedMod*, std::vector<GiveObserver>> s_modObservers;
|
||||
ItemGiveHandle s_nextGiveHandle = 1;
|
||||
size_t s_observerCount = 0;
|
||||
|
||||
void notify_gives(const char* checkName, uint8_t itemNo, fopAc_ac_c* giver, ItemGiveOrigin origin) {
|
||||
if (s_observerCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Callbacks may change registrations, so copy them before invoking one.
|
||||
std::vector<PendingObserver> observers;
|
||||
for (auto& mod : ModLoader::instance().mods()) {
|
||||
if (!mod.active) {
|
||||
continue;
|
||||
}
|
||||
const auto modIt = s_modObservers.find(&mod);
|
||||
if (modIt == s_modObservers.end()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& observer : modIt->second) {
|
||||
observers.push_back({.mod = &mod, .fn = observer.fn, .userData = observer.userData});
|
||||
}
|
||||
}
|
||||
|
||||
const ItemGiveInfo info{
|
||||
.check_name = checkName,
|
||||
.giver_actor = giver,
|
||||
.item = itemNo,
|
||||
.origin = static_cast<uint8_t>(origin),
|
||||
};
|
||||
for (const auto& observer : observers) {
|
||||
if (!observer.mod->active) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
observer.fn(observer.mod->context.get(), &info, observer.userData);
|
||||
} catch (const std::exception& e) {
|
||||
fail_mod(*observer.mod, MOD_ERROR,
|
||||
fmt::format("Exception in item give observer: {}", e.what()));
|
||||
} catch (...) {
|
||||
fail_mod(*observer.mod, MOD_ERROR, "Unknown exception in item give observer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t kGiveQueueLimit = 64;
|
||||
constexpr int kGiveMaxRetries = 5;
|
||||
|
||||
struct QueuedGive {
|
||||
LoadedMod* owner = nullptr;
|
||||
uint32_t tag = 0;
|
||||
uint8_t itemNo = 0;
|
||||
bool silent = false;
|
||||
bool resolveAtDispatch = false;
|
||||
};
|
||||
|
||||
std::deque<QueuedGive> s_giveQueue;
|
||||
QueuedGive s_inFlightGive{};
|
||||
uint8_t s_inFlightItem = 0;
|
||||
int s_inFlightRetries = 0;
|
||||
bool s_inFlight = false;
|
||||
bool s_inFlightSpawned = false;
|
||||
bool s_dispatchingSilent = false;
|
||||
|
||||
bool safe_to_dispatch() {
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure player is in a safe action and not already in an event before dispatching
|
||||
switch (link->mProcID) {
|
||||
case daAlink_c::PROC_WAIT:
|
||||
case daAlink_c::PROC_TIRED_WAIT:
|
||||
case daAlink_c::PROC_MOVE:
|
||||
case daAlink_c::PROC_WOLF_WAIT:
|
||||
case daAlink_c::PROC_WOLF_TIRED_WAIT:
|
||||
case daAlink_c::PROC_WOLF_MOVE:
|
||||
case daAlink_c::PROC_ATN_MOVE:
|
||||
case daAlink_c::PROC_WOLF_ATN_AC_MOVE:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (link->checkEventRun()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int itemId = 0;
|
||||
return link->mMsgFlow.getEventId(&itemId) == 0;
|
||||
}
|
||||
|
||||
bool resolve_queued_give(const QueuedGive& give, ItemGiveOrigin origin, uint8_t& outItem) {
|
||||
outItem = give.itemNo;
|
||||
if (give.resolveAtDispatch) {
|
||||
outItem = item_check(item_give_name(give.tag), give.itemNo, nullptr);
|
||||
}
|
||||
if (outItem != dItemNo_NONE_e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
notify_gives(item_give_name(give.tag), dItemNo_NONE_e, nullptr, origin);
|
||||
return false;
|
||||
}
|
||||
|
||||
void dispatch_silent_give(const QueuedGive& give) {
|
||||
uint8_t itemNo = 0;
|
||||
if (!resolve_queued_give(give, ITEM_GIVE_ORIGIN_QUEUE_SILENT, itemNo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug("dispatching silent item {:#x} for '{}'", itemNo,
|
||||
item_give_name(give.tag) != nullptr ? item_give_name(give.tag) : "");
|
||||
s_dispatchingSilent = true;
|
||||
execItemGet(itemNo, give.tag, nullptr);
|
||||
s_dispatchingSilent = false;
|
||||
}
|
||||
|
||||
void dispatch_demo_give() {
|
||||
Log.debug("dispatching item {:#x} for '{}'", s_inFlightItem,
|
||||
item_give_name(s_inFlightGive.tag) != nullptr ? item_give_name(s_inFlightGive.tag) : "");
|
||||
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
dComIfGp_getEvent()->setGtItm(s_inFlightItem);
|
||||
link->mProcID = daAlink_c::PROC_GET_ITEM;
|
||||
const s16 eventIndex = dComIfGp_getEventManager().getEventIdx(link, "DEFAULT_GETITEM", 0xFF);
|
||||
fopAcM_orderChangeEventId(link, eventIndex, 1, 0xFFFF);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t item_give_tag(const char* name) {
|
||||
if (name == nullptr || *name == '\0') {
|
||||
return 0;
|
||||
}
|
||||
if (const auto it = s_giveNameIds.find(name); it != s_giveNameIds.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
s_giveNames.emplace_back(name);
|
||||
const auto tag = static_cast<uint32_t>(s_giveNames.size());
|
||||
s_giveNameIds.emplace(s_giveNames.back(), tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
uint8_t item_check_tagged(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver) {
|
||||
const char* name = item_give_name(giveTag);
|
||||
return name != nullptr ? item_check(name, itemNo, giver) : itemNo;
|
||||
}
|
||||
|
||||
void item_check_enqueue(const char* name, uint8_t itemNo) {
|
||||
if (s_giveQueue.size() >= kGiveQueueLimit) {
|
||||
Log.warn("item give queue is full; dropping check '{}'", name != nullptr ? name : "");
|
||||
return;
|
||||
}
|
||||
s_giveQueue.push_back({
|
||||
.tag = item_give_tag(name),
|
||||
.itemNo = itemNo,
|
||||
.resolveAtDispatch = true,
|
||||
});
|
||||
}
|
||||
|
||||
void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver) {
|
||||
ItemGiveOrigin origin = ITEM_GIVE_ORIGIN_GAME;
|
||||
if (s_dispatchingSilent) {
|
||||
origin = ITEM_GIVE_ORIGIN_QUEUE_SILENT;
|
||||
} else if (s_inFlight && itemNo == s_inFlightItem && giveTag == s_inFlightGive.tag) {
|
||||
origin = ITEM_GIVE_ORIGIN_QUEUE;
|
||||
s_inFlight = false;
|
||||
s_inFlightSpawned = false;
|
||||
}
|
||||
notify_gives(item_give_name(giveTag), itemNo, giver, origin);
|
||||
}
|
||||
|
||||
bool item_give_queue_dispatching() {
|
||||
return s_inFlight && !s_inFlightSpawned;
|
||||
}
|
||||
|
||||
uint32_t item_give_queue_take_tag() {
|
||||
if (!item_give_queue_dispatching()) {
|
||||
return 0;
|
||||
}
|
||||
s_inFlightSpawned = true;
|
||||
return s_inFlightGive.tag;
|
||||
}
|
||||
|
||||
namespace svc {
|
||||
|
||||
void item_gives_tick() {
|
||||
if ((!s_inFlight && s_giveQueue.empty()) || !safe_to_dispatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_inFlight) {
|
||||
if (++s_inFlightRetries > kGiveMaxRetries) {
|
||||
Log.error("item {:#x} for '{}' did not complete after {} attempts; dropping it",
|
||||
s_inFlightItem,
|
||||
item_give_name(s_inFlightGive.tag) != nullptr ? item_give_name(s_inFlightGive.tag) :
|
||||
"",
|
||||
kGiveMaxRetries);
|
||||
s_inFlight = false;
|
||||
s_inFlightSpawned = false;
|
||||
return;
|
||||
}
|
||||
s_inFlightSpawned = false;
|
||||
dispatch_demo_give();
|
||||
return;
|
||||
}
|
||||
|
||||
while (!s_giveQueue.empty() && s_giveQueue.front().silent) {
|
||||
const QueuedGive give = s_giveQueue.front();
|
||||
s_giveQueue.pop_front();
|
||||
dispatch_silent_give(give);
|
||||
}
|
||||
if (s_giveQueue.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QueuedGive give = s_giveQueue.front();
|
||||
uint8_t itemNo = 0;
|
||||
if (!resolve_queued_give(give, ITEM_GIVE_ORIGIN_QUEUE, itemNo)) {
|
||||
s_giveQueue.pop_front();
|
||||
return;
|
||||
}
|
||||
|
||||
s_giveQueue.pop_front();
|
||||
s_inFlightGive = give;
|
||||
s_inFlightItem = itemNo;
|
||||
s_inFlightRetries = 0;
|
||||
s_inFlight = true;
|
||||
s_inFlightSpawned = false;
|
||||
dispatch_demo_give();
|
||||
}
|
||||
|
||||
void item_gives_clear() {
|
||||
if (!s_giveQueue.empty() || s_inFlight) {
|
||||
Log.info("dropping {} pending item give(s)",
|
||||
s_giveQueue.size() + static_cast<size_t>(s_inFlight));
|
||||
}
|
||||
s_giveQueue.clear();
|
||||
s_inFlight = false;
|
||||
s_inFlightSpawned = false;
|
||||
}
|
||||
|
||||
ModResult item_give_enqueue(LoadedMod& mod, const char* checkName, uint8_t itemNo, uint32_t flags) {
|
||||
if (s_giveQueue.size() >= kGiveQueueLimit) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
s_giveQueue.push_back({
|
||||
.owner = &mod,
|
||||
.tag = item_give_tag(checkName),
|
||||
.itemNo = itemNo,
|
||||
.silent = (flags & ITEM_GIVE_SILENT) != 0,
|
||||
.resolveAtDispatch = (flags & ITEM_GIVE_RESOLVE) != 0,
|
||||
});
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult item_give_add_observer(
|
||||
LoadedMod& mod, ItemGiveObserveFn fn, void* userData, ItemGiveHandle& outHandle) {
|
||||
auto& observer = s_modObservers[&mod].emplace_back();
|
||||
observer.handle = s_nextGiveHandle++;
|
||||
observer.fn = fn;
|
||||
observer.userData = userData;
|
||||
outHandle = observer.handle;
|
||||
++s_observerCount;
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult item_give_remove_observer(LoadedMod& mod, ItemGiveHandle handle) {
|
||||
const auto modIt = s_modObservers.find(&mod);
|
||||
if (modIt == s_modObservers.end()) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto removed = std::erase_if(
|
||||
modIt->second, [&](const auto& observer) { return observer.handle == handle; });
|
||||
s_observerCount -= removed;
|
||||
return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
void item_gives_remove_mod(LoadedMod& mod) {
|
||||
if (const auto modIt = s_modObservers.find(&mod); modIt != s_modObservers.end()) {
|
||||
s_observerCount -= modIt->second.size();
|
||||
s_modObservers.erase(modIt);
|
||||
}
|
||||
std::erase_if(s_giveQueue, [&](const QueuedGive& give) { return give.owner == &mod; });
|
||||
if (s_inFlight && s_inFlightGive.owner == &mod) {
|
||||
// The event system already owns this grant, so it can no longer be canceled safely.
|
||||
s_inFlightGive.owner = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace svc
|
||||
} // namespace dusk::mods
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "item.hpp"
|
||||
|
||||
#include "registry.hpp"
|
||||
|
||||
#include "dusk/mods/item.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
|
||||
#include "d/d_item_data.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kMaxCheckNameLength = 256;
|
||||
constexpr uint32_t kGiveFlagMask = ITEM_GIVE_SILENT | ITEM_GIVE_RESOLVE;
|
||||
|
||||
bool is_valid_check_name(const char* name) {
|
||||
if (name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const std::string_view view{name};
|
||||
return !view.empty() && view.size() <= kMaxCheckNameLength;
|
||||
}
|
||||
|
||||
ModResult item_set_check_override(ModContext* context, const char* name, uint8_t itemNo) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_check_name(name)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return item_check_set_override(*mod, name, itemNo);
|
||||
}
|
||||
|
||||
ModResult item_clear_check_override(ModContext* context, const char* name) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_check_name(name)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return item_check_clear_override(*mod, name);
|
||||
}
|
||||
|
||||
ModResult item_set_check_resolver(ModContext* context, const char* name, ItemCheckResolveFn fn,
|
||||
void* userData, ItemCheckHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || fn == nullptr || (name != nullptr && !is_valid_check_name(name))) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ItemCheckHandle handle = 0;
|
||||
const auto result = item_check_add_resolver(*mod, name, fn, userData, handle);
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = handle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult item_clear_check_resolver(ModContext* context, ItemCheckHandle handle) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return item_check_remove_resolver(*mod, handle);
|
||||
}
|
||||
|
||||
ModResult item_resolve_check(
|
||||
ModContext* context, const char* name, uint8_t originalItemNo, uint8_t* outItem) {
|
||||
if (mod_from_context(context) == nullptr || !is_valid_check_name(name) || outItem == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
*outItem = item_check(name, originalItemNo, nullptr);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult item_give_item(
|
||||
ModContext* context, const char* checkName, uint8_t itemNo, uint32_t flags) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || (checkName != nullptr && !is_valid_check_name(checkName)) ||
|
||||
(flags & ~kGiveFlagMask) != 0)
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
if ((flags & ITEM_GIVE_RESOLVE) != 0) {
|
||||
if (checkName == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
} else if (itemNo == dItemNo_NONE_e) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return item_give_enqueue(*mod, checkName, itemNo, flags);
|
||||
}
|
||||
|
||||
ModResult item_observe_gives(
|
||||
ModContext* context, ItemGiveObserveFn fn, void* userData, ItemGiveHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || fn == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ItemGiveHandle handle = 0;
|
||||
const auto result = item_give_add_observer(*mod, fn, userData, handle);
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = handle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult item_unobserve_gives(ModContext* context, ItemGiveHandle handle) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return item_give_remove_observer(*mod, handle);
|
||||
}
|
||||
|
||||
constexpr ItemService s_itemService{
|
||||
.header = SERVICE_HEADER(ItemService, ITEM_SERVICE_MAJOR, ITEM_SERVICE_MINOR),
|
||||
.set_check_override = item_set_check_override,
|
||||
.clear_check_override = item_clear_check_override,
|
||||
.set_check_resolver = item_set_check_resolver,
|
||||
.clear_check_resolver = item_clear_check_resolver,
|
||||
.resolve_check = item_resolve_check,
|
||||
.give_item = item_give_item,
|
||||
.observe_gives = item_observe_gives,
|
||||
.unobserve_gives = item_unobserve_gives,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
constinit const ServiceModule g_itemModule{
|
||||
.id = ITEM_SERVICE_ID,
|
||||
.majorVersion = ITEM_SERVICE_MAJOR,
|
||||
.minorVersion = ITEM_SERVICE_MINOR,
|
||||
.service = &s_itemService,
|
||||
.modDetached =
|
||||
[](LoadedMod& mod) {
|
||||
item_checks_remove_mod(mod);
|
||||
item_gives_remove_mod(mod);
|
||||
},
|
||||
.frameEnd = item_gives_tick,
|
||||
.shutdown = item_gives_clear,
|
||||
};
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "mods/svc/item.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace dusk::mods {
|
||||
|
||||
struct LoadedMod;
|
||||
|
||||
namespace svc {
|
||||
|
||||
ModResult item_check_set_override(LoadedMod& mod, const char* name, uint8_t itemNo);
|
||||
ModResult item_check_clear_override(LoadedMod& mod, const char* name);
|
||||
ModResult item_check_add_resolver(LoadedMod& mod, const char* name, ItemCheckResolveFn fn,
|
||||
void* userData, ItemCheckHandle& outHandle);
|
||||
ModResult item_check_remove_resolver(LoadedMod& mod, ItemCheckHandle handle);
|
||||
void item_checks_remove_mod(LoadedMod& mod);
|
||||
|
||||
ModResult item_give_enqueue(LoadedMod& mod, const char* checkName, uint8_t itemNo, uint32_t flags);
|
||||
ModResult item_give_add_observer(
|
||||
LoadedMod& mod, ItemGiveObserveFn fn, void* userData, ItemGiveHandle& outHandle);
|
||||
ModResult item_give_remove_observer(LoadedMod& mod, ItemGiveHandle handle);
|
||||
void item_gives_remove_mod(LoadedMod& mod);
|
||||
void item_gives_tick();
|
||||
void item_gives_clear();
|
||||
|
||||
} // namespace svc
|
||||
} // namespace dusk::mods
|
||||
@@ -213,6 +213,7 @@ void ModLoader::init_services() {
|
||||
&svc::g_gfxModule,
|
||||
&svc::g_saveModule,
|
||||
&svc::g_stageModule,
|
||||
&svc::g_itemModule,
|
||||
})
|
||||
{
|
||||
svc::register_module(*module);
|
||||
|
||||
@@ -75,5 +75,6 @@ extern const ServiceModule g_windowModule;
|
||||
extern const ServiceModule g_gfxModule;
|
||||
extern const ServiceModule g_saveModule;
|
||||
extern const ServiceModule g_stageModule;
|
||||
extern const ServiceModule g_itemModule;
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "save.hpp"
|
||||
|
||||
#include "item.hpp"
|
||||
#include "registry.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
@@ -164,6 +165,7 @@ void save_slot_new(uint32_t slot) {
|
||||
store.mods.clear();
|
||||
store.snapshotValid = false;
|
||||
s_currentSlot = static_cast<int32_t>(slot);
|
||||
item_gives_clear();
|
||||
Log.info("new save in slot {}; mod blob store cleared", slot);
|
||||
notify(slot, &SaveObserverRecord::onNewSave, "new-save");
|
||||
}
|
||||
@@ -183,6 +185,7 @@ void save_slot_loaded(uint32_t slot, const void* slotData) {
|
||||
}
|
||||
}
|
||||
s_currentSlot = static_cast<int32_t>(slot);
|
||||
item_gives_clear();
|
||||
notify(slot, &SaveObserverRecord::onLoaded, "save-loaded");
|
||||
}
|
||||
|
||||
@@ -222,6 +225,7 @@ void save_slot_erased(uint32_t slot) {
|
||||
|
||||
void save_no_slot() {
|
||||
s_currentSlot = -1;
|
||||
item_gives_clear();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Reference in New Issue
Block a user