fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
+57
-33
@@ -84,6 +84,61 @@ static void combat_data(const Node& root, Msa& out) {
|
||||
}
|
||||
}
|
||||
|
||||
static void motion_event(const Node& ev, MotionEvent& e) {
|
||||
e.type = ev.inum("MotionEventType");
|
||||
e.start_time = ev.num("StartingTime");
|
||||
e.duration_time = ev.num("DuringTime");
|
||||
e.effect_file = ev.str("EffectFileName");
|
||||
e.sound_file = ev.str("SoundFileName");
|
||||
e.attaching_bone = ev.str("AttachingBoneName");
|
||||
e.attaching = ev.inum("AttachingEnable") != 0;
|
||||
e.following = ev.inum("FollowingEnable") != 0;
|
||||
e.independent = ev.inum("IndependentFlag") != 0;
|
||||
e.fishing_effect = ev.inum("FishingEffectFlag") != 0;
|
||||
vec3(ev.find("EffectPosition"), e.position);
|
||||
|
||||
// 40250 TMotionEventDataFly has its own resource and offset fields.
|
||||
e.fly_file = ev.str("FlyFileName");
|
||||
e.fly_attaching_bone = ev.str("AttachingBoneName");
|
||||
e.fly_attaching = ev.inum("AttachingEnable") != 0;
|
||||
vec3(ev.find("FlyPosition"), e.fly_position);
|
||||
|
||||
// 40250 TScreenWavingEventData defaults AffectingRange to zero and reads
|
||||
// DuringTime/Power from the event itself.
|
||||
e.power = ev.inum("Power");
|
||||
e.affecting_range = ev.inum("AffectingRange");
|
||||
|
||||
// 40250 TMotionAttackingEventData defaults EnableHitProcess to TRUE.
|
||||
e.enable_hit_process = ev.inum("EnableHitProcess", 1) != 0;
|
||||
e.attack_type = ev.inum("AttackType", ev.inum("AttackingType"));
|
||||
e.hitting_type = ev.inum("HittingType");
|
||||
e.stiffen_time = ev.num("StiffenTime");
|
||||
e.invisible_time = ev.num("InvisibleTime");
|
||||
e.external_force = ev.num("ExternalForce");
|
||||
e.hit_limit_count = ev.inum("HitLimitCount");
|
||||
e.collision_type = ev.inum("CollisionType");
|
||||
for (const Node& g : ev.groups) {
|
||||
if (g.name.size() < 10 || g.name.compare(0, 10, "SphereData") != 0)
|
||||
continue;
|
||||
MotionEvent::CollisionSphere sphere;
|
||||
sphere.radius = g.num("Radius");
|
||||
vec3(g.find("Position"), sphere.position);
|
||||
e.collision_spheres.push_back(sphere);
|
||||
}
|
||||
}
|
||||
|
||||
static void motion_events(const Node& root, Msa& out) {
|
||||
const Node* med = root.group("MotionEventData");
|
||||
if (!med) return;
|
||||
int count = med->inum("MotionEventDataCount", int(med->groups.size()));
|
||||
(void)count;
|
||||
for (const Node& ev : med->groups) {
|
||||
MotionEvent e;
|
||||
motion_event(ev, e);
|
||||
out.events.push_back(std::move(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_msa(const std::string& text, Msa& out, std::string* err) {
|
||||
Node root;
|
||||
if (!parse_textscript(text, root, err)) return false;
|
||||
@@ -95,24 +150,7 @@ bool parse_msa(const std::string& text, Msa& out, std::string* err) {
|
||||
loop_data(root, out);
|
||||
combat_data(root, out);
|
||||
|
||||
if (const Node* med = root.group("MotionEventData")) {
|
||||
int count = med->inum("MotionEventDataCount", int(med->groups.size()));
|
||||
(void)count;
|
||||
for (const Node& ev : med->groups) {
|
||||
MotionEvent e;
|
||||
e.type = ev.inum("MotionEventType");
|
||||
e.start_time = ev.num("StartingTime");
|
||||
e.effect_file = ev.str("EffectFileName");
|
||||
e.sound_file = ev.str("SoundFileName");
|
||||
e.attaching_bone = ev.str("AttachingBoneName");
|
||||
e.attaching = ev.inum("AttachingEnable") != 0;
|
||||
e.following = ev.inum("FollowingEnable") != 0;
|
||||
e.independent = ev.inum("IndependentFlag") != 0;
|
||||
e.fishing_effect = ev.inum("FishingEffectFlag") != 0;
|
||||
vec3(ev.find("EffectPosition"), e.position);
|
||||
out.events.push_back(std::move(e));
|
||||
}
|
||||
}
|
||||
motion_events(root, out);
|
||||
|
||||
if (out.motion_gr2.empty()) { if (err) *err = "msa: 缺 MotionFileName"; return false; }
|
||||
return true;
|
||||
@@ -128,21 +166,7 @@ bool parse_msa_file(const std::string& path, Msa& out, std::string* err) {
|
||||
vec3(root.find("Accumulation"), out.accumulation);
|
||||
loop_data(root, out);
|
||||
combat_data(root, out);
|
||||
if (const Node* med = root.group("MotionEventData"))
|
||||
for (const Node& ev : med->groups) {
|
||||
MotionEvent e;
|
||||
e.type = ev.inum("MotionEventType");
|
||||
e.start_time = ev.num("StartingTime");
|
||||
e.effect_file = ev.str("EffectFileName");
|
||||
e.sound_file = ev.str("SoundFileName");
|
||||
e.attaching_bone = ev.str("AttachingBoneName");
|
||||
e.attaching = ev.inum("AttachingEnable") != 0;
|
||||
e.following = ev.inum("FollowingEnable") != 0;
|
||||
e.independent = ev.inum("IndependentFlag") != 0;
|
||||
e.fishing_effect = ev.inum("FishingEffectFlag") != 0;
|
||||
vec3(ev.find("EffectPosition"), e.position);
|
||||
out.events.push_back(std::move(e));
|
||||
}
|
||||
motion_events(root, out);
|
||||
if (out.motion_gr2.empty()) { if (err) *err = "msa: 缺 MotionFileName (" + path + ")"; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
+33
-6
@@ -6,16 +6,43 @@
|
||||
namespace fmt {
|
||||
|
||||
struct MotionEvent {
|
||||
int type = 0; // MotionEventType(10=effect, 4/5=sound, ...)
|
||||
float start_time = 0; // StartingTime
|
||||
std::string effect_file; // EffectFileName(type 10)
|
||||
int type = 0; // MotionEventType(1=effect, 4=special attack, 6=fly, ...)
|
||||
float start_time = 0; // StartingTime
|
||||
float duration_time = 0; // DuringTime(SCREEN_WAVING / SPECIAL_ATTACKING)
|
||||
std::string effect_file; // EffectFileName(type 1/10)
|
||||
std::string sound_file; // SoundFileName
|
||||
std::string attaching_bone;
|
||||
bool attaching = false;
|
||||
bool following = false;
|
||||
bool independent = false;
|
||||
bool fishing_effect = false;
|
||||
float position[3] = {0, 0, 0};
|
||||
bool independent = false;
|
||||
bool fishing_effect = false;
|
||||
float position[3] = {0, 0, 0};
|
||||
|
||||
// FLY 的字段不能复用 EFFECT 的 EffectFileName/EffectPosition:40250
|
||||
// 在 MotionEventDataEvent.h 中使用另一套 FlyFileName/FlyPosition。
|
||||
std::string fly_file;
|
||||
std::string fly_attaching_bone;
|
||||
bool fly_attaching = false;
|
||||
float fly_position[3] = {0, 0, 0};
|
||||
|
||||
// SCREEN_WAVING 参数(GameEventManager::ProcessEventScreenWaving)。
|
||||
int power = 0;
|
||||
int affecting_range = 0;
|
||||
|
||||
// SPECIAL_ATTACKING 参数(TMotionAttackingEventData)。
|
||||
bool enable_hit_process = true; // 40250 缺省值为 TRUE
|
||||
int attack_type = 0;
|
||||
int hitting_type = 0;
|
||||
float stiffen_time = 0;
|
||||
float invisible_time = 0;
|
||||
float external_force = 0;
|
||||
int hit_limit_count = 0;
|
||||
int collision_type = 0;
|
||||
struct CollisionSphere {
|
||||
float radius = 0;
|
||||
float position[3] = {0, 0, 0};
|
||||
};
|
||||
std::vector<CollisionSphere> collision_spheres;
|
||||
};
|
||||
|
||||
struct Msa {
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace fmt {
|
||||
static bool fill(const Node& root, Msm& out, std::string* err) {
|
||||
out = Msm{};
|
||||
out.base_model_gr2 = root.str("BaseModelFileName");
|
||||
out.motion_list_file = root.str("MotionListFileName");
|
||||
if (const Node* hd = root.group("HairData")) {
|
||||
out.hair_path = hd->str("PathName");
|
||||
for (const Node& h : hd->groups) {
|
||||
|
||||
@@ -15,6 +15,7 @@ struct HairEntry {
|
||||
|
||||
struct Msm {
|
||||
std::string base_model_gr2; // BaseModelFileName
|
||||
std::string motion_list_file; // MotionListFileName
|
||||
std::string hair_path; // HairData.PathName
|
||||
std::vector<HairEntry> hairs;
|
||||
// 武器 / 时装挂点 / 材质类型按需补(PoC 先到发型 + base)
|
||||
|
||||
@@ -31,6 +31,96 @@ Group MotionEventData
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
// 40250 RaceMotionDataEvent has dedicated payloads for FLY,
|
||||
// SCREEN_WAVING and SPECIAL_ATTACKING. Keep the no-payload event
|
||||
// types in the same snapshot so they cannot be silently dropped.
|
||||
const std::string events = R"(MotionFileName "events.gr2"
|
||||
Group MotionEventData
|
||||
{
|
||||
Group Event00
|
||||
{
|
||||
MotionEventType 2
|
||||
StartingTime 0.10
|
||||
DuringTime 0.20
|
||||
Power 300
|
||||
AffectingRange 200
|
||||
}
|
||||
Group Event01
|
||||
{
|
||||
MotionEventType 4
|
||||
StartingTime 0.30
|
||||
DuringTime 0.40
|
||||
EnableHitProcess 0
|
||||
AttackType 1
|
||||
HittingType 2
|
||||
StiffenTime 0.05
|
||||
InvisibleTime 0.10
|
||||
ExternalForce 20
|
||||
HitLimitCount 3
|
||||
CollisionType 4
|
||||
SphereDataCount 2
|
||||
Group SphereData00
|
||||
{
|
||||
Radius 100
|
||||
Position 1 2 3
|
||||
}
|
||||
Group SphereData01
|
||||
{
|
||||
Radius 200
|
||||
Position 4 5 6
|
||||
}
|
||||
}
|
||||
Group Event02
|
||||
{
|
||||
MotionEventType 6
|
||||
StartingTime 0.50
|
||||
AttachingEnable 1
|
||||
AttachingBoneName "Bip01 R Hand"
|
||||
FlyFileName "d:/ymir work/pc/warrior/effect/geompung.msf"
|
||||
FlyPosition 7 8 9
|
||||
}
|
||||
Group Event03
|
||||
{
|
||||
MotionEventType 7
|
||||
StartingTime 0.60
|
||||
}
|
||||
Group Event04
|
||||
{
|
||||
MotionEventType 8
|
||||
StartingTime 0.70
|
||||
}
|
||||
Group Event05
|
||||
{
|
||||
MotionEventType 9
|
||||
StartingTime 0.80
|
||||
}
|
||||
}
|
||||
)";
|
||||
fmt::Msa parsed;
|
||||
std::string error;
|
||||
if (!fmt::parse_msa(events, parsed, &error) || parsed.events.size() != 6) {
|
||||
std::fprintf(stderr, "motion event payload parse failed: %s\n", error.c_str());
|
||||
return 1;
|
||||
}
|
||||
const fmt::MotionEvent *wave = &parsed.events[0];
|
||||
const fmt::MotionEvent *attack = &parsed.events[1];
|
||||
const fmt::MotionEvent *fly = &parsed.events[2];
|
||||
if (wave->type != 2 || std::fabs(wave->duration_time - 0.2f) > 1e-6f ||
|
||||
wave->power != 300 || wave->affecting_range != 200 ||
|
||||
attack->type != 4 || attack->enable_hit_process || attack->attack_type != 1 ||
|
||||
attack->hitting_type != 2 || attack->collision_type != 4 ||
|
||||
attack->collision_spheres.size() != 2 ||
|
||||
std::fabs(attack->collision_spheres[1].radius - 200.0f) > 1e-6f ||
|
||||
attack->collision_spheres[1].position[2] != 6.0f ||
|
||||
fly->type != 6 || fly->fly_file.find("geompung.msf") == std::string::npos ||
|
||||
!fly->fly_attaching || fly->fly_attaching_bone != "Bip01 R Hand" ||
|
||||
fly->fly_position[0] != 7.0f || parsed.events[3].type != 7 ||
|
||||
parsed.events[4].type != 8 || parsed.events[5].type != 9) {
|
||||
std::fprintf(stderr, "40250 motion event payload fields were lost\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
const std::string text = R"(
|
||||
ScriptType MotionData
|
||||
MotionFileName "d:/ymir work/pc/test.gr2"
|
||||
|
||||
@@ -10,6 +10,7 @@ int main() {
|
||||
ScriptType RaceDataScript
|
||||
|
||||
BaseModelFileName "d:/ymir work/pc2/warrior/warrior_novice.GR2"
|
||||
MotionListFileName "motlist_range.txt"
|
||||
|
||||
Group HairData
|
||||
{
|
||||
@@ -56,6 +57,7 @@ Group HairData
|
||||
check(m.base_model_gr2 == "d:/ymir work/pc2/warrior/warrior_novice.GR2" ||
|
||||
m.base_model_gr2 == "d:/ymir work/pc2/warrior/warrior_novice.gr2",
|
||||
"base model gr2");
|
||||
check(m.motion_list_file == "motlist_range.txt", "motion list file");
|
||||
check(m.hair_path == "d:/ymir Work/pc2/warrior/" || m.hair_path == "d:/ymir work/pc2/warrior/",
|
||||
"hair PathName");
|
||||
check(m.hairs.size() == 3, "3 hair entries");
|
||||
|
||||
Reference in New Issue
Block a user