Skip to content

Commit f235c1b

Browse files
committed
semi-tested progress
1 parent 4d9f480 commit f235c1b

File tree

3 files changed

+194
-8
lines changed

3 files changed

+194
-8
lines changed

DenizenModelsConverter/PackMaker.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public static void BuildPack(string rawModelPath, BBModel model, string item, st
8686
JObject skippedEntry = new();
8787
skippedEntry.Add("name", outline.Name);
8888
skippedEntry.Add("empty", true);
89+
skippedEntry.Add("pairs", new JArray(outline.Paired.Select(p => p.ToString()).ToArray()));
8990
modelSet.Add(outline.UUID.ToString(), skippedEntry);
9091
continue;
9192
}
@@ -116,9 +117,45 @@ public static void BuildPack(string rawModelPath, BBModel model, string item, st
116117
itemOverride.Remove("overrides");
117118
itemOverride.Add("overrides", overrides);
118119
File.WriteAllText(itemFilePath, itemOverride.ToString());
119-
Debug("Overrides done. Building Denizen file...");
120+
Debug("Overrides done. Building animation data...");
121+
JObject animations = new();
122+
foreach (BBModel.Animation animation in model.Animations)
123+
{
124+
Debug($"Start processing animation {animation.Name}");
125+
if (animations.ContainsKey(animation.Name))
126+
{
127+
throw new Exception($"Cannot output functional model - duplicate animation name '{animation.Name}'");
128+
}
129+
JObject jAnimation = new();
130+
jAnimation.Add("loop", animation.Loop.ToString());
131+
jAnimation.Add("override", animation.Override);
132+
jAnimation.Add("anim_time_update", animation.AnimTimeUpdate);
133+
jAnimation.Add("blend_weight", animation.BlendWeight);
134+
jAnimation.Add("length", animation.Length);
135+
JObject jAnimators = new();
136+
foreach (BBModel.Animation.Animator animator in animation.Animators)
137+
{
138+
JObject jAnimator = new();
139+
JArray keyframes = new();
140+
foreach (BBModel.Animation.Keyframe frame in animator.Keyframes)
141+
{
142+
JObject jFrame = new();
143+
jFrame.Add("channel", frame.Channel.ToString());
144+
jFrame.Add("data", frame.DataPoint.ToDenizenString());
145+
jFrame.Add("time", frame.Time);
146+
jFrame.Add("interpolation", frame.Interpolation.ToString());
147+
keyframes.Add(jFrame);
148+
}
149+
jAnimator.Add("frames", keyframes);
150+
jAnimators.Add(animator.UUID.ToString(), jAnimator);
151+
}
152+
jAnimation.Add("animators", jAnimators);
153+
animations.Add(animation.Name, jAnimation);
154+
}
155+
Debug("Animations done. Building Denizen file...");
120156
JObject denizenFile = new();
121157
denizenFile.Add("models", modelSet);
158+
denizenFile.Add("animations", animations);
122159
File.WriteAllText($"{rawModelPath.Replace(".bbmodel", "")}.dmodel.yml", denizenFile.ToString());
123160
Console.WriteLine("Exported full bbmodel.");
124161
}

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ This takes the form of two components:
1313
- takes the `.bbmodel` file and converts it into two output files
1414
- A resource pack for clients
1515
- A `.dmodel.yml` file for the Minecraft server to read
16-
- Planned to eventually be replaced by entirely on-server Denizen scripts eventually
16+
- Planned to eventually be replaced by entirely on-server Denizen scripts eventually, with automated pack location selection and all, instead of the manual process with external programs
17+
- USAGE: Command line!
18+
- `./DenizenModelsConverter.exe make_pack [bbmodel_file] [pack_path] [model_path] [texture_path]` Puts a model into a resource pack, must specify model and texture path within the pack.
19+
- Example: `./DenizenModelsConverter.exe make_pack goat.bbmodel creaturepack creatures/goat creatures/goat` This example parses a 'goat' model and puts it in reasonable paths, using by default `arrow` as the item to add onto.
1720
- The Denizen script
1821
- `scripts/` directory `.dsc` files
1922
- Runs on your minecraft server using [Denizen](https://github.com/DenizenScript/Denizen)

scripts/dmodels.dsc

Lines changed: 152 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ model_part_stand:
77
marker: true
88
gravity: false
99
visible: false
10-
custom_name_visible: true
11-
custom_name: part
1210

1311
spawn_model:
1412
type: task
@@ -23,13 +21,161 @@ spawn_model:
2321
- stop
2422
- ~yaml id:<[yamlid]> load:<[filename]>
2523
- define parts <yaml[<[yamlid]>].read[models]>
24+
- flag server dmodels_data.model_<[model_name]>:<[parts]>
25+
- flag server dmodels_data.animations_<[model_name]>:<yaml[<[yamlid]>].read[animations]||<map>>
2626
- yaml unload id:<[yamlid]>
27+
- spawn model_part_stand <[location]> save:root
28+
- flag <entry[root].spawned_entity> dmodel_model_id:<[model_name]>
2729
- foreach <[parts]> key:id as:part:
2830
- if <[part.empty]||false>:
2931
- foreach next
3032
- define rots <[part.rotation].split[,].parse[to_radians]>
3133
# Idk wtf is with the scale here. It's somewhere in the range of 25 to 26. 25.45 seems closest in one of my tests,
32-
# but I think that's minecraft packet location imprecision at fault so it's probably just 26.
33-
- define offset <location[<[part.origin]>].div[26].rotate_around_y[<util.pi>]>
34-
- define pose [head=<[rots].get[1].mul[-1]>,<[rots].get[2].mul[-1]>,<[rots].get[3]>]
35-
- spawn model_part_stand[equipment=[helmet=<[part.item]>];armor_pose=<[pose]>] <[location].add[<[offset]>]> save:spawned
34+
# but I think that's minecraft packet location imprecision at fault so it's possibly just 26?
35+
# Supposedly it's 25.6 according to external docs (16 * 1.6), but that also is wrong in my testing.
36+
- define offset <location[<[part.origin]>].div[25.6].rotate_around_y[<util.pi>]>
37+
- define pose <[rots].get[1].mul[-1]>,<[rots].get[2].mul[-1]>,<[rots].get[3]>
38+
- spawn model_part_stand[equipment=[helmet=<[part.item]>];armor_pose=[head=<[pose]>]] <[location].add[<[offset]>]> save:spawned
39+
- flag <entry[spawned].spawned_entity> dmodel_def_pose:<[pose]>
40+
- flag <entry[spawned].spawned_entity> dmodel_def_offset:<[offset]>
41+
- flag <entry[spawned].spawned_entity> dmodel_root:<entry[root].spawned_entity>
42+
- flag <entry[root].spawned_entity> dmodel_parts:->:<entry[spawned].spawned_entity>
43+
- flag <entry[root].spawned_entity> dmodel_anim_part.<[id]>:->:<entry[spawned].spawned_entity>
44+
- foreach <[parts]> key:id as:part:
45+
- foreach <[part.pairs]> as:pair_id:
46+
- flag <entry[root].spawned_entity> dmodel_anim_part.<[id]>:|:<entry[root].spawned_entity.flag[dmodel_anim_part.<[pair_id]>]||<list>>
47+
- determine <entry[root].spawned_entity>
48+
49+
dmodels_correct_to_default_position:
50+
type: task
51+
debug: false
52+
definitions: root_entity
53+
script:
54+
- foreach <[root_entity].flag[dmodel_parts]> as:part:
55+
- adjust <[part]> armor_pose:[head=<[part].flag[dmodel_def_pose]>]
56+
- teleport <[part]> <[root_entity].location.add[<[part].flag[dmodel_def_offset]>]>
57+
58+
dmodels_animate:
59+
type: task
60+
debug: false
61+
definitions: root_entity|animation
62+
script:
63+
- run dmodels_correct_to_default_position def.root_entity:<[root_entity]>
64+
- define animation_data <server.flag[dmodels_data.animations_<[root_entity].flag[dmodel_model_id]>.<[animation]>]||null>
65+
- if <[animation_data]> == null:
66+
- debug error "[DModels] Cannot animate entity <[root_entity].uuid> due to model <[root_entity].flag[dmodel_model_id]> not having an animation named <[animation]>"
67+
- stop
68+
- flag <[root_entity]> dmodels_animation_id:<[animation]>
69+
- flag <[root_entity]> dmodels_anim_time:0
70+
- flag server dmodels_anim_active.<[root_entity].uuid>
71+
72+
dmodels_move_to_frame:
73+
type: task
74+
debug: false
75+
definitions: root_entity|animation|timespot
76+
script:
77+
- define animation_data <server.flag[dmodels_data.animations_<[root_entity].flag[dmodel_model_id]>.<[animation]>]||null>
78+
- if <[timespot]> > <[animation_data.length]>:
79+
- choose <[animation_data.loop]>:
80+
- case loop:
81+
- define timespot <[timespot].mod[<[animation_data.length]>]>
82+
- case once:
83+
- flag server dmodels_anim_active.<[root_entity].uuid>:!
84+
- if <[root_entity].has_flag[dmodels_default_animation]>:
85+
- run dmodels_animate def.root_entity:<[root_entity]> def.animation:<[root_entity].flag[dmodels_default_animation]>
86+
- else:
87+
- run dmodels_correct_to_default_position def.root_entity:<[root_entity]>
88+
- stop
89+
- case hold:
90+
- define timespot <[animation_data.length]>
91+
- flag server dmodels_anim_active.<[root_entity].uuid>:!
92+
- foreach <[animation_data.animators]> key:part_id as:animator:
93+
- define ents <[root_entity].flag[dmodel_anim_part.<[part_id]>]||<list>>
94+
- if <[ents].is_empty>:
95+
- foreach next
96+
- foreach position|rotation as:channel:
97+
- define relevant_frames <[animator.frames].filter[get[channel].equals[<[channel]>]]>
98+
- define before_frame <[relevant_frames].filter[get[time].is_less_than_or_equal_to[<[timespot]>]].last||null>
99+
- define after_frame <[relevant_frames].filter[get[time].is_more_than_or_equal_to[<[timespot]>]].first||null>
100+
- if <[before_frame]> == null:
101+
- define before_frame <[after_frame]>
102+
- if <[after_frame]> == null:
103+
- define after_frame <[before_frame]>
104+
- if <[before_frame]> == null:
105+
- foreach next
106+
- define time_range <[after_frame.time].sub[<[before_frame.time]>]>
107+
- if <[time_range]> == 0:
108+
- define time_percent 0
109+
- else:
110+
- define time_percent <[timespot].sub[<[before_frame.time]>].div[<[time_range]>]>
111+
- choose <[before_frame.interpolation]>:
112+
- case catmullrom:
113+
- define before_extra <[relevant_frames].filter[get[time].is_less_than[<[before_frame.time]>]].last||null>
114+
- if <[before_extra]> == null:
115+
- define before_extra <[before_frame]>
116+
- define after_extra <[relevant_frames].filter[get[time].is_more_than[<[after_frame.time]>]].first||null>
117+
- if <[after_extra]> == null:
118+
- define after_extra <[after_frame]>
119+
- define p0 <[before_extra.data].as_location>
120+
- define p1 <[before_frame.data].as_location>
121+
- define p2 <[after_frame.data].as_location>
122+
- define p3 <[after_extra.data].as_location>
123+
- define data <proc[dmodels_catmullrom_proc].context[<[p0]>|<[p1]>|<[p2]>|<[p3]>|<[time_percent]>]>
124+
- case linear:
125+
- define data <[after_frame.data].as_location.sub[<[before_frame.data]>].mul[<[time_percent]>].add[<[before_frame.data]>].xyz>
126+
- case step:
127+
- define data <[before_frame.data]>
128+
- foreach <[ents]> as:ent:
129+
- choose <[channel]>:
130+
- case position:
131+
- teleport <[ent]> <[root_entity].location.add[<[ent].flag[dmodel_def_offset].add[<[data].as_location.div[25.6]>]>]>
132+
- case rotation:
133+
- define radian_rot <[data].as_location.xyz.split[,].parse[to_radians].separated_by[,]>
134+
- adjust <[ent]> armor_pose:[head=<[ent].flag[dmodel_def_pose].as_location.add[<[radian_rot]>].xyz>]
135+
136+
dmodels_catmullrom_get_t:
137+
type: procedure
138+
debug: false
139+
definitions: t|p0|p1
140+
script:
141+
# This is more complex for different alpha values, but alpha=1 compresses down to a '.length' call conveniently
142+
- determine <[p1].sub[<[p0]>].length.add[<[t]>]>
143+
144+
dmodels_catmullrom_proc:
145+
type: procedure
146+
debug: false
147+
definitions: p0|p1|p2|p3|t
148+
script:
149+
# TODO: Validate this mess
150+
- define t0 0
151+
- define t1 <proc[dmodels_catmullrom_get_t].context[0|<[p0]>|<[p1]>]>
152+
- define t2 <proc[dmodels_catmullrom_get_t].context[<[t1]>|<[p1]>|<[p2]>]>
153+
- define t3 <proc[dmodels_catmullrom_get_t].context[<[t2]>|<[p2]>|<[p3]>]>
154+
- define t <[t2].sub[<[t1]>].mul[<[t]>].add[<[t1]>]>
155+
# ( t1-t )/( t1-t0 )*p0 + ( t-t0 )/( t1-t0 )*p1;
156+
- define a1 <[p0].mul[<[t1].sub[<[t]>].div[<[t1]>]>].add[<[p1].mul[<[t].div[<[t1]>]>]>]>
157+
# ( t2-t )/( t2-t1 )*p1 + ( t-t1 )/( t2-t1 )*p2;
158+
- define a2 <[p1].mul[<[t2].sub[<[t]>].div[<[t2].sub[<[t1]>]>]>].add[<[p2].mul[<[t].sub[<[t1]>].div[<[t2].sub[<[t1]>]>]>]>]>
159+
# FVector A3 = ( t3-t )/( t3-t2 )*p2 + ( t-t2 )/( t3-t2 )*p3;
160+
- define a3 <[a1].mul[<[t2].sub[<[t]>].div[<[t2]>]>].add[<[a2].mul[<[t].div[<[t2]>]>]>]>
161+
# FVector B1 = ( t2-t )/( t2-t0 )*A1 + ( t-t0 )/( t2-t0 )*A2;
162+
- define b1 <[a1].mul[<[t2].sub[<[t]>].div[<[t2]>]>].add[<[a2].mul[<[t].div[<[t2]>]>]>]>
163+
# FVector B2 = ( t3-t )/( t3-t1 )*A2 + ( t-t1 )/( t3-t1 )*A3;
164+
- define b2 <[a2].mul[<[t3].sub[<[t]>].div[<[t3].sub[<[t1]>]>]>].add[<[a3].mul[<[t].sub[<[t1]>].div[<[t3].sub[<[t1]>]>]>]>]>
165+
# FVector C = ( t2-t )/( t2-t1 )*B1 + ( t-t1 )/( t2-t1 )*B2;
166+
- determine <[b1].mul[<[t2].sub[<[t]>].div[<[t2].sub[<[t1]>]>]>].add[<[b2].mul[<[t].sub[<[t1]>].div[<[t2].sub[<[t1]>]>]>]>]>
167+
168+
dmodels_animator:
169+
type: world
170+
debug: false
171+
events:
172+
on server start priority:-1000:
173+
# Cleanup
174+
- flag server dmodels_data:!
175+
- flag server dmodels_anim_active:!
176+
on tick server_flagged:dmodels_anim_active:
177+
- foreach <server.flag[dmodels_anim_active]> key:root_id:
178+
- define root <entity[<[root_id]>]||null>
179+
- if <[root].is_spawned||false>:
180+
- run dmodels_move_to_frame def.root_entity:<[root]> def.animation:<[root].flag[dmodels_animation_id]> def.timespot:<[root].flag[dmodels_anim_time].div[20]>
181+
- flag <[root]> dmodels_anim_time:++

0 commit comments

Comments
 (0)