yosbr
266
config/yosbr/config/figura/avatars/Hoshino/avatar.json
Normal file
|
@ -0,0 +1,266 @@
|
|||
{
|
||||
"name": "Hoshino",
|
||||
"description": "Hoshino Takanashi from Blue Archive",
|
||||
"author": "Gakuto1112",
|
||||
"version": "0.1.4",
|
||||
"color": "a3e0fb",
|
||||
"ignoredTextures": [
|
||||
"textures.for_modeling.leather_layer_1",
|
||||
"textures.for_modeling.leather_layer_1_overlay",
|
||||
"textures.for_modeling.leather_layer_2",
|
||||
"textures.for_modeling.leather_layer_2_overlay",
|
||||
"textures.for_modeling.trim",
|
||||
"textures.for_modeling.trim_leggings",
|
||||
"textures.for_modeling.water_still",
|
||||
"textures.for_modeling.water_flow",
|
||||
"textures.for_modeling.ravager",
|
||||
"textures.for_modeling.pillager",
|
||||
"textures.for_modeling.vindicator",
|
||||
"textures.for_modeling.firework_rocket",
|
||||
"textures.for_modeling.zombie",
|
||||
"textures.for_modeling.creeper"
|
||||
],
|
||||
"autoScripts": [
|
||||
"scripts/avatar.lua"
|
||||
],
|
||||
"customizations": {
|
||||
"models.models.main": {
|
||||
"primaryRenderType": "CUTOUT"
|
||||
},
|
||||
"models.models.main.Avatar.Head.HeadRing": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.Avatar.UpperBody": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.Avatar.LowerBody": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.main.CameraAnchor": {
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.armor.ArmorRA": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.RightArm",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRA.RightChestplate.RightChestplateTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRAB": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRAB.RightChestplateBottom.RightChestplateBottomTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLA": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.LeftArm",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLA.LeftChestplate.LeftChestplateTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLAB": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLAB.LeftChestplateBottom.LeftChestplateBottomTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRL": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody.Legs.RightLeg"
|
||||
},
|
||||
"models.models.armor.ArmorRL.RightLeggings": {
|
||||
"visible": false,
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.armor.ArmorRL.RightLeggings.RightLeggingsTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRL.RightBoots": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRL.RightBoots.RightBootsTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRLB": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom"
|
||||
},
|
||||
"models.models.armor.ArmorRLB.RightLeggingsBottom": {
|
||||
"visible": false,
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.armor.ArmorRLB.RightLeggingsBottom.RightLeggingsBottomTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRLB.RightBootsBottom": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorRLB.RightBootsBottom.RightBootsBottomTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLL": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody.Legs.LeftLeg"
|
||||
},
|
||||
"models.models.armor.ArmorLL.LeftLeggings": {
|
||||
"visible": false,
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.armor.ArmorLL.LeftLeggings.LeftLeggingsTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLL.LeftBoots": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLL.LeftBoots.LeftBootsTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLLB": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom"
|
||||
},
|
||||
"models.models.armor.ArmorLLB.LeftLeggingsBottom": {
|
||||
"visible": false,
|
||||
"parentType": "None"
|
||||
},
|
||||
"models.models.armor.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottomTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLLB.LeftBootsBottom": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.armor.ArmorLLB.LeftBootsBottom.LeftBootsBottomTrim": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.gun.Gun": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Body"
|
||||
},
|
||||
"models.models.gun.SubGun": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Body",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_1.ShineEffect": {
|
||||
"moveTo": "models.models.gun.Gun.Barrel",
|
||||
"primaryRenderType": "EMISSIVE_SOLID",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_2.WhaleFloat": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_2.Waves": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_3.Illagers": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_3.Firework": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_3.Explosion": {
|
||||
"visible": false,
|
||||
"primaryRenderType": "EMISSIVE_SOLID"
|
||||
},
|
||||
"models.models.ex_skill_4.Zombie": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_4.Creeper": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_4.EyeShine": {
|
||||
"visible": false,
|
||||
"moveTo": "models.models.main.Avatar.Head",
|
||||
"primaryRenderType": "EMISSIVE_SOLID"
|
||||
},
|
||||
"models.models.ex_skill_4.MuzzleFlash": {
|
||||
"visible": false,
|
||||
"moveTo": "models.models.gun.Gun",
|
||||
"primaryRenderType": "EMISSIVE_SOLID"
|
||||
},
|
||||
"models.models.ex_skill_3.Gui": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_masked.CMaskedH": {
|
||||
"moveTo": "models.models.main.Avatar.Head",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitH": {
|
||||
"moveTo": "models.models.main.Avatar.Head",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitB": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Body",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitB.GunBag": {
|
||||
"primaryRenderType": "TRANSLUCENT"
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitRA": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.RightArm",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitRAB": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitLA": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.LeftArm",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitLAB": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitRL": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody.Legs.RightLeg",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_swimsuit.CSwimsuitLL": {
|
||||
"moveTo": "models.models.main.Avatar.LowerBody.Legs.LeftLeg",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_battle.CBattleH": {
|
||||
"moveTo": "models.models.main.Avatar.Head",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.costume_battle.CBattleB": {
|
||||
"moveTo": "models.models.main.Avatar.UpperBody.Body",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.ex_skill_frame.Particles": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.bubble.Camera.AvatarBubble": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.bubble.Camera.AvatarBubble.Bullets": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.bubble.Camera.AvatarBubble.Dots": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.action_wheel_gui.Gui": {
|
||||
"visible": false
|
||||
},
|
||||
"models.models.barrier": {
|
||||
"moveTo": "models.models.main.Avatar",
|
||||
"primaryRenderType": "CUTOUT_EMISSIVE_SOLID",
|
||||
"visible": false
|
||||
},
|
||||
"models.models.death_animation": {
|
||||
"visible": false,
|
||||
"parentType": "World"
|
||||
}
|
||||
}
|
||||
}
|
BIN
config/yosbr/config/figura/avatars/Hoshino/avatar.png
Normal file
After Width: | Height: | Size: 26 KiB |
3811
config/yosbr/config/figura/avatars/Hoshino/models/armor.bbmodel
Normal file
4318
config/yosbr/config/figura/avatars/Hoshino/models/barrier.bbmodel
Normal file
902
config/yosbr/config/figura/avatars/Hoshino/models/bubble.bbmodel
Normal file
|
@ -0,0 +1,902 @@
|
|||
{
|
||||
"meta": {
|
||||
"format_version": "4.9",
|
||||
"model_format": "free",
|
||||
"box_uv": false
|
||||
},
|
||||
"name": "bubble",
|
||||
"model_identifier": "",
|
||||
"visible_box": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"variable_placeholders": "",
|
||||
"variable_placeholder_buttons": [],
|
||||
"timeline_setups": [],
|
||||
"unhandled_root_fields": {},
|
||||
"resolution": {
|
||||
"width": 16,
|
||||
"height": 16
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Bubble",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-3,
|
||||
-4,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
5,
|
||||
4,
|
||||
0
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "ee16cd89-5e16-d1c0-bdb4-bec63d4d29dc"
|
||||
},
|
||||
{
|
||||
"name": "Emoji",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-1.5,
|
||||
-2.5,
|
||||
-0.05
|
||||
],
|
||||
"to": [
|
||||
3.5,
|
||||
2.5,
|
||||
-0.05
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 1
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "e1f1401a-3276-4ed6-1633-16078de30336"
|
||||
},
|
||||
{
|
||||
"name": "Bullet1",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
1.9375,
|
||||
-1.875,
|
||||
-0.1
|
||||
],
|
||||
"to": [
|
||||
3.1875,
|
||||
1.875,
|
||||
-0.1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 2
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "015d120f-7c4c-05e7-86b8-e442d2ca89ce"
|
||||
},
|
||||
{
|
||||
"name": "Bullet2",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
0.375,
|
||||
-1.875,
|
||||
-0.1
|
||||
],
|
||||
"to": [
|
||||
1.625,
|
||||
1.875,
|
||||
-0.1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 2
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "71c04be5-6d32-4cee-9e7d-b8d98db60f7d"
|
||||
},
|
||||
{
|
||||
"name": "Bullet3",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-1.1875,
|
||||
-1.875,
|
||||
-0.1
|
||||
],
|
||||
"to": [
|
||||
0.0625,
|
||||
1.875,
|
||||
-0.1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 2
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "c13ac616-5ee7-3b04-83ed-f9490507b4f8"
|
||||
},
|
||||
{
|
||||
"name": "Dot1",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
2.25,
|
||||
-0.625,
|
||||
-0.1
|
||||
],
|
||||
"to": [
|
||||
3.5,
|
||||
0.625,
|
||||
-0.1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 4
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "f336f75d-4273-d986-7b79-5028befe8efc"
|
||||
},
|
||||
{
|
||||
"name": "Dot2",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
0.375,
|
||||
-0.625,
|
||||
-0.1
|
||||
],
|
||||
"to": [
|
||||
1.625,
|
||||
0.625,
|
||||
-0.1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 4
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "9d1c3e18-15c3-3c07-f04a-d705ceec1fed"
|
||||
},
|
||||
{
|
||||
"name": "Dot3",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-1.5,
|
||||
-0.625,
|
||||
-0.1
|
||||
],
|
||||
"to": [
|
||||
-0.25,
|
||||
0.625,
|
||||
-0.1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 4
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "23a7b4da-5022-66ac-bfac-23544ab88f69"
|
||||
}
|
||||
],
|
||||
"outliner": [
|
||||
{
|
||||
"name": "Camera",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "1ad8e11f-891c-06f0-491f-55ff468a6e74",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "AvatarBubble",
|
||||
"origin": [
|
||||
-3,
|
||||
-4,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "ce33d433-402d-decd-b93f-250225de88c7",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
"ee16cd89-5e16-d1c0-bdb4-bec63d4d29dc",
|
||||
"e1f1401a-3276-4ed6-1633-16078de30336",
|
||||
{
|
||||
"name": "Bullets",
|
||||
"origin": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "2a47b3c1-ef22-e939-70d9-e17ea10d79d9",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
"015d120f-7c4c-05e7-86b8-e442d2ca89ce",
|
||||
"71c04be5-6d32-4cee-9e7d-b8d98db60f7d",
|
||||
"c13ac616-5ee7-3b04-83ed-f9490507b4f8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Dots",
|
||||
"origin": [
|
||||
-3,
|
||||
-4,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "55f984b9-0c0d-7ae1-712d-78a7d7fbfa03",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
"f336f75d-4273-d986-7b79-5028befe8efc",
|
||||
"9d1c3e18-15c3-3c07-f04a-d705ceec1fed",
|
||||
"23a7b4da-5022-66ac-bfac-23544ab88f69"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"path": "",
|
||||
"name": "bubble.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "0",
|
||||
"width": 16,
|
||||
"height": 16,
|
||||
"uv_width": 16,
|
||||
"uv_height": 16,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "849f6abb-5d52-7cbc-bc5e-c54506c00158",
|
||||
"relative_path": "../../textures/bubble.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAGhJREFUOE/tk0sOABAMRNul+1/WroKO+KcSdqykzGvDDNN8yaLOfb0vJKHIXM9cro+brFs1bvsqKEFAMouBAuQK4Lh7PUWc4AMuvIGaz2aiJz4A1Pyd6kJHRP44TMgLIEM8dZxdnJuzAGqUPxJDQDMGAAAAAElFTkSuQmCC"
|
||||
},
|
||||
{
|
||||
"path": "",
|
||||
"name": "reload.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "4",
|
||||
"width": 16,
|
||||
"height": 16,
|
||||
"uv_width": 16,
|
||||
"uv_height": 16,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "e5650158-1574-55d1-6b46-d58173bbb96b",
|
||||
"relative_path": "../../textures/emojis/reload.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAEpJREFUOE9jZKAQMFKonwFuwOf37/+DDOMVFCRJjCTFIAvQLQIbABKE2QxjEys2aoAg42gYIIUBcp4AJSpYioOJ4xKjXmYiN1cCAKtG/BHrN3pVAAAAAElFTkSuQmCC"
|
||||
},
|
||||
{
|
||||
"path": "",
|
||||
"name": "bullet.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "5",
|
||||
"width": 4,
|
||||
"height": 12,
|
||||
"uv_width": 16,
|
||||
"uv_height": 16,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "1a2d73a2-6180-0319-1efb-16f49aa91e7a",
|
||||
"relative_path": "../../textures/bullet.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAMCAYAAABFohwTAAAAAXNSR0IArs4c6QAAAGdJREFUGFdjTEhI4Pj1/f93LpbvDH9YuTgZMQSiwuP/V4azMOh4+TKkJi9jYAQJJDt+ZHAK9GFILdoFESj0/cpgEuTBkJoKFSCsAqwlrJAhNXEikhl4BVKdXjE4xAZDbEmJDvvPgAQAMBRGJaIzndEAAAAASUVORK5CYII="
|
||||
},
|
||||
{
|
||||
"path": "",
|
||||
"name": "dots.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "3",
|
||||
"width": 16,
|
||||
"height": 16,
|
||||
"uv_width": 16,
|
||||
"uv_height": 16,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "7dda2e3d-9f23-52bb-e799-b8a2879d3871",
|
||||
"relative_path": "../../textures/emojis/dots.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAADhJREFUOE9jZKAQMFKon2HUAAZIGHx+//4/iOYVFEQJE2LEGWGKYLEBM4RYccoNoNgLowlpEOQFAJrjL7ESx0KNAAAAAElFTkSuQmCC"
|
||||
},
|
||||
{
|
||||
"path": "",
|
||||
"name": "black_dot.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "6",
|
||||
"width": 4,
|
||||
"height": 4,
|
||||
"uv_width": 16,
|
||||
"uv_height": 16,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "d1f6e856-c31e-ace6-6507-d03259b83bf5",
|
||||
"relative_path": "../../textures/emojis/black_dot.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAABhJREFUGFdjZICA/1CakRGJAxbDKoCiBQCSkQQDt6KjJwAAAABJRU5ErkJggg=="
|
||||
}
|
||||
]
|
||||
}
|
237
config/yosbr/config/figura/avatars/Hoshino/models/bullet.bbmodel
Normal file
|
@ -0,0 +1,237 @@
|
|||
{
|
||||
"meta": {
|
||||
"format_version": "4.5",
|
||||
"model_format": "free",
|
||||
"box_uv": false
|
||||
},
|
||||
"name": "bullet",
|
||||
"model_identifier": "",
|
||||
"visible_box": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"variable_placeholders": "",
|
||||
"variable_placeholder_buttons": [],
|
||||
"timeline_setups": [],
|
||||
"unhandled_root_fields": {},
|
||||
"resolution": {
|
||||
"width": 4,
|
||||
"height": 12
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Bullet1",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-0.5,
|
||||
0,
|
||||
-1
|
||||
],
|
||||
"to": [
|
||||
0.5,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
1,
|
||||
3,
|
||||
2,
|
||||
4
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
1,
|
||||
3,
|
||||
2,
|
||||
11
|
||||
],
|
||||
"rotation": 90,
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
1,
|
||||
10,
|
||||
2,
|
||||
11
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
1,
|
||||
3,
|
||||
2,
|
||||
11
|
||||
],
|
||||
"rotation": 270,
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
1,
|
||||
3,
|
||||
2,
|
||||
11
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
1,
|
||||
3,
|
||||
2,
|
||||
11
|
||||
],
|
||||
"rotation": 180,
|
||||
"texture": 0
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "eb1cee40-d947-b0fd-992e-a0445207f5c3"
|
||||
},
|
||||
{
|
||||
"name": "Bullet2",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-0.375,
|
||||
0.125,
|
||||
-1.25
|
||||
],
|
||||
"to": [
|
||||
0.375,
|
||||
0.875,
|
||||
-1
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"texture": 0
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "c69c3bcf-da9b-3ad4-7d0a-e697c8066967"
|
||||
}
|
||||
],
|
||||
"outliner": [
|
||||
{
|
||||
"name": "Arrow",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "0c9fab8f-3742-3b33-0bef-de2eca96287f",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
"eb1cee40-d947-b0fd-992e-a0445207f5c3",
|
||||
"c69c3bcf-da9b-3ad4-7d0a-e697c8066967"
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"path": "",
|
||||
"name": "bullet.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "0",
|
||||
"particle": false,
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"mode": "bitmap",
|
||||
"saved": true,
|
||||
"uuid": "d4264722-75e2-f338-9736-9de1cd8631d0",
|
||||
"relative_path": "../../textures/bullet.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAMCAYAAABFohwTAAAAAXNSR0IArs4c6QAAAGdJREFUGFdjTEhI4Pj1/f93LpbvDH9YuTgZMQSiwuP/V4azMOh4+TKkJi9jYAQJJDt+ZHAK9GFILdoFESj0/cpgEuTBkJoKFSCsAqwlrJAhNXEikhl4BVKdXjE4xAZDbEmJDvvPgAQAMBRGJaIzndEAAAAASUVORK5CYII="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
{
|
||||
"meta": {
|
||||
"format_version": "4.9",
|
||||
"model_format": "free",
|
||||
"box_uv": false
|
||||
},
|
||||
"name": "costume_masked",
|
||||
"model_identifier": "",
|
||||
"visible_box": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"variable_placeholders": "",
|
||||
"variable_placeholder_buttons": [],
|
||||
"timeline_setups": [],
|
||||
"unhandled_root_fields": {},
|
||||
"resolution": {
|
||||
"width": 32,
|
||||
"height": 16
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Mask",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-4,
|
||||
24,
|
||||
-4
|
||||
],
|
||||
"to": [
|
||||
4,
|
||||
32,
|
||||
4
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"inflate": 0.51,
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
8,
|
||||
8,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
8,
|
||||
8,
|
||||
16
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
24,
|
||||
8,
|
||||
32,
|
||||
16
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
16,
|
||||
8,
|
||||
24,
|
||||
16
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
8,
|
||||
0,
|
||||
16,
|
||||
8
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
16,
|
||||
0,
|
||||
24,
|
||||
8
|
||||
],
|
||||
"texture": 0
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "b747bd75-507f-7929-aeae-3c66cb26fac7"
|
||||
},
|
||||
{
|
||||
"name": "Number",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
-1.07,
|
||||
29,
|
||||
-4.55
|
||||
],
|
||||
"to": [
|
||||
1.07,
|
||||
32,
|
||||
-4.55
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5,
|
||||
7
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "9da77f2b-9ae5-1508-7197-7cd1652e0981"
|
||||
}
|
||||
],
|
||||
"outliner": [
|
||||
{
|
||||
"name": "CMaskedH",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "8aa59adb-e386-5f5c-152e-db9887b01653",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
"b747bd75-507f-7929-aeae-3c66cb26fac7",
|
||||
"9da77f2b-9ae5-1508-7197-7cd1652e0981"
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"path": "",
|
||||
"name": "costume_masked.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "1",
|
||||
"width": 32,
|
||||
"height": 16,
|
||||
"uv_width": 32,
|
||||
"uv_height": 16,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "375bf4c3-b00b-7f4b-fdf5-83236cf51fe5",
|
||||
"relative_path": "../../textures/costume_masked.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAAXNSR0IArs4c6QAAANlJREFUSEtjZICCL9++/+fh4mSE8WH0vXnz/oPYohLCDK9fvMWgCckpJSVhmIlsB1gSZLmxkSfD2XPbGdAd8XnbRrAD0C2HiYFoXJaA9PJ6+eN3AMxykEHYHAAKAZDvkR2BzsZlCUgvVUIA2few4INFCcUhQCgKYCEAcgS65bCQoVsIYIsGqoQALBRw5QL0NACLEliIUBwC6FkPmY8cBehZEVkdrmxKMBHiswCXodQUZxx1wJAIAVhCAhWtoByAzqckTRCVBgaNA9CzKyxEaB4CMAuoGfSwwgwAAvE293iLdaoAAAAASUVORK5CYII="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,694 @@
|
|||
{
|
||||
"meta": {
|
||||
"format_version": "4.9",
|
||||
"model_format": "free",
|
||||
"box_uv": false
|
||||
},
|
||||
"name": "ex_skill_1",
|
||||
"model_identifier": "",
|
||||
"visible_box": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"variable_placeholders": "",
|
||||
"variable_placeholder_buttons": [],
|
||||
"timeline_setups": [],
|
||||
"unhandled_root_fields": {},
|
||||
"resolution": {
|
||||
"width": 14,
|
||||
"height": 13
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "ShineEffect1",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
0,
|
||||
1.55,
|
||||
-8.85
|
||||
],
|
||||
"to": [
|
||||
0,
|
||||
6.05,
|
||||
-7.35
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0.8,
|
||||
-11.1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
6,
|
||||
2,
|
||||
9,
|
||||
11
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
6,
|
||||
2,
|
||||
9,
|
||||
11
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 90,
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 270,
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "fb3d3f3b-bb29-91c5-1423-9f4dbce83643"
|
||||
},
|
||||
{
|
||||
"name": "ShineEffect2",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
0,
|
||||
3.05,
|
||||
-10.35
|
||||
],
|
||||
"to": [
|
||||
0,
|
||||
4.55,
|
||||
-5.85
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0.8,
|
||||
-11.1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
3,
|
||||
5,
|
||||
12,
|
||||
8
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
3,
|
||||
5,
|
||||
12,
|
||||
8
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 90,
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 270,
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "4dec548f-8312-44ad-95c8-0c3fe22373b1"
|
||||
},
|
||||
{
|
||||
"name": "ShineEffect3",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
0,
|
||||
0.55,
|
||||
-8.35
|
||||
],
|
||||
"to": [
|
||||
0,
|
||||
7.05,
|
||||
-7.85
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0.8,
|
||||
-11.1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
13
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
13
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 90,
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 270,
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "1de56f71-072a-4eef-f9a4-1da443e775db"
|
||||
},
|
||||
{
|
||||
"name": "ShineEffect4",
|
||||
"box_uv": false,
|
||||
"rescale": false,
|
||||
"locked": false,
|
||||
"render_order": "default",
|
||||
"allow_mirror_modeling": true,
|
||||
"from": [
|
||||
0,
|
||||
3.55,
|
||||
-11.35
|
||||
],
|
||||
"to": [
|
||||
0,
|
||||
4.05,
|
||||
-4.85
|
||||
],
|
||||
"autouv": 0,
|
||||
"color": 0,
|
||||
"origin": [
|
||||
0,
|
||||
0.8,
|
||||
-11.1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
1,
|
||||
6,
|
||||
14,
|
||||
7
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"texture": null
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
1,
|
||||
6,
|
||||
14,
|
||||
7
|
||||
],
|
||||
"texture": 0
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 90,
|
||||
"texture": null
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": 270,
|
||||
"texture": null
|
||||
}
|
||||
},
|
||||
"type": "cube",
|
||||
"uuid": "0a591207-abc9-1b7f-940c-5a03334e5ff8"
|
||||
}
|
||||
],
|
||||
"outliner": [
|
||||
{
|
||||
"name": "ShineEffect",
|
||||
"origin": [
|
||||
0,
|
||||
3.8,
|
||||
-8.1
|
||||
],
|
||||
"rotation": [
|
||||
0,
|
||||
90,
|
||||
0
|
||||
],
|
||||
"color": 0,
|
||||
"uuid": "f767d5d3-75c7-501a-a586-e004357f2c28",
|
||||
"export": true,
|
||||
"mirror_uv": false,
|
||||
"isOpen": true,
|
||||
"locked": false,
|
||||
"visibility": true,
|
||||
"autouv": 0,
|
||||
"children": [
|
||||
"fb3d3f3b-bb29-91c5-1423-9f4dbce83643",
|
||||
"4dec548f-8312-44ad-95c8-0c3fe22373b1",
|
||||
"1de56f71-072a-4eef-f9a4-1da443e775db",
|
||||
"0a591207-abc9-1b7f-940c-5a03334e5ff8"
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"path": "",
|
||||
"name": "ex_skill_1.png",
|
||||
"folder": "",
|
||||
"namespace": "",
|
||||
"id": "0",
|
||||
"width": 14,
|
||||
"height": 13,
|
||||
"uv_width": 14,
|
||||
"uv_height": 13,
|
||||
"particle": false,
|
||||
"layers_enabled": false,
|
||||
"sync_to_project": "",
|
||||
"render_mode": "default",
|
||||
"render_sides": "auto",
|
||||
"frame_time": 1,
|
||||
"frame_order_type": "loop",
|
||||
"frame_order": "",
|
||||
"frame_interpolate": false,
|
||||
"visible": true,
|
||||
"internal": true,
|
||||
"saved": true,
|
||||
"uuid": "8fef8f15-329a-9210-3f20-250416f9d3f0",
|
||||
"relative_path": "../../textures/ex_skill_1.png",
|
||||
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAYAAACZ3F9/AAAAAXNSR0IArs4c6QAAANlJREFUKFOVksFOAjEURc8DZ2ZJWOgGTFASMP7/tyiYoCSIC2UBrOhQ5pqZMhpjNWkXbdP23Jv3bk2SzMyIDM0fZNP76F30sNXQy0J2M04D9boSZ8QG17/gPx318a7W2S6vEsD9VsigqrB+/39Q65XIc8gLyDLoXsDJg3Pgj1AescGwEWkmPT2qeVzkkNVrEeBapHRQlnA4fO+da8sP1QTHM1DDdUr+BN5/QT8coxnutqLTgUpYr5fQnM1GmAKY1NW3dYhDFUk5NszyWTa6Tfs5odsz2eQuCn4CIl5duuF7d3AAAAAASUVORK5CYII="
|
||||
}
|
||||
],
|
||||
"animations": [
|
||||
{
|
||||
"uuid": "7b05d1aa-9efa-10af-3456-222a207d2ee5",
|
||||
"name": "ex_skill_1",
|
||||
"loop": "hold",
|
||||
"override": true,
|
||||
"length": 4.6,
|
||||
"snapping": 20,
|
||||
"selected": true,
|
||||
"anim_time_update": "",
|
||||
"blend_weight": "",
|
||||
"start_delay": "",
|
||||
"loop_delay": "",
|
||||
"animators": {
|
||||
"f767d5d3-75c7-501a-a586-e004357f2c28": {
|
||||
"name": "ShineEffect",
|
||||
"type": "bone",
|
||||
"keyframes": [
|
||||
{
|
||||
"channel": "rotation",
|
||||
"data_points": [
|
||||
{
|
||||
"x": "90",
|
||||
"y": "135",
|
||||
"z": "90"
|
||||
}
|
||||
],
|
||||
"uuid": "f3d25414-1f60-6f1d-a29a-387b4d5e3122",
|
||||
"time": 3.95,
|
||||
"color": -1,
|
||||
"interpolation": "bezier",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.1,
|
||||
-0.024,
|
||||
-0.1
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.1,
|
||||
0.024,
|
||||
0.1
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "rotation",
|
||||
"data_points": [
|
||||
{
|
||||
"x": "90",
|
||||
"y": "180",
|
||||
"z": "90"
|
||||
}
|
||||
],
|
||||
"uuid": "5730c31b-d036-b4f3-4eb8-13ef3955c50f",
|
||||
"time": 4.15,
|
||||
"color": -1,
|
||||
"interpolation": "bezier",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.1,
|
||||
-0.222,
|
||||
-0.1
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.1,
|
||||
0.222,
|
||||
0.1
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "scale",
|
||||
"data_points": [
|
||||
{
|
||||
"x": "0",
|
||||
"y": "0",
|
||||
"z": "0"
|
||||
}
|
||||
],
|
||||
"uuid": "3535fa90-518c-b623-4297-c02d791f15a8",
|
||||
"time": 0,
|
||||
"color": -1,
|
||||
"uniform": true,
|
||||
"interpolation": "linear",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.1,
|
||||
-0.1,
|
||||
-0.1
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "scale",
|
||||
"data_points": [
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
],
|
||||
"uuid": "6104fb46-48a4-03ed-3818-dd2e47b69e54",
|
||||
"time": 3.95,
|
||||
"color": -1,
|
||||
"uniform": true,
|
||||
"interpolation": "linear",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.1,
|
||||
-0.1,
|
||||
-0.1
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "scale",
|
||||
"data_points": [
|
||||
{
|
||||
"x": "1",
|
||||
"y": "1",
|
||||
"z": "1"
|
||||
}
|
||||
],
|
||||
"uuid": "759a303f-e459-6cfd-c8c8-c0cce4530232",
|
||||
"time": 4,
|
||||
"color": -1,
|
||||
"uniform": true,
|
||||
"interpolation": "linear",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.1,
|
||||
-0.1,
|
||||
-0.1
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "scale",
|
||||
"data_points": [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
}
|
||||
],
|
||||
"uuid": "c246e8a8-6da3-8727-42cf-66497c2970af",
|
||||
"time": 4.15,
|
||||
"color": -1,
|
||||
"uniform": true,
|
||||
"interpolation": "bezier",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.02212,
|
||||
-0.02212,
|
||||
-0.02212
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.02212,
|
||||
0.02212,
|
||||
0.02212
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"channel": "scale",
|
||||
"data_points": [
|
||||
{
|
||||
"x": "0",
|
||||
"y": "0",
|
||||
"z": "0"
|
||||
}
|
||||
],
|
||||
"uuid": "915ee3ec-2806-1987-0aeb-047e3c8d2a07",
|
||||
"time": 4.35,
|
||||
"color": -1,
|
||||
"uniform": true,
|
||||
"interpolation": "bezier",
|
||||
"bezier_linked": true,
|
||||
"bezier_left_time": [
|
||||
-0.22239,
|
||||
-0.22239,
|
||||
-0.22239
|
||||
],
|
||||
"bezier_left_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"bezier_right_time": [
|
||||
0.22239,
|
||||
0.22239,
|
||||
0.22239
|
||||
],
|
||||
"bezier_right_value": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
3271
config/yosbr/config/figura/avatars/Hoshino/models/ex_skill_2.bbmodel
Normal file
11042
config/yosbr/config/figura/avatars/Hoshino/models/ex_skill_3.bbmodel
Normal file
7958
config/yosbr/config/figura/avatars/Hoshino/models/ex_skill_4.bbmodel
Normal file
9418
config/yosbr/config/figura/avatars/Hoshino/models/gun.bbmodel
Normal file
50702
config/yosbr/config/figura/avatars/Hoshino/models/main.bbmodel
Normal file
229
config/yosbr/config/figura/avatars/Hoshino/scripts/avatar.lua
Normal file
|
@ -0,0 +1,229 @@
|
|||
---@class Avatar アバターのメインクラス
|
||||
---@field public avatarEvents AvatarEvents
|
||||
---@field public modelUtils ModelUtils
|
||||
---@field public playerUtils PlayerUtils
|
||||
---@field public compatibilityUtils CompatibilityUtils
|
||||
---@field public characterData BlueArchiveCharacter
|
||||
---@field public headRing HeadRing
|
||||
---@field public headBlock HeadBlock
|
||||
---@field public locale Locale
|
||||
---@field public config Config
|
||||
---@field public cameraManager CameraManager
|
||||
---@field public keyManager KeyManager
|
||||
---@field public vanillaModel VanillaModel
|
||||
---@field public arms Arms
|
||||
---@field public skirt Skirt
|
||||
---@field public armor Armor
|
||||
---@field public faceParts FaceParts
|
||||
---@field public portrait Portrait
|
||||
---@field public physics Physics
|
||||
---@field public gun GunHoshino
|
||||
---@field public nameplate Nameplate
|
||||
---@field public exSkill ExSkill
|
||||
---@field public frameParticleManager ExSkillFrameParticleManager
|
||||
---@field public placementObjectManager PlacementObjectManager
|
||||
---@field public costume Costume
|
||||
---@field public actionWheel ActionWheel
|
||||
---@field public actionWheelGui ActionWheelGui
|
||||
---@field public bubble Bubble
|
||||
---@field public barrier Barrier
|
||||
---@field public deathAnimation DeathAnimation
|
||||
---@field public hypixelZombies HypixelZombies
|
||||
---@field public updateChecker UpdateChecker
|
||||
---@field public shield Shield
|
||||
---@field public whaleFloat WhaleFloat
|
||||
---@field public subGun SubGun
|
||||
---@field public waveParticleManager ExSkill2WaveParticleManager
|
||||
---@field public instantiate fun(class: table, super: table, ...: any) クラスをインスタンス化する
|
||||
|
||||
Avatar = {
|
||||
---コンストラクタ
|
||||
---@return Avatar
|
||||
new = function ()
|
||||
---@type Avatar
|
||||
local instance = Avatar.instantiate(Avatar)
|
||||
|
||||
--ENTITY_INIT前に読み込み
|
||||
require("scripts.avatar_modules.avatar_module")
|
||||
|
||||
--ユーティリティクラスの読み込み
|
||||
require("scripts.avatar_modules.events.abstract_event")
|
||||
require("scripts.avatar_modules.events.script_init_event")
|
||||
require("scripts.avatar_modules.events.avatar_events")
|
||||
instance.avatarEvents = AvatarEvents.new(instance)
|
||||
instance.avatarEvents:init()
|
||||
|
||||
require("scripts.avatar_modules.utils.model_utils")
|
||||
instance.modelUtils = ModelUtils.new(instance)
|
||||
instance.modelUtils:init()
|
||||
|
||||
--アバターモジュールの読み込み
|
||||
require("scripts.blue_archive_character")
|
||||
instance.characterData = BlueArchiveCharacter.new(instance)
|
||||
instance.characterData:init()
|
||||
|
||||
require("scripts.avatar_modules.head_ring")
|
||||
instance.headRing = HeadRing.new(instance)
|
||||
instance.headRing:init()
|
||||
|
||||
require("scripts.avatar_modules.head_model_generator")
|
||||
require("scripts.avatar_modules.head_block")
|
||||
instance.headBlock = HeadBlock.new(instance)
|
||||
instance.headBlock:init()
|
||||
|
||||
--生徒固有クラスの読み込み
|
||||
|
||||
events.ENTITY_INIT:register(function ()
|
||||
--ユーティリティクラスの読み込み
|
||||
require("scripts.avatar_modules.utils.player_utils")
|
||||
instance.playerUtils = PlayerUtils.new(instance)
|
||||
instance.playerUtils:init()
|
||||
|
||||
require("scripts.avatar_modules.utils.compatibility_utils")
|
||||
instance.compatibilityUtils = CompatibilityUtils.new(instance)
|
||||
instance.compatibilityUtils:init()
|
||||
|
||||
require("scripts.avatar_modules.utils.spawn_object_manager")
|
||||
require("scripts.avatar_modules.utils.spawn_object")
|
||||
|
||||
--アバターモジュールの読み込み
|
||||
require("scripts.avatar_modules.locale")
|
||||
instance.locale = Locale.new(instance)
|
||||
instance.locale:init()
|
||||
|
||||
require("scripts.avatar_modules.config")
|
||||
instance.config = Config.new(instance)
|
||||
instance.config:init()
|
||||
|
||||
require("scripts.avatar_modules.camera_manager")
|
||||
instance.cameraManager = CameraManager.new(instance)
|
||||
instance.cameraManager:init()
|
||||
|
||||
require("scripts.avatar_modules.key_manager")
|
||||
instance.keyManager = KeyManager.new(instance)
|
||||
instance.keyManager:init()
|
||||
|
||||
require("scripts.avatar_modules.vanilla_model")
|
||||
instance.vanillaModel = VanillaModel.new(instance)
|
||||
instance.vanillaModel:init()
|
||||
|
||||
require("scripts.avatar_modules.arms")
|
||||
instance.arms = Arms.new(instance)
|
||||
instance.arms:init()
|
||||
|
||||
require("scripts.avatar_modules.skirt")
|
||||
instance.skirt = Skirt.new(instance)
|
||||
instance.skirt:init()
|
||||
|
||||
require("scripts.avatar_modules.armor")
|
||||
instance.armor = Armor.new(instance)
|
||||
instance.armor:init()
|
||||
|
||||
require("scripts.avatar_modules.face_parts")
|
||||
instance.faceParts = FaceParts.new(instance)
|
||||
instance.faceParts:init()
|
||||
|
||||
require("scripts.avatar_modules.portrait")
|
||||
instance.portrait = Portrait.new(instance)
|
||||
instance.portrait:init()
|
||||
|
||||
require("scripts.avatar_modules.physics")
|
||||
instance.physics = Physics.new(instance)
|
||||
instance.physics:init()
|
||||
|
||||
require("scripts.avatar_modules.gun")
|
||||
require("scripts.character_scripts.gun_hoshino")
|
||||
instance.gun = GunHoshino.new(instance)
|
||||
instance.gun:init()
|
||||
|
||||
require("scripts.avatar_modules.nameplate")
|
||||
instance.nameplate = Nameplate.new(instance)
|
||||
instance.nameplate:init()
|
||||
|
||||
require("scripts.avatar_modules.ex_skill.ex_skill")
|
||||
instance.exSkill = ExSkill.new(instance)
|
||||
instance.exSkill:init()
|
||||
|
||||
require("scripts.avatar_modules.ex_skill.ex_skill_frame_particle_manager")
|
||||
require("scripts.avatar_modules.ex_skill.ex_skill_frame_particle")
|
||||
instance.frameParticleManager = ExSkillFrameParticleManager.new(instance)
|
||||
instance.frameParticleManager:init()
|
||||
|
||||
require("scripts.avatar_modules.placement_object.placement_object_manager")
|
||||
require("scripts.avatar_modules.placement_object.placement_object")
|
||||
instance.placementObjectManager = PlacementObjectManager.new(instance)
|
||||
instance.placementObjectManager:init()
|
||||
|
||||
require("scripts.avatar_modules.costume")
|
||||
instance.costume = Costume.new(instance)
|
||||
instance.costume:init()
|
||||
|
||||
require("scripts.avatar_modules.action_wheel.action_wheel")
|
||||
instance.actionWheel = ActionWheel.new(instance)
|
||||
instance.actionWheel:init()
|
||||
|
||||
require("scripts.avatar_modules.action_wheel.action_wheel_gui")
|
||||
instance.actionWheelGui = ActionWheelGui.new(instance)
|
||||
instance.actionWheelGui:init()
|
||||
|
||||
require("scripts.avatar_modules.bubble")
|
||||
instance.bubble = Bubble.new(instance)
|
||||
instance.bubble:init()
|
||||
|
||||
require("scripts.avatar_modules.barrier")
|
||||
instance.barrier = Barrier.new(instance)
|
||||
instance.barrier:init()
|
||||
|
||||
require("scripts.avatar_modules.death_animation")
|
||||
instance.deathAnimation = DeathAnimation.new(instance)
|
||||
instance.deathAnimation:init()
|
||||
|
||||
require("scripts.avatar_modules.hypixel_zombies")
|
||||
instance.hypixelZombies = HypixelZombies.new(instance)
|
||||
instance.hypixelZombies:init()
|
||||
|
||||
require("scripts.avatar_modules.action_wheel.update_checker")
|
||||
instance.updateChecker = UpdateChecker.new(instance)
|
||||
instance.updateChecker:init()
|
||||
|
||||
--生徒固有クラスの読み込み
|
||||
require("scripts.character_scripts.shield")
|
||||
instance.shield = Shield.new(instance)
|
||||
instance.shield:init()
|
||||
|
||||
require("scripts.character_scripts.whale_float")
|
||||
instance.whaleFloat = WhaleFloat.new(instance)
|
||||
instance.whaleFloat:init()
|
||||
|
||||
require("scripts.character_scripts.sub_gun")
|
||||
instance.subGun = SubGun.new(instance)
|
||||
instance.subGun:init()
|
||||
|
||||
require("scripts.character_scripts.ex_skill_2_wave_particle_manager")
|
||||
require("scripts.character_scripts.ex_skill_2_wave_particle")
|
||||
instance.waveParticleManager = ExSkill2WaveParticleManager.new(instance)
|
||||
instance.waveParticleManager:init()
|
||||
|
||||
--SCRIPT_INITイベントを実行
|
||||
instance.avatarEvents.SCRIPT_INIT:fire()
|
||||
end)
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---クラスをインスタンス化する。
|
||||
---@generic S
|
||||
---@generic C
|
||||
---@param class `C` インスタンス化するクラス
|
||||
---@param super? `S` インスタンス化するクラスのスーパークラス
|
||||
---@param ... any クラスのインスタンス時に渡される引数
|
||||
---@return C instance インスタンス化されたクラスのオブジェクト
|
||||
instantiate = function (class, super, ...)
|
||||
local instance = super and super.new(...) or {}
|
||||
setmetatable(instance, {__index = class})
|
||||
setmetatable(class, {__index = super})
|
||||
return instance
|
||||
end;
|
||||
}
|
||||
|
||||
AvatarInstance = Avatar.new()
|
|
@ -0,0 +1,319 @@
|
|||
---@class (exact) ActionWheel : AvatarModule アクションホイールを管理するクラス
|
||||
---@field package mainPage Page アクションホイールのメインページのインスタンスへの参照
|
||||
---@field package selectingCostume integer 現在選択中の衣装
|
||||
---@field package selectingName integer 現在選択中の表示名
|
||||
---@field package selectingShouldShowClubName boolean 現在選択中の「部活名を表示するかどうか」
|
||||
---@field package selectingExSkillParticleAmount integer 現在選択中のExスキルフレームのパーティクル量
|
||||
---@field public shouldReplaceVehicleModels boolean 乗り物のモデルを置き換えるかどうか
|
||||
---@field package isActionWheelOpenedPrev boolean 前ティックにアクションホイールを開けていたかどうか
|
||||
---@field package refreshCostumeChangeActionTitle fun(self: ActionWheel) 衣装変更アクションのタイトルを更新する
|
||||
---@field package refreshNameChangeActionTitle fun(self: ActionWheel) 名前変更アクションのタイトルを更新する
|
||||
---@field package refreshExSkillParticleActionTitle fun(self: ActionWheel) Exスキルアニメーションのパーティクル量調整アクションのタイトルを更新する
|
||||
---@field package refreshUpdateActionStatus fun(self: ActionWheel) アップデート確認アクションの状態を更新する
|
||||
|
||||
ActionWheel = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ActionWheel
|
||||
new = function (parent)
|
||||
---@type ActionWheel
|
||||
local instance = Avatar.instantiate(ActionWheel, AvatarModule, parent)
|
||||
|
||||
instance.mainPage = action_wheel:newPage("main")
|
||||
instance.selectingCostume = instance.parent.costume.currentCostume
|
||||
instance.selectingName = instance.parent.nameplate.currentName
|
||||
instance.selectingShouldShowClubName = instance.parent.nameplate.shouldShowClubName
|
||||
instance.selectingExSkillParticleAmount = instance.parent.exSkill.frameParticleAmount
|
||||
instance.shouldReplaceVehicleModels = instance.parent.config:loadConfig("PRIVATE", "replaceVehicleModels", true)
|
||||
instance.isActionWheelOpenedPrev = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self ActionWheel
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if host:isHost() then
|
||||
events.TICK:register(function()
|
||||
local isActionWheelOpened = action_wheel:isEnabled()
|
||||
if isActionWheelOpened then
|
||||
local mainAction3 = self.mainPage:getAction(3)
|
||||
mainAction3:setTitle(self.parent.locale:getLocale("action_wheel.main.action_3.title").."§c"..self.parent.locale:getLocale("action_wheel.toggle_off"))
|
||||
mainAction3:setToggleTitle(self.parent.locale:getLocale("action_wheel.main.action_3.title").."§a"..self.parent.locale:getLocale("action_wheel.toggle_on"))
|
||||
local mainAction4 = self.mainPage:getAction(4)
|
||||
mainAction4:setTitle(self.parent.locale:getLocale("action_wheel.main.action_4.title").."§c"..self.parent.locale:getLocale("action_wheel.toggle_off"))
|
||||
mainAction4:setToggleTitle(self.parent.locale:getLocale("action_wheel.main.action_4.title").."§a"..self.parent.locale:getLocale("action_wheel.toggle_on"))
|
||||
local mainAction6 = self.mainPage:getAction(6)
|
||||
if self.parent.characterData.actionWheel.isVehicleOptionEnabled then
|
||||
mainAction6:setTitle(self.parent.locale:getLocale("action_wheel.main.action_6.title").."§c"..self.parent.locale:getLocale("action_wheel.toggle_off"))
|
||||
else
|
||||
mainAction6:setTitle("§7"..self.parent.locale:getLocale("action_wheel.main.action_6.title")..self.parent.locale:getLocale("action_wheel.toggle_off"))
|
||||
end
|
||||
mainAction6:setToggleTitle(self.parent.locale:getLocale("action_wheel.main.action_6.title").."§a"..self.parent.locale:getLocale("action_wheel.toggle_on"))
|
||||
self:refreshCostumeChangeActionTitle()
|
||||
self:refreshNameChangeActionTitle()
|
||||
self:refreshExSkillParticleActionTitle()
|
||||
self:refreshUpdateActionStatus()
|
||||
elseif not isActionWheelOpened and self.isActionWheelOpenedPrev then
|
||||
if self.selectingCostume ~= self.parent.costume.currentCostume then
|
||||
pings.actionWheelChangeCostume(self.selectingCostume)
|
||||
self.parent.config:saveConfig("PRIVATE", "costume", self.selectingCostume)
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:item.armor.equip_leather"), player:getPos())
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_1.done_first")..self.parent.costume:getCostumeLocalName(self.selectingCostume)..self.parent.locale:getLocale("action_wheel.main.action_1.done_last"))
|
||||
end
|
||||
if self.selectingName ~= self.parent.nameplate.currentName or self.selectingShouldShowClubName ~= self.parent.nameplate.shouldShowClubName then
|
||||
pings.actionWheelChangeName(self.selectingName, self.selectingShouldShowClubName)
|
||||
self.parent.config:saveConfig("PRIVATE", "name", self.selectingName)
|
||||
self.parent.config:saveConfig("PRIVATE", "showClubName", self.selectingShouldShowClubName)
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:ui.cartography_table.take_result"), player:getPos())
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_2.done_first")..self.parent.nameplate:getName(self.selectingName)..self.parent.locale:getLocale("action_wheel.main.action_2.done_last"))
|
||||
end
|
||||
if self.selectingExSkillParticleAmount ~= self.parent.exSkill.frameParticleAmount then
|
||||
self.parent.exSkill.frameParticleAmount = self.selectingExSkillParticleAmount
|
||||
self.parent.config:saveConfig("PRIVATE", "exSkillFrameParticleAmount", self.selectingExSkillParticleAmount)
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.item.pickup"), player:getPos(), 1, 0.5)
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_5.done_first")..self.parent.locale:getLocale("action_wheel.main.action_5.option_"..self.selectingExSkillParticleAmount)..self.parent.locale:getLocale("action_wheel.main.action_5.done_last"))
|
||||
end
|
||||
end
|
||||
self.isActionWheelOpenedPrev = isActionWheelOpened
|
||||
end)
|
||||
|
||||
--アクションの設定
|
||||
|
||||
--アクション1. 衣装を変更
|
||||
self.mainPage:newAction(1):setItem(self.parent.compatibilityUtils:checkItem("minecraft:leather_chestplate")):setOnScroll(function(direction)
|
||||
if #self.parent.costume.costumeList >= 2 then
|
||||
if direction < 0 then
|
||||
self.selectingCostume = self.selectingCostume == #self.parent.costume.costumeList and 1 or self.selectingCostume + 1
|
||||
else
|
||||
self.selectingCostume = self.selectingCostume == 1 and #self.parent.costume.costumeList or self.selectingCostume - 1
|
||||
end
|
||||
self:refreshCostumeChangeActionTitle()
|
||||
else
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_1.unavailable"))
|
||||
end
|
||||
end):setOnLeftClick(function()
|
||||
if #self.parent.costume.costumeList >= 2 then
|
||||
self.selectingCostume = self.parent.costume.currentCostume
|
||||
self:refreshCostumeChangeActionTitle()
|
||||
end
|
||||
end):setOnRightClick(function()
|
||||
if #self.parent.costume.costumeList >= 2 then
|
||||
self.selectingCostume = 1
|
||||
self:refreshCostumeChangeActionTitle()
|
||||
end
|
||||
end)
|
||||
|
||||
if #self.parent.costume.costumeList >= 2 then
|
||||
local action = self.mainPage:getAction(1)
|
||||
action:setColor(0.78, 0.78, 0.78)
|
||||
action:setHoverColor(1, 1, 1)
|
||||
else
|
||||
local action = self.mainPage:getAction(1)
|
||||
action:setColor(0.16, 0.16, 0.16)
|
||||
action:setHoverColor(1, 0.33, 0.33)
|
||||
end
|
||||
|
||||
--アクション2. 表示名の変更
|
||||
self.mainPage:newAction(2):setItem(self.parent.compatibilityUtils:checkItem("minecraft:name_tag")):setColor(0.78, 0.78, 0.78):setHoverColor(1, 1, 1):setOnScroll(function(direction)
|
||||
if direction < 0 then
|
||||
self.selectingName = self.selectingName == 6 and 1 or self.selectingName + 1
|
||||
else
|
||||
self.selectingName = self.selectingName == 1 and 6 or self.selectingName - 1
|
||||
end
|
||||
self:refreshNameChangeActionTitle()
|
||||
end):setOnLeftClick(function()
|
||||
self.selectingShouldShowClubName = not self.selectingShouldShowClubName
|
||||
self:refreshNameChangeActionTitle()
|
||||
end):setOnRightClick(function()
|
||||
self.selectingName = 1
|
||||
self.selectingShouldShowClubName = false
|
||||
self:refreshNameChangeActionTitle()
|
||||
end)
|
||||
|
||||
--アクション3. 防具の表示
|
||||
self.mainPage:newAction(3):setItem(self.parent.compatibilityUtils:checkItem("minecraft:iron_chestplate")):setColor(0.67, 0, 0):setHoverColor(1, 0.33, 0.33):setToggleColor(0, 0.67, 0):setOnToggle(function (_, action)
|
||||
pings.actionWheelSetArmorVisible(true)
|
||||
action:setHoverColor(0.33, 1, 0.33)
|
||||
self.parent.config:saveConfig("PRIVATE", "showArmor", true)
|
||||
end):setOnUntoggle(function(_, action)
|
||||
pings.actionWheelSetArmorVisible(false)
|
||||
action:setHoverColor(1, 0.33, 0.33)
|
||||
self.parent.config:saveConfig("PRIVATE", "showArmor", false)
|
||||
end)
|
||||
if self.parent.config:loadConfig("PRIVATE", "showArmor", false) then
|
||||
local action = self.mainPage:getAction(3)
|
||||
action:setToggled(true)
|
||||
action:setHoverColor(0.33, 1, 0.33)
|
||||
end
|
||||
|
||||
--アクション4. 一人称視点での武器モデルの表示
|
||||
self.mainPage:newAction(4):setItem(self.parent.compatibilityUtils:checkItem("minecraft:bow")):setColor(0.67, 0, 0):setHoverColor(1, 0.33, 0.33):setToggleColor(0, 0.67, 0):setOnToggle(function (_, action)
|
||||
self.parent.gun.shouldShowWeaponInFirstPerson = true
|
||||
action:setHoverColor(0.33, 1, 0.33)
|
||||
self.parent.config:saveConfig("PRIVATE", "firstPersonWeapon", true)
|
||||
end):setOnUntoggle(function (_, action)
|
||||
self.parent.gun.shouldShowWeaponInFirstPerson = false
|
||||
action:setHoverColor(1, 0.33, 0.33)
|
||||
self.parent.config:saveConfig("PRIVATE", "firstPersonWeapon", false)
|
||||
end)
|
||||
if self.parent.config:loadConfig("PRIVATE", "firstPersonWeapon", true) then
|
||||
local action = self.mainPage:getAction(4)
|
||||
action:setToggled(true)
|
||||
action:setHoverColor(0.33, 1, 0.33)
|
||||
end
|
||||
|
||||
--アクション5. Exスキルフレームのパーティクルの量
|
||||
self.mainPage:newAction(5):setItem(self.parent.compatibilityUtils:checkItem("minecraft:glowstone_dust")):setColor(0.78, 0.78, 0.78):setHoverColor(1, 1, 1):setOnScroll(function (direction)
|
||||
if direction < 0 then
|
||||
self.selectingExSkillParticleAmount = self.selectingExSkillParticleAmount == 4 and 1 or self.selectingExSkillParticleAmount + 1
|
||||
else
|
||||
self.selectingExSkillParticleAmount = self.selectingExSkillParticleAmount == 1 and 4 or self.selectingExSkillParticleAmount - 1
|
||||
end
|
||||
self:refreshExSkillParticleActionTitle()
|
||||
end):setOnLeftClick(function ()
|
||||
self.selectingExSkillParticleAmount = self.parent.exSkill.frameParticleAmount
|
||||
self:refreshExSkillParticleActionTitle()
|
||||
end):setOnRightClick(function ()
|
||||
self.selectingExSkillParticleAmount = 1
|
||||
self:refreshExSkillParticleActionTitle()
|
||||
end)
|
||||
|
||||
--アクション6. 乗り物モデルの置き換え
|
||||
self.mainPage:newAction(6):setItem(self.parent.compatibilityUtils:checkItem("minecraft:oak_boat")):setColor(0.67, 0, 0):setHoverColor(1, 0.33, 0.33):setToggleColor(0, 0.67, 0):setOnToggle(function (_, action)
|
||||
if self.parent.characterData.actionWheel.isVehicleOptionEnabled then
|
||||
pings.actionWheelSetShouldReplaceVehicleModels(true)
|
||||
action:setHoverColor(0.33, 1, 0.33)
|
||||
self.parent.config:saveConfig("PRIVATE", "replaceVehicleModels", true)
|
||||
else
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_6.unavailable"))
|
||||
action:setToggled(false)
|
||||
end
|
||||
end):setOnUntoggle(function (_, action)
|
||||
pings.actionWheelSetShouldReplaceVehicleModels(false)
|
||||
action:setHoverColor(1, 0.33, 0.33)
|
||||
self.parent.config:saveConfig("PRIVATE", "replaceVehicleModels", false)
|
||||
end)
|
||||
if not self.parent.characterData.actionWheel.isVehicleOptionEnabled then
|
||||
local action = self.mainPage:getAction(6)
|
||||
action:setColor(0.16, 0.16, 0.16)
|
||||
action:setHoverColor(1, 0.33, 0.33)
|
||||
self.shouldReplaceVehicleModels = false
|
||||
elseif self.shouldReplaceVehicleModels then
|
||||
local action = self.mainPage:getAction(6)
|
||||
action:setToggled(true)
|
||||
action:setHoverColor(0.33, 1, 0.33)
|
||||
end
|
||||
|
||||
--アクション7. アップデートの確認
|
||||
self.mainPage:newAction(7):setItem("minecraft:compass"):setOnLeftClick(function ()
|
||||
if not self.parent.updateChecker.checkerStatus ~= "CHECKING" then
|
||||
self.parent.updateChecker:checkUpdate()
|
||||
else
|
||||
print("action_wheel.main.action_7.ongoing")
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.note_block.bass"), player:getPos(), 1, 0.5)
|
||||
end
|
||||
if not net:isNetworkingAllowed() or not net:isLinkAllowed("https://api.github.com") then
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_7.networking_api"))
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.note_block.bass"), player:getPos(), 1, 0.5)
|
||||
end
|
||||
end):onRightClick(function ()
|
||||
if self.parent.updateChecker.latestVersion ~= nil then
|
||||
host:setClipboard("https://github.com/Gakuto1112/FiguraBlueArchiveCharacters/releases/tag/"..self.parent.updateChecker.latestVersion)
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_7.copied"))
|
||||
else
|
||||
print(self.parent.locale:getLocale("action_wheel.main.action_7.cannot_check_latest"))
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.note_block.bass"), player:getPos(), 1, 0.5)
|
||||
end
|
||||
end)
|
||||
|
||||
--アクション8. (空欄)
|
||||
self.mainPage:newAction(8):setColor(0.16, 0.16, 0.16):setHoverColor(0.16, 0.16, 0.16)
|
||||
|
||||
action_wheel:setPage(self.mainPage)
|
||||
end
|
||||
end;
|
||||
|
||||
---衣装変更アクションのタイトルを更新する。
|
||||
---@param self ActionWheel
|
||||
refreshCostumeChangeActionTitle = function (self)
|
||||
if #self.parent.costume.costumeList >= 2 then
|
||||
self.mainPage:getAction(1):setTitle(self.parent.locale:getLocale("action_wheel.main.action_1.title").."§b"..self.parent.costume:getCostumeLocalName(self.selectingCostume))
|
||||
else
|
||||
self.mainPage:getAction(1):setTitle("§7"..self.parent.locale:getLocale("action_wheel.main.action_1.title")..self.parent.costume:getCostumeLocalName(self.selectingCostume))
|
||||
end
|
||||
end;
|
||||
|
||||
---名前変更アクションのタイトルを更新する。
|
||||
---@param self ActionWheel
|
||||
refreshNameChangeActionTitle = function (self)
|
||||
if self.selectingName >= 2 then
|
||||
if self.selectingShouldShowClubName then
|
||||
self.mainPage:getAction(2):setTitle(self.parent.locale:getLocale("action_wheel.main.action_2.title").."§b"..self.parent.nameplate:getName(self.selectingName).."\n§r"..self.parent.locale:getLocale("action_wheel.main.action_2.title_2").."§a"..self.parent.locale:getLocale("action_wheel.toggle_on"))
|
||||
else
|
||||
self.mainPage:getAction(2):setTitle(self.parent.locale:getLocale("action_wheel.main.action_2.title").."§b"..self.parent.nameplate:getName(self.selectingName).."\n§r"..self.parent.locale:getLocale("action_wheel.main.action_2.title_2").."§c"..self.parent.locale:getLocale("action_wheel.toggle_off"))
|
||||
end
|
||||
else
|
||||
self.mainPage:getAction(2):setTitle(self.parent.locale:getLocale("action_wheel.main.action_2.title").."§b"..self.parent.nameplate:getName(self.selectingName).."\n§7"..self.parent.locale:getLocale("action_wheel.main.action_2.title_2")..self.parent.locale:getLocale("action_wheel.toggle_"..(self.selectingShouldShowClubName and "on" or "off")))
|
||||
end
|
||||
end;
|
||||
|
||||
---Exスキルアニメーションのパーティクル量調整アクションのタイトルを更新する。
|
||||
---@param self ActionWheel
|
||||
refreshExSkillParticleActionTitle = function (self)
|
||||
self.mainPage:getAction(5):title(self.parent.locale:getLocale("action_wheel.main.action_5.title").."§b"..self.parent.locale:getLocale("action_wheel.main.action_5.option_"..self.selectingExSkillParticleAmount))
|
||||
end;
|
||||
|
||||
---アップデート確認アクションの状態を更新する。
|
||||
---@param self ActionWheel
|
||||
refreshUpdateActionStatus = function (self)
|
||||
local action = self.mainPage:getAction(7)
|
||||
local actionTitle = ""
|
||||
if self.parent.updateChecker.checkerStatus == "CHECKING" then
|
||||
actionTitle = actionTitle.."§7"..self.parent.locale:getLocale("action_wheel.main.action_7.title_1")..self.parent.locale:getLocale("action_wheel.main.action_7.title_2").."\n"
|
||||
action:setColor(0.16, 0.16, 0.16)
|
||||
action:setHoverColor(1, 0.33, 0.33)
|
||||
else
|
||||
actionTitle = actionTitle..self.parent.locale:getLocale("action_wheel.main.action_7.title_1").."§b"..self.parent.locale:getLocale("action_wheel.main.action_7.title_2").."\n"
|
||||
action:setColor(0.78, 0.78, 0.78)
|
||||
action:setHoverColor(1, 1, 1)
|
||||
end
|
||||
if self.parent.updateChecker.latestVersion == nil then
|
||||
actionTitle = actionTitle.."§7"..self.parent.locale:getLocale("action_wheel.main.action_7.title_3")..self.parent.locale:getLocale("action_wheel.main.action_7.title_4")
|
||||
else
|
||||
actionTitle = actionTitle.."§r"..self.parent.locale:getLocale("action_wheel.main.action_7.title_3").."§b"..self.parent.locale:getLocale("action_wheel.main.action_7.title_4")
|
||||
end
|
||||
action:setTitle(actionTitle)
|
||||
end;
|
||||
}
|
||||
|
||||
---アクションホイールから衣装を変更するトリガー関数
|
||||
---@param costumeId integer 新しい衣装のインデックス番号
|
||||
function pings.actionWheelChangeCostume(costumeId)
|
||||
if costumeId >= 2 then
|
||||
AvatarInstance.costume:setCostume(costumeId)
|
||||
else
|
||||
AvatarInstance.costume:resetCostume()
|
||||
end
|
||||
end
|
||||
|
||||
---アクションホイールから名前を変更するトリガー関数
|
||||
---@param typeId integer 新しい名前の表示形式のインデックス番号
|
||||
---@param shouldShowClubName boolean 部活名を表示するかどうか
|
||||
function pings.actionWheelChangeName(typeId, shouldShowClubName)
|
||||
AvatarInstance.nameplate:setName(typeId, shouldShowClubName)
|
||||
end
|
||||
|
||||
---アクションホイールから防具の可視性を変更するトリガー関数
|
||||
---@param visible boolean 防具を表示するかどうか
|
||||
function pings.actionWheelSetArmorVisible(visible)
|
||||
AvatarInstance.armor.shouldShowArmor = visible
|
||||
end
|
||||
|
||||
---アクションホイールから乗り物モデルの置き換えを変更するトリガー関数
|
||||
---@param enabled boolean 乗り物モデルの置き換えを有効化するかどうか
|
||||
function pings.actionWheelSetShouldReplaceVehicleModels(enabled)
|
||||
AvatarInstance.actionWheel.shouldReplaceVehicleModels = enabled
|
||||
end
|
|
@ -0,0 +1,113 @@
|
|||
---@class ActionWheelGui : AvatarModule アクションホイールに表示する追加のGUIを管理するクラス
|
||||
---@field package isActionWheelOpenedPrev boolean 前ティックにアクションホイールを開けていたかどうか
|
||||
|
||||
ActionWheelGui = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ActionWheelGui
|
||||
new = function (parent)
|
||||
---@type ActionWheelGui
|
||||
local instance = Avatar.instantiate(ActionWheelGui, AvatarModule, parent)
|
||||
|
||||
instance.isActionWheelOpenedPrev = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self ActionWheelGui
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if host:isHost() then
|
||||
events.TICK:register(function ()
|
||||
local isActionWheelOpened = action_wheel:isEnabled()
|
||||
if isActionWheelOpened and not self.isActionWheelOpenedPrev then
|
||||
models.models.action_wheel_gui.Gui:setVisible(true)
|
||||
|
||||
local windowSize = client:getScaledWindowSize()
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide:setPos(windowSize.x * -0.5 + 44, windowSize.y * -0.5 + 5, 0)
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide:setPos(windowSize.x * -0.5 + 57, -21, 0)
|
||||
models.models.action_wheel_gui.Gui.VersionDisplay:setPos(-0.75, -0.5, 0)
|
||||
|
||||
local bubbleGuideTextTasks = {models.models.action_wheel_gui.Gui.BubbleGuide:getTask("action_wheel.gui.bubble_guide.title"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.GoodEmoji:getTask("bubble_guide.bubble_1.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.HeartEmoji:getTask("bubble_guide.bubble_2.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.NoteEmoji:getTask("bubble_guide.bubble_3.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.QuestionEmoji:getTask("bubble_guide.bubble_4.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.SweatEmoji:getTask("bubble_guide.bubble_5.key_name")}
|
||||
bubbleGuideTextTasks[1]:setText(self.parent.locale:getLocale("action_wheel.gui.bubble_guide.title"))
|
||||
for i = 2, #bubbleGuideTextTasks do
|
||||
bubbleGuideTextTasks[i]:setText("§0"..self.parent.keyManager.keyMappings["bubble_"..(i - 1)]:getKeyName())
|
||||
end
|
||||
|
||||
local bubbleGuideTitleWidth = client.getTextWidth(bubbleGuideTextTasks[1]:getText()) / 2 + 4
|
||||
local bubbleGuideBodyWidth = 0
|
||||
for i = 2, #bubbleGuideTextTasks do
|
||||
bubbleGuideBodyWidth = math.max(bubbleGuideBodyWidth, client.getTextWidth(bubbleGuideTextTasks[i]:getText()) * 0.5)
|
||||
end
|
||||
local bubbleGuideWidth = math.max(bubbleGuideTitleWidth + 6, bubbleGuideBodyWidth + 22)
|
||||
bubbleGuideWidth = math.max(bubbleGuideWidth, 39)
|
||||
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.TitleBar:setPos((bubbleGuideWidth - bubbleGuideTitleWidth) / 2, 0, 0)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.TitleBar:setScale(bubbleGuideTitleWidth, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.TitleCenter:setScale(bubbleGuideWidth - 25, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.TitleLeft:setPos(bubbleGuideWidth - 26, 0, 0)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.BodyTop:setScale(bubbleGuideWidth, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.BodyBottomCenter:setScale(bubbleGuideWidth - 39, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.BubbleGuideBackground.BodyBottomLeft:setPos(bubbleGuideWidth - 40, 0, 0)
|
||||
models.models.action_wheel_gui.Gui.BubbleGuide.Emojis:setPos((bubbleGuideWidth - (bubbleGuideBodyWidth + 22)) / 2 + bubbleGuideBodyWidth + 9, 0, 0)
|
||||
bubbleGuideTextTasks[1]:setPos(bubbleGuideWidth / 2, 0, 0)
|
||||
for i = 2, #bubbleGuideTextTasks do
|
||||
bubbleGuideTextTasks[i]:setPos(bubbleGuideBodyWidth / -2 - 4, 1.5, 0)
|
||||
end
|
||||
|
||||
local exSkillGuideTextTasks = {models.models.action_wheel_gui.Gui.ExSkillGuide:getTask("action_wheel.gui.ex_skill_guide.title"), models.models.action_wheel_gui.Gui.ExSkillGuide:getTask("action_wheel.gui.ex_skill_guide.body_1"), models.models.action_wheel_gui.Gui.ExSkillGuide:getTask("action_wheel.gui.ex_skill_guide.body_2")}
|
||||
exSkillGuideTextTasks[1]:setText(self.parent.locale:getLocale("action_wheel.gui.ex_skill_guide.title"))
|
||||
exSkillGuideTextTasks[2]:setText(self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill == nil and "§0§l\""..self.parent.locale:getLocale("action_wheel.gui.ex_skill_guide.ex_skill_"..self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].exSkill..".name").."\"" or "§0§l\""..self.parent.locale:getLocale("action_wheel.gui.ex_skill_guide.ex_skill_"..self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].exSkill..".name").."\"§r§0 - \""..self.parent.keyManager.keyMappings.ex_skill:getKeyName().."\"")
|
||||
exSkillGuideTextTasks[3]:setText(self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill == nil and "§0"..self.parent.locale:getLocale("action_wheel.gui.ex_skill_guide.key_pre")..self.parent.keyManager.keyMappings.ex_skill:getKeyName()..self.parent.locale:getLocale("action_wheel.gui.ex_skill_guide.key_post") or "§0§l\""..self.parent.locale:getLocale("action_wheel.gui.ex_skill_guide.ex_skill_"..self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill..".name").."\"§r§0 - \""..self.parent.keyManager.keyMappings.ex_skill_sub:getKeyName().."\"")
|
||||
|
||||
local exSkillGuideTitleWidth = client.getTextWidth(exSkillGuideTextTasks[1]:getText()) / 2 + 4
|
||||
local exSkillGuideWidth = exSkillGuideTitleWidth
|
||||
for i = 2, #exSkillGuideTextTasks do
|
||||
exSkillGuideWidth = math.max(exSkillGuideWidth, client.getTextWidth(exSkillGuideTextTasks[i]:getText()) / 2 + 10)
|
||||
end
|
||||
exSkillGuideWidth = math.max(exSkillGuideWidth, 39)
|
||||
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide.ExSkillGuideBackground.TitleBar:setPos((exSkillGuideWidth - exSkillGuideTitleWidth) / 2, 0, 0)
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide.ExSkillGuideBackground.TitleBar:setScale(exSkillGuideTitleWidth, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide.ExSkillGuideBackground.TitleCenter:setScale(exSkillGuideWidth - 25, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide.ExSkillGuideBackground.TitleLeft:setPos(exSkillGuideWidth - 26, 0, 0)
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide.ExSkillGuideBackground.BodyBottomCenter:setScale(exSkillGuideWidth - 39, 1, 1)
|
||||
models.models.action_wheel_gui.Gui.ExSkillGuide.ExSkillGuideBackground.BodyBottomLeft:setPos(exSkillGuideWidth - 40, 0, 0)
|
||||
exSkillGuideTextTasks[1]:setPos(exSkillGuideWidth / 2, 0, 0)
|
||||
for i = 2, #exSkillGuideTextTasks do
|
||||
exSkillGuideTextTasks[i]:setPos(exSkillGuideWidth / 2, (i - 2) * -5 - 8)
|
||||
end
|
||||
|
||||
local versionDisplayTextTasks = {}
|
||||
for i = 1, 3 do
|
||||
table.insert(versionDisplayTextTasks, models.models.action_wheel_gui.Gui.VersionDisplay:getTask("action_wheel.gui.version_display.l"..i))
|
||||
end
|
||||
|
||||
for i = 2, 3 do
|
||||
versionDisplayTextTasks[i]:setPos(0, (i - 1) * -2.25, 0)
|
||||
end
|
||||
|
||||
elseif not isActionWheelOpened and self.isActionWheelOpenedPrev then
|
||||
models.models.action_wheel_gui.Gui:setVisible(false)
|
||||
end
|
||||
self.isActionWheelOpenedPrev = isActionWheelOpened
|
||||
end)
|
||||
|
||||
models.models.action_wheel_gui.Gui:setScale(2, 2, 2)
|
||||
for _, textTask in ipairs({models.models.action_wheel_gui.Gui.BubbleGuide:newText("action_wheel.gui.bubble_guide.title"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.GoodEmoji:newText("bubble_guide.bubble_1.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.HeartEmoji:newText("bubble_guide.bubble_2.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.NoteEmoji:newText("bubble_guide.bubble_3.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.QuestionEmoji:newText("bubble_guide.bubble_4.key_name"), models.models.action_wheel_gui.Gui.BubbleGuide.Emojis.SweatEmoji:newText("bubble_guide.bubble_5.key_name"), models.models.action_wheel_gui.Gui.ExSkillGuide:newText("action_wheel.gui.ex_skill_guide.title"), models.models.action_wheel_gui.Gui.ExSkillGuide:newText("action_wheel.gui.ex_skill_guide.body_1"), models.models.action_wheel_gui.Gui.ExSkillGuide:newText("action_wheel.gui.ex_skill_guide.body_2")}) do
|
||||
textTask:setScale(0.5, 0.5, 0.5)
|
||||
textTask:setAlignment("CENTER")
|
||||
end
|
||||
for i = 1, 3 do
|
||||
local textTask = models.models.action_wheel_gui.Gui.VersionDisplay:newText("action_wheel.gui.version_display.l"..i)
|
||||
textTask:setScale(0.25, 0.25, 0.25)
|
||||
textTask:setShadow(true)
|
||||
if i == 1 then
|
||||
textTask:setText("Figura Blue Archive Characters (FBAC)")
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
---@alias UpdateChecker.CheckerStatus
|
||||
---| "INIT" # 初期状態
|
||||
---| "CHECKING" # アップデート確認中
|
||||
---| "LATEST" # アップデート確認済み:最新版
|
||||
---| "UPDATE_AVAILABLE" # アップデート確認済み:アップデートあり
|
||||
---| "ERROR_INVALID_JSON" # エラー:予期しないJSONデータ
|
||||
---| "ERROR_INVALID_JSON_SYNTAX" # エラー:不正なJSON構文
|
||||
---| "ERROR_REQUEST_FAILED" # リクエストに失敗
|
||||
---| "ERROR_NETWORK_ERR" # ネットワークエラー
|
||||
---| "ERROR_NOT_ALLOWED" # ネットワーキングAPIが不許可
|
||||
|
||||
---@class (exact) UpdateChecker : AvatarModule FBACのアップデートの確認を管理するクラス
|
||||
---@field package FBAC_VERSION string 現在のFBACバージョン
|
||||
---@field package BRANCH_NAME string このブランチ名(キャラクター名)
|
||||
---@field public latestVersion? string リモート上にある最新のFBACバージョン
|
||||
---@field public checkerStatus UpdateChecker.CheckerStatus アップデートチェッカーの状態
|
||||
---@field package requestStatus integer 送信したリクエストのステータスコード
|
||||
---@field package responseHandler Future.HttpResponse|nil httpレスポンスのハンドラ
|
||||
---@field package textAnimationCount integer 新しいバージョン表示のテキストのアニメーションのカウンター
|
||||
---@field package isActionWheelOpenedPrev boolean 前ティックにアクションホイールを開けていたかどうか
|
||||
---@field package compareVersions fun(version1: string, version2: string): string|nil 2つのバージョン文字列を比較し、新しい方を返す
|
||||
---@field package showNewUpdateMessage fun(self: UpdateChecker) 新FBACバージョンのお知らせを表示する
|
||||
---@field public checkUpdate fun(self: UpdateChecker) FBACアップデートの確認を行う
|
||||
|
||||
UpdateChecker = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return UpdateChecker
|
||||
new = function (parent)
|
||||
---@type UpdateChecker
|
||||
local instance = Avatar.instantiate(UpdateChecker, AvatarModule, parent)
|
||||
|
||||
instance.FBAC_VERSION = "v2.1.2"
|
||||
instance.BRANCH_NAME = "Hoshino"
|
||||
instance.latestVersion = instance.parent.config:loadConfig("PUBLIC", "latestVersion", nil)
|
||||
instance.checkerStatus = "INIT"
|
||||
instance.requestStatus = 0
|
||||
instance.textAnimationCount = 0
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self UpdateChecker
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if host:isHost() then
|
||||
models.models.action_wheel_gui.Gui.VersionDisplay:getTask("action_wheel.gui.version_display.l2"):setText(self.FBAC_VERSION.." - "..self.BRANCH_NAME)
|
||||
|
||||
events.TICK:register(function ()
|
||||
local isActionWheelOpened = action_wheel:isEnabled()
|
||||
if isActionWheelOpened then
|
||||
local textTask = models.models.action_wheel_gui.Gui.VersionDisplay:getTask("action_wheel.gui.version_display.l3")
|
||||
if self.checkerStatus == "UPDATE_AVAILABLE" then
|
||||
if math.floor(self.textAnimationCount / 20) % 2 == 0 then
|
||||
textTask:setText("§6§n"..self.parent.locale:getLocale("action_wheel.gui.update_check.update_available")..self.latestVersion)
|
||||
else
|
||||
textTask:setText("§n"..self.parent.locale:getLocale("action_wheel.gui.update_check.update_available")..self.latestVersion)
|
||||
end
|
||||
self.textAnimationCount = self.textAnimationCount + 1
|
||||
elseif self.checkerStatus == "ERROR_REQUEST_FAILED" then
|
||||
textTask:setText(self.parent.locale:getLocale("action_wheel.gui.update_check.error_request_failed").."("..self.requestStatus..")")
|
||||
else
|
||||
textTask:setText(self.parent.locale:getLocale("action_wheel.gui.update_check."..self.checkerStatus:lower()))
|
||||
end
|
||||
elseif not isActionWheelOpened and self.isActionWheelOpenedPrev then
|
||||
self.textAnimationCount = 0
|
||||
end
|
||||
self.isActionWheelOpenedPrev = isActionWheelOpened
|
||||
end)
|
||||
|
||||
local lastUpdateCheckTime = self.parent.config:loadConfig("PUBLIC", "lastUpdateCheckTime", 0)
|
||||
if client:getSystemTime() >= lastUpdateCheckTime + 86400000 then
|
||||
self:checkUpdate()
|
||||
else
|
||||
local newerVersion = self.compareVersions(self.latestVersion, self.FBAC_VERSION)
|
||||
if newerVersion ~= nil and newerVersion ~= self.FBAC_VERSION then
|
||||
self:showNewUpdateMessage()
|
||||
self.checkerStatus = "UPDATE_AVAILABLE"
|
||||
else
|
||||
self.checkerStatus = "LATEST"
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
---2つのバージョン文字列を比較し、新しい方を返す。
|
||||
---@param version1 string 比較するバージョン文字列1
|
||||
---@param version2 string 比較するバージョン文字列2
|
||||
---@return string|nil newerVersion 新しい方のバージョン文字列。比較不可能だった場合はnilを返す。
|
||||
compareVersions = function (version1, version2)
|
||||
local major1, minor1, patch1 = version1:match("^v(%d+)%.(%d+)%.(%d+)")
|
||||
local major2, minor2, patch2 = version2:match("^v(%d+)%.(%d+)%.(%d+)")
|
||||
major1 = tonumber(major1)
|
||||
minor1 = tonumber(minor1)
|
||||
patch1 = tonumber(patch1)
|
||||
major2 = tonumber(major2)
|
||||
minor2 = tonumber(minor2)
|
||||
patch2 = tonumber(patch2)
|
||||
if major1 ~= nil and minor1 ~= nil and patch1 ~= nil and major2 ~= nil and minor2 ~= nil and patch2 ~= nil then
|
||||
return (major1 > major2 or (major1 == major2 and minor1 > minor2) or (major1 == major2 and minor1 == minor2 and patch1 > patch2)) and version1 or version2
|
||||
end
|
||||
end;
|
||||
|
||||
---新FBACバージョンのお知らせを表示する。
|
||||
---@param self UpdateChecker
|
||||
showNewUpdateMessage = function (self)
|
||||
print(self.parent.locale:getLocale("action_wheel.gui.update_check.update_available")..self.latestVersion)
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.experience_orb.pickup"), player:getPos(), 1, 1)
|
||||
end;
|
||||
|
||||
---FBACアップデートの確認を行う。
|
||||
---@param self UpdateChecker
|
||||
checkUpdate = function (self)
|
||||
if host:isHost() and self.checkerStatus ~= "CHECKING" then
|
||||
self.checkerStatus = "CHECKING"
|
||||
if net:isNetworkingAllowed() and net:isLinkAllowed("https://api.github.com") then
|
||||
local request = net.http:request("https://api.github.com/repos/Gakuto1112/FiguraBlueArchiveCharacters/tags")
|
||||
self.responseHandler = request:send()
|
||||
events.TICK:register(function ()
|
||||
if self.responseHandler:isDone() then
|
||||
local response = self.responseHandler:getValue()
|
||||
if response ~= nil then
|
||||
local stats = response:getResponseCode()
|
||||
if math.floor(stats / 100) == 2 then
|
||||
local stream = response:getData()
|
||||
local buffer = data:createBuffer()
|
||||
buffer:readFromStream(stream)
|
||||
buffer:setPosition(0)
|
||||
local jsonData = buffer:readByteArray()
|
||||
if json.isSerializable(jsonData) then
|
||||
local parseData = parseJson(jsonData)
|
||||
if parseData[1] ~= nil and parseData[1].name ~= nil then
|
||||
local newerVersion = self.compareVersions(parseData[1].name, self.FBAC_VERSION)
|
||||
if newerVersion ~= nil then
|
||||
if newerVersion ~= self.FBAC_VERSION then
|
||||
--新しいバージョンがある
|
||||
self.latestVersion = parseData[1].name
|
||||
self.checkerStatus = "UPDATE_AVAILABLE"
|
||||
self:showNewUpdateMessage()
|
||||
else
|
||||
--現在は最新
|
||||
self.latestVersion = parseData[1].name
|
||||
self.checkerStatus = "LATEST"
|
||||
end
|
||||
self.parent.config:saveConfig("PUBLIC", "lastUpdateCheckTime", client:getSystemTime())
|
||||
self.parent.config:saveConfig("PUBLIC", "latestVersion", parseData[1].name)
|
||||
else
|
||||
--予期しないJSONデータ
|
||||
self.checkerStatus = "ERROR_INVALID_JSON"
|
||||
end
|
||||
else
|
||||
--予期しないJSONデータ
|
||||
self.checkerStatus = "ERROR_INVALID_JSON"
|
||||
end
|
||||
else
|
||||
--JSON解析エラー
|
||||
self.checkerStatus = "ERROR_INVALID_JSON_SYNTAX"
|
||||
end
|
||||
stream:close()
|
||||
buffer:close()
|
||||
else
|
||||
--ステータスコードが200番台以外
|
||||
self.checkerStatus = "ERROR_REQUEST_FAILED"
|
||||
self.requestStatus = stats
|
||||
end
|
||||
else
|
||||
--ネットワークエラー
|
||||
self.checkerStatus = "ERROR_NETWORK_ERR"
|
||||
end
|
||||
events.TICK:remove("update_checker_http_tick")
|
||||
end
|
||||
end, "update_checker_http_tick")
|
||||
else
|
||||
---ネットワーキングAPIが不許可
|
||||
self.checkerStatus = "ERROR_NOT_ALLOWED"
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
---防具の部位
|
||||
---@alias Armor.ArmorPart
|
||||
---| "HELMET" # ヘルメット
|
||||
---| "CHEST_PLATE" # チェストプレート
|
||||
---| "LEGGINGS" # レギンス
|
||||
---| "BOOTS" # ブーツ
|
||||
|
||||
---@class (exact) Armor : AvatarModule 防具の表示を制御するクラス
|
||||
---@field public shouldShowArmor boolean 防具を表示するかどうか
|
||||
---@field public armorSlotItems ItemStack[] 現ティックの防具スロットのアイテム
|
||||
---@field package armorSlotItemsPrev ItemStack[] 前ティックの防具スロットのアイテム
|
||||
---@field public isArmorVisible Armor.VisiblePartsSet 各防具の部位(ヘルメット、チェストプレート、レギンス、ブーツ)が可視状態かどうか
|
||||
---@field package textureQueue Armor.TextureQueueData[] テクスチャ処理のキュー
|
||||
---@field package getArmorColor fun(armorItem: ItemStack): number 防具の色を取得する
|
||||
---@field package compareTrims fun(trim1?: Armor.TrimData, trim2?: Armor.TrimData): boolean 防具装飾が同じものか比較する
|
||||
---@field package addTextureQueue fun(self: Armor, texture: Texture, paletteName: string) テクスチャの処理のキューにデータを挿入する
|
||||
---@field package getTrimTexture fun(self: Armor, trimData?: Armor.TrimData, armorId: string): Texture|nil バニラパーツの防具装飾のテクスチャを取得する。テクスチャの処理は次のチック以降行われる。
|
||||
---@field public setHelmet fun(self: Armor, helmetItem: ItemStack) ヘルメットを更新する
|
||||
---@field public setChestplate fun(self: Armor, chestplateItem: ItemStack) チェストプレートを更新する
|
||||
---@field public setLeggings fun(self: Armor, leggingsItem: ItemStack) レギンスを更新する
|
||||
---@field public setBoots fun(self: Armor, bootsItem: ItemStack) ブーツを更新する
|
||||
|
||||
---@class (exact) Armor.VisiblePartsSet 各防具の部位の可視状態のセット
|
||||
---@field public helmet boolean ヘルメット
|
||||
---@field public chestplate boolean チェストプレート
|
||||
---@field public leggings boolean レギンス
|
||||
---@field public boots boolean ブーツ
|
||||
|
||||
---@class (exact) Armor.TextureQueueData テクスチャキューに入るデータの構造体
|
||||
---@field public texture Texture 処理対象のテクスチャ
|
||||
---@field public palette Texture 処理に使用するパレットのテクスチャ
|
||||
---@field public iterationCount integer 現在の繰り返しカウンター
|
||||
|
||||
---@class (exact) Armor.TrimData 防具装飾のデータセット
|
||||
---@field public pattern string 防具装飾の模様
|
||||
---@field public material string 防具装飾の素材
|
||||
|
||||
Armor = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Armor
|
||||
new = function (parent)
|
||||
---@type Armor
|
||||
local instance = Avatar.instantiate(Armor, AvatarModule, parent)
|
||||
instance.shouldShowArmor = instance.parent.config:loadConfig("PRIVATE", "showArmor", false)
|
||||
instance.armorSlotItems = {world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air"))}
|
||||
instance.armorSlotItemsPrev = {world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air"))}
|
||||
instance.isArmorVisible = {
|
||||
helmet = false;
|
||||
chestplate = false;
|
||||
leggings = false;
|
||||
boots = false;
|
||||
}
|
||||
instance.textureQueue = {}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Armor
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
self.armorSlotItems = self.shouldShowArmor and {player:getItem(6), player:getItem(5), player:getItem(4), player:getItem(3)} or {world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")), world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air"))}
|
||||
if self.armorSlotItems[1].id ~= self.armorSlotItemsPrev[1].id then
|
||||
self:setHelmet(self.armorSlotItems[1])
|
||||
end
|
||||
if self.armorSlotItems[2].id ~= self.armorSlotItemsPrev[2].id then
|
||||
self:setChestplate(self.armorSlotItems[2])
|
||||
end
|
||||
if self.armorSlotItems[3].id ~= self.armorSlotItemsPrev[3].id then
|
||||
self:setLeggings(self.armorSlotItems[3])
|
||||
end
|
||||
if self.armorSlotItems[4].id ~= self.armorSlotItemsPrev[4].id then
|
||||
self:setBoots(self.armorSlotItems[4])
|
||||
end
|
||||
|
||||
for index, armorSlotItem in ipairs(self.armorSlotItems) do
|
||||
local glint = armorSlotItem:hasGlint()
|
||||
if glint ~= self.armorSlotItemsPrev[index]:hasGlint() then
|
||||
--エンチャント変更
|
||||
local renderType = glint and "GLINT" or "NONE"
|
||||
if index == 2 then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom}) do
|
||||
armorPart:setSecondaryRenderType(renderType)
|
||||
end
|
||||
elseif index == 3 then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom}) do
|
||||
armorPart:setSecondaryRenderType(renderType)
|
||||
end
|
||||
else
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom}) do
|
||||
armorPart:setSecondaryRenderType(renderType)
|
||||
end
|
||||
end
|
||||
end
|
||||
local armorColor = self.getArmorColor(armorSlotItem)
|
||||
if armorColor ~= self.getArmorColor(self.armorSlotItemsPrev[index]) then
|
||||
--色変更
|
||||
local colorVector = vectors.intToRGB(armorColor)
|
||||
if index == 2 then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplate, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom.RightChestplateBottom, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplate, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom.LeftChestplateBottom}) do
|
||||
armorPart:setColor(colorVector)
|
||||
end
|
||||
elseif index == 3 then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings.RightLeggings, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom.RightLeggingsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings.LeftLeggings, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottom}) do
|
||||
armorPart:setColor(colorVector)
|
||||
end
|
||||
else
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots.RightBoots, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom.RightBootsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots.LeftBoots, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom.LeftBootsBottom}) do
|
||||
armorPart:setColor(colorVector)
|
||||
end
|
||||
end
|
||||
end
|
||||
local trim = self.armorSlotItems[index].tag.Trim
|
||||
if not self.compareTrims(trim, self.armorSlotItemsPrev[index].tag.Trim) then
|
||||
--トリム変更
|
||||
if index == 2 then
|
||||
local trimTexture = self:getTrimTexture(trim, self.armorSlotItems[2].id)
|
||||
if trimTexture then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplateTrim, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom.RightChestplateBottomTrim, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplateTrim, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom.LeftChestplateBottomTrim}) do
|
||||
armorPart:setVisible(true)
|
||||
armorPart:setPrimaryTexture("CUSTOM", trimTexture)
|
||||
end
|
||||
else
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplateTrim, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom.RightChestplateBottomTrim, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplateTrim, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom.LeftChestplateBottomTrim}) do
|
||||
armorPart:setVisible(false)
|
||||
end
|
||||
end
|
||||
elseif index == 3 then
|
||||
local trimTexture = self:getTrimTexture(trim, self.armorSlotItems[3].id)
|
||||
if trimTexture then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings.RightLeggingsTrim, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom.RightLeggingsBottomTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings.LeftLeggingsTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottomTrim}) do
|
||||
armorPart:setVisible(true)
|
||||
armorPart:setPrimaryTexture("CUSTOM", trimTexture)
|
||||
end
|
||||
else
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings.RightLeggingsTrim, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom.RightLeggingsBottomTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings.LeftLeggingsTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottomTrim}) do
|
||||
armorPart:setVisible(false)
|
||||
end
|
||||
end
|
||||
else
|
||||
local trimTexture = self:getTrimTexture(trim, self.armorSlotItems[4].id)
|
||||
if trimTexture then
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots.RightBootsTrim, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom.RightBootsBottomTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots.LeftBootsTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom.LeftBootsBottomTrim}) do
|
||||
armorPart:setVisible(true)
|
||||
armorPart:setPrimaryTexture("CUSTOM", trimTexture)
|
||||
end
|
||||
else
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots.RightBootsTrim, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom.RightBootsBottomTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots.LeftBootsTrim, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom.LeftBootsBottomTrim}) do
|
||||
armorPart:setVisible(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--テクスチャの作成処理
|
||||
if #self.textureQueue > 0 then
|
||||
local instructionsAvailable = avatar:getMaxTickCount() - 3000 --このTICKで使用出来る残りの命令数
|
||||
while #self.textureQueue > 0 and instructionsAvailable > 0 do
|
||||
local dimension = self.textureQueue[1].texture:getDimensions()
|
||||
for y = math.floor(self.textureQueue[1].iterationCount / dimension.x), dimension.y - 1 do
|
||||
for x = self.textureQueue[1].iterationCount % dimension.x, dimension.x - 1 do
|
||||
local pixel = self.textureQueue[1].texture:getPixel(x, y)
|
||||
if pixel.w == 1 then
|
||||
self.textureQueue[1].texture:setPixel(x, y, self.textureQueue[1].palette:getPixel(7 - math.floor(pixel.x * 8), 0))
|
||||
end
|
||||
self.textureQueue[1].iterationCount = self.textureQueue[1].iterationCount + 1
|
||||
instructionsAvailable = instructionsAvailable - 45
|
||||
if instructionsAvailable <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
if instructionsAvailable <= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
self.textureQueue[1].texture:update()
|
||||
if self.textureQueue[1].iterationCount == dimension.x * dimension.y then
|
||||
table.remove(self.textureQueue, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.armorSlotItemsPrev = self.armorSlotItems
|
||||
end)
|
||||
|
||||
for _, vanillaModel in ipairs({vanilla_model.HELMET, vanilla_model.CHESTPLATE, vanilla_model.LEGGINGS}) do
|
||||
vanillaModel:setVisible(false)
|
||||
end
|
||||
for _, overlayPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplateOverlay, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplateOverlay, models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplateOverlay, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom.RightChestplateBottomOverlay, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplateOverlay, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom.LeftChestplateBottomOverlay, models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots.RightBootsOverlay, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom.RightBootsBottomOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots.LeftBootsOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom.LeftBootsBottomOverlay}) do
|
||||
overlayPart:setPrimaryTexture("RESOURCE", "minecraft:textures/models/armor/leather_layer_1_overlay.png")
|
||||
end
|
||||
for _, overlayPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings.RightLeggingsOverlay, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom.RightLeggingsBottomOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings.LeftLeggingsOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottomOverlay}) do
|
||||
overlayPart:setPrimaryTexture("RESOURCE", "minecraft:textures/models/armor/leather_layer_2_overlay.png")
|
||||
end
|
||||
end;
|
||||
|
||||
---防具の色を取得する。
|
||||
---@param armorItem ItemStack 調べるアイテムのオブジェクト
|
||||
---@return number color 防具モデルに設定すべき色
|
||||
getArmorColor = function (armorItem)
|
||||
if armorItem.id:find("^minecraft:leather_") then
|
||||
if armorItem.tag then
|
||||
if armorItem.tag.display then
|
||||
return armorItem.tag.display.color and armorItem.tag.display.color or 10511680
|
||||
else
|
||||
return 10511680
|
||||
end
|
||||
else
|
||||
return 10511680
|
||||
end
|
||||
else
|
||||
return 16777215
|
||||
end
|
||||
end;
|
||||
|
||||
---防具装飾が同じものか比較する。
|
||||
---@param trim1? Armor.TrimData 比較する防具装飾のテーブル1
|
||||
---@param trim2? Armor.TrimData 比較する防具装飾のテーブル2
|
||||
---@return boolean isTrimSame 2つの防具装飾が同じものかどうか
|
||||
compareTrims = function (trim1, trim2)
|
||||
if type(trim1) == type(trim2) then
|
||||
if trim1 then
|
||||
if trim1.pattern ~= trim2.pattern then
|
||||
return false
|
||||
elseif trim1.material ~= trim2.material then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end;
|
||||
|
||||
---テクスチャの処理のキューにデータを挿入する。
|
||||
---@param self Armor
|
||||
---@param texture Texture 処理を行うテクスチャ
|
||||
---@param paletteName string 使用するパレットの名前
|
||||
addTextureQueue = function (self, texture, paletteName)
|
||||
if textures["trim_palette_"..paletteName] == nil then
|
||||
textures:fromVanilla("trim_palette_"..paletteName, "minecraft:textures/trims/color_palettes/"..paletteName..".png")
|
||||
end
|
||||
table.insert(self.textureQueue, 1, {
|
||||
texture = texture,
|
||||
palette = textures["trim_palette_"..paletteName],
|
||||
iterationCount = 0
|
||||
})
|
||||
end;
|
||||
|
||||
---バニラパーツの防具装飾のテクスチャを取得する。テクスチャの処理は次のチック以降行われる。
|
||||
---@param self Armor
|
||||
---@param trimData? Armor.TrimData 防具装飾のデータ
|
||||
---@param armorId string 防具アイテムのID。
|
||||
---@return Texture|nil trimTexture 色を付けた防具装飾のテクスチャ。防具や防具装飾が非バニラの場合はnilを返す。
|
||||
getTrimTexture = function (self, trimData, armorId)
|
||||
if trimData and trimData.pattern:find("^minecraft:.+$") and trimData.material:find("^minecraft:.+$") and armorId:find("^minecraft:.+_.+$") then
|
||||
local normalizedPatternName = trimData.pattern:match("^minecraft:(%a+)$")
|
||||
local normalizedArmorMaterialName = armorId:match("^minecraft:(%a+)_.+$")
|
||||
normalizedArmorMaterialName = normalizedArmorMaterialName == "golden" and "gold" or normalizedArmorMaterialName
|
||||
local normalizedMaterialName = trimData.material:match("^minecraft:(%a+)$")
|
||||
normalizedMaterialName = normalizedMaterialName..(normalizedArmorMaterialName == normalizedMaterialName and "_darker" or "")
|
||||
local isLeggings = armorId:find("^minecraft:.+_leggings$")
|
||||
local textureName = "trim_"..normalizedPatternName.."_"..normalizedMaterialName..(isLeggings and "_leggings" or "")
|
||||
if textures[textureName] then
|
||||
return textures[textureName]
|
||||
else
|
||||
local texture = textures:fromVanilla(textureName, "minecraft:textures/trims/models/armor/"..normalizedPatternName..(armorId:find("^minecraft:.+_leggings$") ~= nil and "_leggings" or "")..".png")
|
||||
self:addTextureQueue(texture, normalizedMaterialName)
|
||||
return texture
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
---ヘルメットを更新する。
|
||||
---@param self Armor
|
||||
---@param helmetItem ItemStack ヘルメットのスロットに入っているアイテム
|
||||
setHelmet = function (self, helmetItem)
|
||||
local helmetFound = helmetItem.id ~= "minecraft:air"
|
||||
vanilla_model.HELMET:setVisible(helmetFound)
|
||||
self.isArmorVisible.helmet = helmetFound
|
||||
if self.parent.characterData.costume.callbacks ~= nil and self.parent.characterData.costume.callbacks.onArmorChange ~= nil then
|
||||
self.parent.characterData.costume.callbacks.onArmorChange(self.parent.characterData, "HELMET", self.isArmorVisible.helmet)
|
||||
end
|
||||
end;
|
||||
|
||||
---チェストプレートを更新する。
|
||||
---@param self Armor
|
||||
---@param chestplateItem ItemStack チェストプレートスロットに入っているアイテム
|
||||
setChestplate = function (self, chestplateItem)
|
||||
local chestplateFound = chestplateItem.id:find("^minecraft:.+_chestplate$") ~= nil
|
||||
vanilla_model.CHESTPLATE:setVisible(chestplateFound)
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB}) do
|
||||
armorPart:setVisible(chestplateFound)
|
||||
end
|
||||
self.isArmorVisible.chestplate = chestplateFound
|
||||
if self.isArmorVisible.chestplate then
|
||||
local material = chestplateItem.id:match("^minecraft:(%a+)_chestplate$")
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplate, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom.RightChestplateBottom, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplate, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom.LeftChestplateBottom}) do
|
||||
armorPart:setPrimaryTexture("RESOURCE", "minecraft:textures/models/armor/"..(material == "golden" and "gold" or material).."_layer_1.png")
|
||||
end
|
||||
end
|
||||
local overlayVisible = chestplateItem.id == "minecraft:leather_chestplate"
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.UpperBody.Arms.RightArm.ArmorRA.RightChestplate.RightChestplateOverlay, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB.RightChestplateBottom.RightChestplateBottomOverlay, models.models.main.Avatar.UpperBody.Arms.LeftArm.ArmorLA.LeftChestplate.LeftChestplateOverlay, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB.LeftChestplateBottom.LeftChestplateBottomOverlay}) do
|
||||
armorPart:setVisible(overlayVisible)
|
||||
end
|
||||
if self.parent.characterData.costume.callbacks ~= nil and self.parent.characterData.costume.callbacks.onArmorChange ~= nil then
|
||||
self.parent.characterData.costume.callbacks.onArmorChange(self.parent.characterData, "CHEST_PLATE", self.isArmorVisible.chestplate)
|
||||
end
|
||||
end;
|
||||
|
||||
---レギンスを更新する。
|
||||
---@param self Armor
|
||||
---@param leggingsItem ItemStack レギンススロットに入っているアイテム
|
||||
setLeggings = function (self, leggingsItem)
|
||||
local leggingsFound = leggingsItem.id:find("^minecraft:.+_leggings$") ~= nil
|
||||
vanilla_model.LEGGINGS:setVisible(leggingsFound)
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom}) do
|
||||
armorPart:setVisible(leggingsFound)
|
||||
end
|
||||
self.isArmorVisible.leggings = leggingsFound
|
||||
if self.isArmorVisible.leggings then
|
||||
local material = leggingsItem.id:match("^minecraft:(%a+)_leggings$")
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings.RightLeggings, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom.RightLeggingsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings.LeftLeggings, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottom}) do
|
||||
armorPart:setPrimaryTexture("RESOURCE", "minecraft:textures/models/armor/"..(material == "golden" and "gold" or material).."_layer_2.png")
|
||||
end
|
||||
end
|
||||
local overlayVisible = leggingsItem.id == "minecraft:leather_leggings"
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightLeggings.RightLeggingsOverlay, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightLeggingsBottom.RightLeggingsBottomOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftLeggings.LeftLeggingsOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftLeggingsBottom.LeftLeggingsBottomOverlay}) do
|
||||
armorPart:setVisible(overlayVisible)
|
||||
end
|
||||
if self.parent.characterData.costume.callbacks ~= nil and self.parent.characterData.costume.callbacks.onArmorChange ~= nil then
|
||||
self.parent.characterData.costume.callbacks.onArmorChange(self.parent.characterData, "LEGGINGS", self.isArmorVisible.leggings)
|
||||
end
|
||||
end;
|
||||
|
||||
---ブーツを更新する。
|
||||
---@param self Armor
|
||||
---@param bootsItem ItemStack ブーツスロットに入っているアイテム
|
||||
setBoots = function (self, bootsItem)
|
||||
local bootsFound = bootsItem.id:find("^minecraft:.+_boots$") ~= nil
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom}) do
|
||||
armorPart:setVisible(bootsFound)
|
||||
end
|
||||
self.isArmorVisible.boots = bootsFound
|
||||
if self.isArmorVisible.boots then
|
||||
local material = bootsItem.id:match("^minecraft:(%a+)_boots$")
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots.RightBoots, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom.RightBootsBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots.LeftBoots, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom.LeftBootsBottom}) do
|
||||
armorPart:setPrimaryTexture("RESOURCE", "minecraft:textures/models/armor/"..(material == "golden" and "gold" or material).."_layer_1.png")
|
||||
end
|
||||
end
|
||||
local overlayVisible = bootsItem.id == "minecraft:leather_boots"
|
||||
for _, armorPart in ipairs({models.models.main.Avatar.LowerBody.Legs.RightLeg.ArmorRL.RightBoots.RightBootsOverlay, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB.RightBootsBottom.RightBootsBottomOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.ArmorLL.LeftBoots.LeftBootsOverlay, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB.LeftBootsBottom.LeftBootsBottomOverlay}) do
|
||||
armorPart:setVisible(overlayVisible)
|
||||
end
|
||||
if self.parent.characterData.costume.callbacks ~= nil and self.parent.characterData.costume.callbacks.onArmorChange ~= nil then
|
||||
self.parent.characterData.costume.callbacks.onArmorChange(self.parent.characterData, "BOOTS", self.isArmorVisible.boots)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
---@class (exact) Arms : AvatarModule アバターの腕を制御するクラス
|
||||
---@field public armState Arms.ArmStateSet 腕の状態:0. バニラ状態, 1. 銃を構えている際の、銃を構えている方の腕, 2. 銃を構えている際の、銃を構えていない方の腕, 3. クロスボウ装填中
|
||||
---@field package armStatePrev Arms.ArmStateSet 前ティックの腕の状態
|
||||
---@field package swingCount integer 腕をプラプラさせるカウンター
|
||||
---@field package isSwingCountProcessed boolean 腕プラプラカウンターを処理したかどうか
|
||||
---@field public processArmSwingCount fun(self: Arms) 腕プラプラカウンターを処理する
|
||||
---@field public setArmState fun(self: Arms, right?: integer, left?: integer) 腕の状態を設定する
|
||||
|
||||
---@class (exact) Arms.ArmStateSet 腕の状態を示すデータセット
|
||||
---@field public right integer 右腕の状態
|
||||
---@field public left integer 左腕の状態
|
||||
|
||||
Arms = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Arms
|
||||
new = function (parent)
|
||||
---@type Arms
|
||||
local instance = Avatar.instantiate(Arms, AvatarModule, parent)
|
||||
|
||||
instance.armState = {
|
||||
right = 0;
|
||||
left = 0;
|
||||
}
|
||||
instance.armStatePrev = {
|
||||
right = 0;
|
||||
left = 0;
|
||||
}
|
||||
instance.swingCount = 0
|
||||
instance.isSwingCountProcessed = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Arms
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
self.isSwingCountProcessed = false
|
||||
end)
|
||||
end;
|
||||
|
||||
---腕プラプラカウンターを処理する。
|
||||
---@param self Arms
|
||||
processArmSwingCount = function (self)
|
||||
if not client:isPaused() and not self.isSwingCountProcessed then
|
||||
self.swingCount = self.swingCount + 1
|
||||
self.swingCount = self.swingCount == 100 and 0 or self.swingCount
|
||||
self.isSwingCountProcessed = true
|
||||
end
|
||||
end;
|
||||
|
||||
---腕の状態を設定する。
|
||||
---@param self Arms
|
||||
---@param right? integer 右腕の状態
|
||||
---@param left? integer 左腕の状態
|
||||
setArmState = function (self, right, left)
|
||||
if right ~= nil then
|
||||
self.armState.right = right
|
||||
end
|
||||
if left ~= nil then
|
||||
self.armState.left = left
|
||||
end
|
||||
if (self.armState.right == 1 or self.armState.left == 1) and player:getActiveItem().id == "minecraft:crossbow" then
|
||||
self:setArmState(3, 3)
|
||||
return
|
||||
end
|
||||
if self.parent.characterData.arms.callbacks ~= nil and self.parent.characterData.arms.callbacks.onArmStateChanged ~= nil then
|
||||
local result = self.parent.characterData.arms.callbacks.onArmStateChanged(self.parent.characterData, self.armState.right, self.armState.left)
|
||||
if result ~= nil then
|
||||
if result.right ~= nil then
|
||||
self.armState.right = result.right
|
||||
end
|
||||
if result.left ~= nil then
|
||||
self.armState.left = result.left
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--右腕の操作
|
||||
if self.armState.right ~= self.armStatePrev.right then
|
||||
--腕の状態をリセット
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setRot()
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setParentType("RightArm")
|
||||
events.TICK:remove("right_arm_tick")
|
||||
events.RENDER:remove("right_arm_render")
|
||||
if self.armState.right == 1 then
|
||||
--銃を構えている際の、銃を構えている方の腕
|
||||
events.TICK:register(function ()
|
||||
if self.armState.right == 1 then
|
||||
self:processArmSwingCount()
|
||||
if player:isSwingingArm() and not player:isLeftHanded() then
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setParentType("RightArm")
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setParentType("Body")
|
||||
end
|
||||
if player:getActiveItem().id == "minecraft:crossbow" then
|
||||
self:setArmState(3, 3)
|
||||
end
|
||||
end
|
||||
end, "right_arm_tick")
|
||||
events.RENDER:register(function (delta)
|
||||
local headRot = vanilla_model.HEAD:getOriginRot()
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setRot(player:isSwingingArm() and not player:isLeftHanded() and vectors.vec3() or vectors.vec3(headRot.x + math.sin((self.swingCount + delta) / 100 * math.pi * 2) * 2.5 + 90 + (player:isCrouching() and 30 or 0), headRot.y, 0))
|
||||
end, "right_arm_render")
|
||||
elseif self.armState.right == 2 then
|
||||
--銃を構えている際の、銃を構えていない方の腕
|
||||
events.TICK:register(function ()
|
||||
self:processArmSwingCount()
|
||||
end, "right_arm_tick")
|
||||
events.RENDER:register(function (delta, context)
|
||||
local headRot = vanilla_model.HEAD:getOriginRot()
|
||||
local isSwingingArm = player:isSwingingArm() and not player:isLeftHanded()
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setParentType((isSwingingArm or context == "FIRST_PERSON") and "RightArm" or "Body")
|
||||
models.models.main.Avatar.UpperBody.Arms.RightArm:setRot(isSwingingArm and vectors.vec3() or vectors.vec3(headRot.x + math.sin((self.swingCount + delta) / 100 * math.pi * 2) * 2.5 + 90 + (player:isCrouching() and 30 or 0), math.map((headRot.y + 180) % 360 - 180, -50, 50, -21, 78), 0))
|
||||
end, "right_arm_render")
|
||||
elseif self.armState.right == 3 then
|
||||
--クロスボウ装填中
|
||||
events.TICK:register(function ()
|
||||
if player:getActiveItem().id ~= "minecraft:crossbow" and self.armState.right == 3 then
|
||||
if self.parent.gun.currentGunPosition == "RIGHT" then
|
||||
self:setArmState(1, 2)
|
||||
elseif self.parent.gun.currentGunPosition == "LEFT" then
|
||||
self:setArmState(2, 1)
|
||||
end
|
||||
end
|
||||
end, "right_arm_tick")
|
||||
end
|
||||
if self.parent.characterData.arms.callbacks ~= nil and self.parent.characterData.arms.callbacks.onAdditionalRightArmProcess ~= nil then
|
||||
self.parent.characterData.arms.callbacks.onAdditionalRightArmProcess(self.parent.characterData, self.armState.right)
|
||||
end
|
||||
self.armStatePrev.right = self.armState.right
|
||||
end
|
||||
--左腕の操作
|
||||
if self.armState.left ~= self.armStatePrev.left then
|
||||
--腕の状態をリセット
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setRot()
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setParentType("LeftArm")
|
||||
events.TICK:remove("left_arm_tick")
|
||||
events.RENDER:remove("left_arm_render")
|
||||
if self.armState.left == 1 then
|
||||
--銃を構えている際の、銃を構えている方の腕
|
||||
events.TICK:register(function ()
|
||||
if self.armState.left == 1 then
|
||||
self:processArmSwingCount()
|
||||
if player:isSwingingArm() and player:isLeftHanded() then
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setParentType("LeftArm")
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setParentType("Body")
|
||||
end
|
||||
if player:getActiveItem().id == "minecraft:crossbow" then
|
||||
self:setArmState(3, 3)
|
||||
end
|
||||
end
|
||||
end, "left_arm_tick")
|
||||
events.RENDER:register(function (delta)
|
||||
local headRot = vanilla_model.HEAD:getOriginRot()
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setRot(player:isSwingingArm() and player:isLeftHanded() and vectors.vec3() or vectors.vec3(headRot.x + math.sin((self.swingCount + delta) / 100 * math.pi * 2) * -2.5 + 90 + (player:isCrouching() and 30 or 0), headRot.y, 0))
|
||||
end, "left_arm_render")
|
||||
elseif self.armState.left == 2 then
|
||||
--銃を構えている際の、銃を構えていない方の腕
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setParentType("Body")
|
||||
events.TICK:register(function ()
|
||||
self:processArmSwingCount()
|
||||
end, "left_arm_tick")
|
||||
events.RENDER:register(function (delta, context)
|
||||
local headRot = vanilla_model.HEAD:getOriginRot()
|
||||
local isSwingingArm = player:isSwingingArm() and player:isLeftHanded()
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setParentType((isSwingingArm or context == "FIRST_PERSON") and "LeftArm" or "Body")
|
||||
models.models.main.Avatar.UpperBody.Arms.LeftArm:setRot(isSwingingArm and vectors.vec3() or vectors.vec3(headRot.x + math.sin((self.swingCount + delta) / 100 * math.pi * 2) * -2.5 + 90 + (player:isCrouching() and 30 or 0), math.map((headRot.y + 180) % 360 - 180, -50, 50, -78, 21), 0))
|
||||
end, "left_arm_render")
|
||||
elseif self.armState.left == 3 then
|
||||
--クロスボウ装填中
|
||||
events.TICK:register(function ()
|
||||
if player:getActiveItem().id ~= "minecraft:crossbow" and self.armState.left == 3 then
|
||||
if self.parent.gun.currentGunPosition == "RIGHT" then
|
||||
self:setArmState(1, 2)
|
||||
elseif self.parent.gun.currentGunPosition == "LEFT" then
|
||||
self:setArmState(2, 1)
|
||||
end
|
||||
end
|
||||
end, "left_arm_tick")
|
||||
end
|
||||
if self.parent.characterData.arms.callbacks ~= nil and self.parent.characterData.arms.callbacks.onAdditionalLeftArmProcess ~= nil then
|
||||
self.parent.characterData.arms.callbacks.onAdditionalLeftArmProcess(self.parent.characterData, self.armState.left)
|
||||
end
|
||||
self.armStatePrev.left = self.armState.left
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
---@class AvatarModule アバターの動作を構成するモジュールの抽象クラス
|
||||
---@field public parent Avatar アバターのメインクラスへの参照
|
||||
---@field public new fun(parent: Avatar): AvatarModule コンストラクタ
|
||||
---@field public init fun(self: AvatarModule) 初期化関数
|
||||
|
||||
AvatarModule = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return AvatarModule
|
||||
new = function (parent)
|
||||
---@type AvatarModule
|
||||
local instance = Avatar.instantiate(AvatarModule)
|
||||
|
||||
instance.parent = parent
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self AvatarModule
|
||||
init = function (self)
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
---@class (exact) Barrier : AvatarModule バリアの視覚効果を管理するクラス
|
||||
---@field package animationCounts number[] バリアのアニメーションのカウンター
|
||||
---@field public isBarrierVisible boolean バリアが可視化状態かどうか
|
||||
---@field package hadAbsorptionPrev boolean 前ティックの衝撃吸収のハートを持っていたかどうか
|
||||
---@field package colorFactor number バリアの色の係数
|
||||
---@field public enable fun(self: Barrier) バリア機能を有効にする
|
||||
---@field public disable fun(self: Barrier) バリア機能を無効にする
|
||||
|
||||
Barrier = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Barrier
|
||||
new = function (parent)
|
||||
---@type Barrier
|
||||
local instance = Avatar.instantiate(Barrier, AvatarModule, parent)
|
||||
|
||||
instance.animationCounts = {}
|
||||
instance.isBarrierVisible = false
|
||||
instance.hadAbsorptionPrev = false
|
||||
instance.colorFactor = client:hasShaderPack() and 0.5 or 1
|
||||
|
||||
for i = 1, 32 do
|
||||
instance.animationCounts[i] = math.random(0, 39)
|
||||
end
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Barrier
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
models.models.main.Avatar.barrier:setLight(15)
|
||||
|
||||
events.TICK:register(function ()
|
||||
local hasAbsorption = player:getAbsorptionAmount() > 0 and player:getHealth() > 0
|
||||
if hasAbsorption and not self.hadAbsorptionPrev then
|
||||
self:enable()
|
||||
elseif not hasAbsorption and self.hadAbsorptionPrev then
|
||||
self:disable()
|
||||
end
|
||||
self.hadAbsorptionPrev = hasAbsorption
|
||||
end)
|
||||
end;
|
||||
|
||||
---バリア機能を有効にする。
|
||||
---@param self Barrier
|
||||
enable = function (self)
|
||||
for i = 1, 32 do
|
||||
self.animationCounts[i] = math.random(0, 39)
|
||||
end
|
||||
|
||||
events.TICK:register(function ()
|
||||
for i = 1, 32 do
|
||||
self.animationCounts[i] = self.animationCounts[i] + 1
|
||||
if self.animationCounts[i] == 40 then
|
||||
self.animationCounts[i] = 0
|
||||
end
|
||||
end
|
||||
self.colorFactor = client:hasShaderPack() and 0.5 or 1
|
||||
end, "barrier_tick")
|
||||
|
||||
events.RENDER:register(function (delta, context)
|
||||
if context == "FIRST_PERSON" or context == "RENDER" then
|
||||
models.models.main.Avatar.barrier:setVisible(true)
|
||||
for i = 1, 32 do
|
||||
local opacity = math.abs(-0.025 * (self.animationCounts[i] + delta) + 0.5) + 0.5
|
||||
models.models.main.Avatar.barrier.Barrier["Barrier"..i]:setOpacity(opacity)
|
||||
models.models.main.Avatar.barrier.Barrier["Barrier"..i]:setColor(opacity * self.colorFactor, opacity * self.colorFactor, 1)
|
||||
end
|
||||
else
|
||||
models.models.main.Avatar.barrier:setVisible(false)
|
||||
end
|
||||
end, "barrier_render")
|
||||
|
||||
self.isBarrierVisible = true
|
||||
end;
|
||||
|
||||
---バリア機能を無効にする。
|
||||
---@param self Barrier
|
||||
disable = function (self)
|
||||
models.models.main.Avatar.barrier:setVisible(false)
|
||||
events.TICK:remove("barrier_tick")
|
||||
events.RENDER:remove("barrier_render")
|
||||
self.isBarrierVisible = false
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
---@alias Bubble.BubbleType
|
||||
---| "GOOD" # 👍
|
||||
---| "HEART" # 💗
|
||||
---| "NOTE" # 🎵
|
||||
---| "QUESTION" # ❓
|
||||
---| "SWEAT" # 💦
|
||||
---| "RELOAD" # 弾薬をリロードする絵文字
|
||||
---| "DOTS" # …
|
||||
---| "V" # ✌
|
||||
|
||||
---@class (exact) Bubble : AvatarModule 吹き出しエモートを管理するクラス
|
||||
---@field public bubbleCount integer 吹き出しの表示時間を測るカウンター
|
||||
---@field public emoji Bubble.BubbleType 吹き出しの絵文字
|
||||
---@field package duration integer 吹き出しを表示する時間。-1は時間無制限を示す。
|
||||
---@field package isAutoBubble boolean 吹き出しエモートが自動で出たものかどうか
|
||||
---@field package shouldShowInHud boolean 一人称用にHUDに吹き出しを表示するかどうか
|
||||
---@field package emojiAnimationCount number 絵文字のアニメーションのタイミングを測るカウンター
|
||||
---@field package isForcedStop boolean 吹き出しエモートが強制停止させられたかどうか
|
||||
---@field public isChatOpened boolean チャットを開けているかどうか
|
||||
---@field package isChatOpenedPrev boolean 前ティックにチャットを開けていたかどうか
|
||||
---@field public play fun(self: Bubble, type: Bubble.BubbleType, duration: integer, offsetPos: Vector2, offsetRot: number, shouldShowInHud: boolean) 吹き出しエモートを再生する
|
||||
---@field public stop fun(self: Bubble) 吹き出しエモートを停止する
|
||||
|
||||
Bubble = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Bubble
|
||||
new = function (parent)
|
||||
---@type Bubble
|
||||
local instance = Avatar.instantiate(Bubble, AvatarModule, parent)
|
||||
|
||||
instance.bubbleCount = 0
|
||||
instance.emoji = "GOOD"
|
||||
instance.duration = 0
|
||||
instance.isAutoBubble = false
|
||||
instance.shouldShowInHud = false
|
||||
instance.emojiAnimationCount = 0
|
||||
instance.isForcedStop = false
|
||||
instance.isChatOpened = false
|
||||
instance.isChatOpenedPrev = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Bubble
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
models.models.bubble:addChild(models:newPart("Gui", "Gui"))
|
||||
models.models.bubble.Gui:addChild(models.models.bubble.Camera.AvatarBubble:copy("FirstPersonBubble"))
|
||||
models.models.bubble.Gui.FirstPersonBubble:setVisible(false)
|
||||
for _, modelPart in ipairs({models.models.bubble.Camera.AvatarBubble, models.models.bubble.Gui.FirstPersonBubble}) do
|
||||
modelPart:setScale(0, 0, 0)
|
||||
end
|
||||
|
||||
--エモートガイド
|
||||
if host:isHost() then
|
||||
self.parent.keyManager:register("bubble_1", self.parent.config:loadConfig("PRIVATE", "keybind.bubble_1", "key.keyboard.j")):onPress(function ()
|
||||
if self.parent.exSkill.animationCount == -1 and (self.bubbleCount == 0 or self.isAutoBubble) then
|
||||
pings.showBubbleEmote("GOOD")
|
||||
end
|
||||
end)
|
||||
self.parent.keyManager:register("bubble_2", self.parent.config:loadConfig("PRIVATE", "keybind.bubble_2", "key.keyboard.k")):onPress(function ()
|
||||
if self.parent.exSkill.animationCount == -1 and (self.bubbleCount == 0 or self.isAutoBubble) then
|
||||
pings.showBubbleEmote("HEART")
|
||||
end
|
||||
end)
|
||||
self.parent.keyManager:register("bubble_3", self.parent.config:loadConfig("PRIVATE", "keybind.bubble_3", "key.keyboard.n")):onPress(function ()
|
||||
if self.parent.exSkill.animationCount == -1 and (self.bubbleCount == 0 or self.isAutoBubble) then
|
||||
pings.showBubbleEmote("NOTE")
|
||||
end
|
||||
end)
|
||||
self.parent.keyManager:register("bubble_4", self.parent.config:loadConfig("PRIVATE", "keybind.bubble_4", "key.keyboard.m")):onPress(function ()
|
||||
if self.parent.exSkill.animationCount == -1 and (self.bubbleCount == 0 or self.isAutoBubble) then
|
||||
pings.showBubbleEmote("QUESTION")
|
||||
end
|
||||
end)
|
||||
self.parent.keyManager:register("bubble_5", self.parent.config:loadConfig("PRIVATE", "keybind.bubble_5", "key.keyboard.comma")):onPress(function ()
|
||||
if self.parent.exSkill.animationCount == -1 and (self.bubbleCount == 0 or self.isAutoBubble) then
|
||||
pings.showBubbleEmote("SWEAT")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
events.TICK:register(function ()
|
||||
if host:isHost() then
|
||||
local isChatOpened = host:isChatOpen()
|
||||
if isChatOpened ~= self.isChatOpened then
|
||||
pings.setChatOpen(isChatOpened)
|
||||
end
|
||||
self.isChatOpenedPrev = isChatOpened
|
||||
end
|
||||
|
||||
if player:getActiveItem().id == "minecraft:crossbow" then
|
||||
if self.bubbleCount == 0 or (self.isAutoBubble and self.emoji ~= "RELOAD") then
|
||||
self:play("RELOAD", -1, vectors.vec2(), 0, false)
|
||||
self.isAutoBubble = true
|
||||
end
|
||||
elseif self.isChatOpened and self.parent.exSkill.transitionCount == 0 then
|
||||
if self.bubbleCount == 0 or (self.isAutoBubble and self.emoji ~= "DOTS") then
|
||||
self:play("DOTS", -1, vectors.vec2(), 0, false)
|
||||
self.isAutoBubble = true
|
||||
end
|
||||
elseif self.isAutoBubble then
|
||||
self:stop()
|
||||
self.isAutoBubble = false
|
||||
end
|
||||
end)
|
||||
end;
|
||||
|
||||
---吹き出しエモートを再生する。
|
||||
---@param self Bubble
|
||||
---@param type Bubble.BubbleType 再生する絵文字の種類
|
||||
---@param duration integer 吹き出しを表示している時間。-1にすると停止するまでずっと表示する。
|
||||
---@param offsetPos Vector2 吹き出しの位置のオフセット値
|
||||
---@param offsetRot number アバター周回上の、吹き出しが表示される位置のオフセット値
|
||||
---@param shouldShowInHud boolean 一人称用にHUDに吹き出しを表示するかどうか
|
||||
play = function (self, type, duration, offsetPos, offsetRot, shouldShowInHud)
|
||||
self.emoji = type
|
||||
self.duration = duration
|
||||
self.shouldShowInHud = shouldShowInHud
|
||||
self.bubbleCount = 1
|
||||
self.emojiAnimationCount = 0
|
||||
models.models.bubble.Camera.AvatarBubble.Emoji:setPrimaryTexture("CUSTOM", textures["textures.emojis."..self.emoji:lower()])
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets:setVisible(self.emoji == "RELOAD")
|
||||
models.models.bubble.Camera.AvatarBubble.Dots:setVisible(self.emoji == "DOTS")
|
||||
if self.shouldShowInHud then
|
||||
models.models.bubble.Gui.FirstPersonBubble.Emoji:setPrimaryTexture("CUSTOM", textures["textures.emojis."..self.emoji:lower()])
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.item.pickup"), self.parent.modelUtils.getModelWorldPos(models.models.main.Avatar))
|
||||
end
|
||||
|
||||
if events.TICK:getRegisteredCount("bubble_tick") == 0 then
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
models.models.bubble.Gui.FirstPersonBubble:setVisible(self.shouldShowInHud and renderer:isFirstPerson())
|
||||
self.bubbleCount = self.bubbleCount + 1
|
||||
if self.bubbleCount == 0 then
|
||||
for _, modelPart in ipairs({models.models.bubble.Camera.AvatarBubble, models.models.bubble.Gui.FirstPersonBubble, models.models.bubble.Camera.AvatarBubble.Bullets, models.models.bubble.Gui.FirstPersonBubble.Bullets}) do
|
||||
modelPart:setVisible(false)
|
||||
end
|
||||
events.TICK:remove("bubble_tick")
|
||||
events.RENDER:remove("bubble_render")
|
||||
if self.parent.characterData.bubble.callbacks ~= nil and self.parent.characterData.bubble.callbacks.onStop ~= nil then
|
||||
self.parent.characterData.bubble.callbacks.onStop(self.parent.characterData, type, self.isForcedStop)
|
||||
end
|
||||
elseif self.duration >= 0 and self.bubbleCount == self.duration + 2 then
|
||||
self:stop()
|
||||
end
|
||||
if self.emoji == "RELOAD" or self.emoji == "DOTS" then
|
||||
self.emojiAnimationCount = self.emojiAnimationCount + 1
|
||||
self.emojiAnimationCount = self.emojiAnimationCount == 25 and 0 or self.emojiAnimationCount
|
||||
if self.emoji == "DOTS" then
|
||||
for i = 1, 3 do
|
||||
models.models.bubble.Camera.AvatarBubble.Dots["Dot"..i]:setVisible(self.emojiAnimationCount >= 6 * i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "bubble_tick")
|
||||
end
|
||||
|
||||
if events.RENDER:getRegisteredCount("bubble_render") == 0 then
|
||||
events.RENDER:register(function (delta, context)
|
||||
models.models.bubble.Camera.AvatarBubble:setVisible(context ~= "OTHER")
|
||||
if not client:isPaused() then
|
||||
local bubbleScale = math.min(math.abs(0.5 * (self.bubbleCount + delta)), 1)
|
||||
models.models.bubble.Camera.AvatarBubble:setScale(vectors.vec3(1, 1, 1):scale(bubbleScale))
|
||||
local playerPos = self.parent.modelUtils.getModelWorldPos(models.models.main.Avatar)
|
||||
local avatarBubblePos = context == "PAPERDOLL" and vectors.vec3(0, 32, 0) or vectors.rotateAroundAxis(player:getBodyYaw(delta) + 180, playerPos:copy():sub(player:getPos(delta)):scale(17.067):add(0, 32 + offsetPos.y, 0), 0, 1, 0)
|
||||
if not renderer:isFirstPerson() then
|
||||
local cameraPos = client:getCameraPos()
|
||||
avatarBubblePos:add(vectors.rotateAroundAxis(math.deg(math.atan2(cameraPos.z - playerPos.z, cameraPos.x - playerPos.x) - math.pi / 2) % 360 - (player:getBodyYaw(delta) + offsetRot) % 360, 12 + offsetPos.x, 0, 0, 0, -1, 0))
|
||||
else
|
||||
avatarBubblePos:add(12 + offsetPos.x, 0, 0)
|
||||
end
|
||||
models.models.bubble.Camera:setOffsetPivot(avatarBubblePos)
|
||||
models.models.bubble.Camera.AvatarBubble:setPos(avatarBubblePos)
|
||||
if host:isHost() and self.shouldShowInHud then
|
||||
local windowSize = client:getScaledWindowSize()
|
||||
models.models.bubble.Gui.FirstPersonBubble:setPos(-windowSize.x + 10, -windowSize.y + (action_wheel:isEnabled() and 125 or 10), 0)
|
||||
models.models.bubble.Gui.FirstPersonBubble:setScale(vectors.vec3(1, 1, 1):scale(bubbleScale * 4))
|
||||
end
|
||||
if self.emoji == "RELOAD" then
|
||||
local bullet1Counter = math.clamp((self.emojiAnimationCount + delta) * 0.2 - 1, 0, 1)
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets.Bullet1:setPos(0, 1 - bullet1Counter, 0)
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets.Bullet1:setOpacity(bullet1Counter)
|
||||
local bullet2Counter = math.clamp((self.emojiAnimationCount + delta) * 0.2 - 2, 0, 1)
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets.Bullet2:setPos(0, 1 - bullet2Counter, 0)
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets.Bullet2:setOpacity(bullet2Counter)
|
||||
local bullet3Counter = math.clamp((self.emojiAnimationCount + delta) * 0.2 - 3, 0, 1)
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets.Bullet3:setPos(0, 1 - bullet3Counter, 0)
|
||||
models.models.bubble.Camera.AvatarBubble.Bullets.Bullet3:setOpacity(bullet3Counter)
|
||||
end
|
||||
end
|
||||
end, "bubble_render")
|
||||
end
|
||||
if self.parent.characterData.bubble.callbacks ~= nil and self.parent.characterData.bubble.callbacks.onPlay ~= nil then
|
||||
self.parent.characterData.bubble.callbacks.onPlay(self.parent.characterData, type, duration, shouldShowInHud)
|
||||
end
|
||||
end;
|
||||
|
||||
---吹き出しエモートを停止する。
|
||||
---@param self Bubble
|
||||
stop = function (self)
|
||||
if self.bubbleCount > 0 then
|
||||
self.isForcedStop = self.duration == -1 or self.bubbleCount < self.duration + 2
|
||||
self.bubbleCount = -2
|
||||
end
|
||||
end;
|
||||
}
|
||||
|
||||
---吹き出しエモートを表示する。
|
||||
---@param type Bubble.BubbleType 表示する絵文字の種類
|
||||
function pings.showBubbleEmote(type)
|
||||
AvatarInstance.bubble:play(type, 50, vectors.vec2(), 0, true)
|
||||
AvatarInstance.bubble.isAutoBubble = false
|
||||
end
|
||||
|
||||
---Bubbleのチャットを開けているフラグを更新する。
|
||||
---@param value boolean 新しい値
|
||||
function pings.setChatOpen(value)
|
||||
AvatarInstance.bubble.isChatOpened = value
|
||||
end
|
|
@ -0,0 +1,99 @@
|
|||
---@class (exact) CameraManager : AvatarModule カメラ制御全般を管理するクラス
|
||||
---@field package COLLISION_DENIAL_DISABLED boolean カメラの当たり判定打ち消し機能を無効にする。撮影用。
|
||||
---@field package thirdPersonCameraDistance number 三人称視点でのカメラと回転軸の距離
|
||||
---@field package isCameraCollisionDenialEnabled boolean 三人称視点でのカメラの当たり判定打ち消し機能が有効かどうか
|
||||
---@field public setCameraManagerRender fun(self: CameraManager, enabled: boolean) CameraManagerのレンダー関数を設定する
|
||||
---@field public setCameraPivot fun(newPivot?: Vector3) カメラの回転軸のオフセット位置を変更する
|
||||
---@field public setCameraRot fun(newRot?: Vector3) カメラ方向を変更する
|
||||
---@field public setThirdPersonCameraDistance fun(self: CameraManager, distance: number) 三人称視点でのカメラと回転軸の距離を設定する
|
||||
---@field public setCameraCollisionDenial fun(self: CameraManager, enabled: boolean) カメラの当たり判定打ち消し機能を設定する
|
||||
|
||||
CameraManager = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return CameraManager
|
||||
new = function (parent)
|
||||
---@type CameraManager
|
||||
local instance = Avatar.instantiate(CameraManager, AvatarModule, parent)
|
||||
|
||||
instance.COLLISION_DENIAL_DISABLED = false
|
||||
instance.thirdPersonCameraDistance = 4
|
||||
instance.isCameraCollisionDenialEnabled = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---CameraManagerのレンダー関数を設定する。
|
||||
---@param self CameraManager
|
||||
---@param enabled boolean CameraManagerのレンダー関数を有効化するかどうか
|
||||
setCameraManagerRender = function (self, enabled)
|
||||
if enabled and events.RENDER:getRegisteredCount("camera_manager_render") == 0 then
|
||||
events.RENDER:register(function ()
|
||||
if renderer:isFirstPerson() then
|
||||
renderer:setCameraPos()
|
||||
else
|
||||
local rawOffsetCameraPivot = renderer:getCameraOffsetPivot()
|
||||
rawOffsetCameraPivot = rawOffsetCameraPivot == nil and vectors.vec3() or rawOffsetCameraPivot
|
||||
local cameraPivot = player:getPos():add(0, 1.62, 0):add(rawOffsetCameraPivot)
|
||||
local cameraDir = client:getCameraDir()
|
||||
local baseVector = vectors.rotateAroundAxis(math.deg(math.asin(cameraDir.y)), 0, 0.21, 0, vectors.rotateAroundAxis(math.deg(math.atan2(cameraDir.z, cameraDir.x)) * -1 - 90, 1, 0, 0, 0, 1, 0))
|
||||
local minDistance = math.max(self.thirdPersonCameraDistance, 4)
|
||||
if not self.COLLISION_DENIAL_DISABLED then
|
||||
for i = 0, 3 do
|
||||
local startPos = vectors.rotateAroundAxis(i * 90 + 45, baseVector:copy(), cameraDir):add(cameraPivot)
|
||||
local _, collisionPos, _ = raycast:block(startPos, startPos:copy():add(cameraDir:copy():scale(-4)), "VISUAL", "NONE")
|
||||
minDistance = math.min(collisionPos:copy():sub(startPos):length(), minDistance)
|
||||
end
|
||||
end
|
||||
renderer:setCameraPos(0, 0, (minDistance > self.thirdPersonCameraDistance or self.isCameraCollisionDenialEnabled) and self.thirdPersonCameraDistance - minDistance or 0)
|
||||
end
|
||||
end, "camera_manager_render")
|
||||
elseif not enabled then
|
||||
events.RENDER:remove("camera_manager_render")
|
||||
renderer:setCameraPos()
|
||||
end
|
||||
end;
|
||||
|
||||
---カメラの回転軸のオフセット位置を変更する。
|
||||
---@param newPivot? Vector3 設定する新しいカメラ回転軸のオフセット位置。nilの場合は設定値がリセットされる。
|
||||
setCameraPivot = function (newPivot)
|
||||
if host:isHost() then
|
||||
renderer:setOffsetCameraPivot(newPivot)
|
||||
end
|
||||
end;
|
||||
|
||||
---カメラ方向を変更する。
|
||||
---@param newRot? Vector3 設定する新しいカメラのオフセット方向。nilの場合は設定値がリセットされる。
|
||||
setCameraRot = function (newRot)
|
||||
if host:isHost() then
|
||||
renderer:setCameraRot(newRot)
|
||||
end
|
||||
end;
|
||||
|
||||
---三人称視点でのカメラと回転軸の距離を設定する。
|
||||
---@param self CameraManager
|
||||
---@param distance number 設定する新しい距離(ブロック単位)。デフォルトは4ブロック。
|
||||
setThirdPersonCameraDistance = function (self, distance)
|
||||
if host:isHost() then
|
||||
if distance ~= 4 then
|
||||
self:setCameraManagerRender(true)
|
||||
elseif not self.isCameraCollisionDenialEnabled then
|
||||
self:setCameraManagerRender(false)
|
||||
end
|
||||
self.thirdPersonCameraDistance = distance
|
||||
end
|
||||
end;
|
||||
|
||||
---カメラの当たり判定打ち消し機能を設定する。
|
||||
---@param enabled boolean カメラの当たり判定打ち消し機能を有効にするかどうか。有効にするとカメラがブロックの中にめり込むようになる。
|
||||
setCameraCollisionDenial = function (self, enabled)
|
||||
if host:isHost() then
|
||||
if enabled then
|
||||
self:setCameraManagerRender(true)
|
||||
elseif self.thirdPersonCameraDistance == 4 then
|
||||
self:setCameraManagerRender(false)
|
||||
end
|
||||
self.isCameraCollisionDenialEnabled = enabled
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
---@alias Config.Storage
|
||||
---| "PUBLIC" # FBACキャラクター共通ストレージ
|
||||
---| "PRIVATE" # キャラクター固有ストレージ
|
||||
|
||||
---@class (exact) Config : AvatarModule アバター設定を管理するクラス
|
||||
---@field package privateStorageName string キャラクター固有ストレージの名前
|
||||
---@field package defaultValues {[string]: number|boolean|string} 読み込んだ値のデフォルト値を保持するテーブル
|
||||
---@field package nextSyncCount integer 次の同期pingまでのカウンター
|
||||
---@field package isSynced boolean 設定値がホストと同期されているかどうか
|
||||
---@field public loadConfig fun(self: Config, storage: Config.Storage, keyName: string, defaultValue: any): any 設定を読み出す
|
||||
---@field public saveConfig fun(self: Config, storage: Config.Storage, keyName: string, valueToSave: any) 設定を保存する
|
||||
|
||||
Config = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Config
|
||||
new = function (parent)
|
||||
---@type Config
|
||||
local instance = Avatar.instantiate(Config, AvatarModule, parent)
|
||||
|
||||
instance.privateStorageName = "BlueArchive_"..instance.parent.characterData.basic.firstName.en_us..instance.parent.characterData.basic.lastName.en_us
|
||||
instance.defaultValues = {}
|
||||
instance.nextSyncCount = 0
|
||||
instance.isSynced = host:isHost()
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Config
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if host:isHost() then
|
||||
events.TICK:register(function ()
|
||||
if self.nextSyncCount == 0 then
|
||||
pings.syncAvatarConfig(self.parent.nameplate.currentName, self.parent.nameplate.shouldShowClubName, self.parent.costume.currentCostume, self.parent.armor.shouldShowArmor, self.parent.actionWheel.shouldReplaceVehicleModels, self.parent.bubble.isChatOpened, self.parent.characterData.dataSync.syncData)
|
||||
self.nextSyncCount = 300
|
||||
else
|
||||
self.nextSyncCount = self.nextSyncCount - 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
end;
|
||||
|
||||
---設定を読み出す。
|
||||
---@generic T
|
||||
---@param self Config
|
||||
---@param storage Config.Storage 読み出し先のストレージ
|
||||
---@param keyName string 読み出す設定の名前
|
||||
---@param defaultValue `T` 該当の設定が無い場合や、ホスト外での実行の場合はこの値が返される。
|
||||
---@return `T` loadedValue 読み出した値
|
||||
loadConfig = function (self, storage, keyName, defaultValue)
|
||||
if host:isHost() then
|
||||
if storage == "PUBLIC" then
|
||||
config:setName("BlueArchive_public")
|
||||
else
|
||||
config:setName(self.privateStorageName)
|
||||
end
|
||||
local loadedData = config:load(keyName)
|
||||
self.defaultValues[keyName] = defaultValue
|
||||
if loadedData ~= nil then
|
||||
return loadedData
|
||||
else
|
||||
return defaultValue
|
||||
end
|
||||
else
|
||||
return defaultValue
|
||||
end
|
||||
end;
|
||||
|
||||
---設定を保存する。
|
||||
---@param self Config
|
||||
---@param storage Config.Storage 書き込み先のストレージ
|
||||
---@param keyName string 保存する設定の名前
|
||||
---@param valueToSave any 保存する値
|
||||
saveConfig = function (self, storage, keyName, valueToSave)
|
||||
if host:isHost() then
|
||||
if storage == "PUBLIC" then
|
||||
config:setName("BlueArchive_public")
|
||||
else
|
||||
config:setName(self.privateStorageName)
|
||||
end
|
||||
if self.defaultValues[keyName] == valueToSave then
|
||||
config:save(keyName, nil)
|
||||
else
|
||||
config:save(keyName, valueToSave)
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
||||
|
||||
---アバター設定を他Figuraクライアントと同期する。
|
||||
---@param nameTypeId integer 表示名の種類ID
|
||||
---@param shouldShowClubName boolean 部活名を表示するかどうか
|
||||
---@param costumeId integer 現在の衣装ID
|
||||
---@param shouldShowArmor boolean 防具が見えているかどうか
|
||||
---@param shouldReplaceVehicleModels boolean 乗り物モデルを置き換えるかどうか
|
||||
---@param isChatOpened boolean チャット欄を開いているかどうか
|
||||
---@param additionalData {[string]: any} キャラクター固有用に追加で同期するデータ
|
||||
function pings.syncAvatarConfig(nameTypeId, shouldShowClubName, costumeId, shouldShowArmor, shouldReplaceVehicleModels, isChatOpened, additionalData)
|
||||
if not AvatarInstance.config.isSynced then
|
||||
AvatarInstance.nameplate:setName(nameTypeId, shouldShowClubName)
|
||||
AvatarInstance.armor.shouldShowArmor = shouldShowArmor
|
||||
AvatarInstance.actionWheel.shouldReplaceVehicleModels = shouldReplaceVehicleModels
|
||||
AvatarInstance.bubble.isChatOpened = isChatOpened
|
||||
if costumeId >= 2 then
|
||||
AvatarInstance.costume:setCostume(costumeId)
|
||||
end
|
||||
AvatarInstance.characterData.dataSync.syncData = additionalData
|
||||
if AvatarInstance.characterData.dataSync.callbacks ~= nil and AvatarInstance.characterData.dataSync.callbacks.onDataSynced ~= nil then
|
||||
AvatarInstance.characterData.dataSync.callbacks.onDataSynced(AvatarInstance.characterData)
|
||||
end
|
||||
AvatarInstance.config.isSynced = true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,113 @@
|
|||
---@class (exact) Costume : AvatarModule キャラクターのコスチュームを管理し、円滑に切り替えられるようにするクラス
|
||||
---@field public costumeList string[] 利用可能なコスチューム一覧。BlueArchiveCharacterクラスから動的に生成される。
|
||||
---@field public currentCostume integer 現在のコスチューム
|
||||
---@field public isChangingCostume boolean コスチュームを変更中かどうか
|
||||
---@field public getCostumeLocalName fun(self: Costume, costumeId: integer) 設定言語を考慮した、衣装の名前を返す
|
||||
---@field public setCostumeTextureOffset fun(offset: integer) メインモデルのテクスチャのオフセット値を設定する
|
||||
---@field public setCostume fun(self: Costume, costume: integer) コスチュームを設定する
|
||||
---@field public resetCostume fun(self: Costume) コスチュームをリセットしデフォルトのコスチュームにする
|
||||
|
||||
Costume = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Costume
|
||||
new = function (parent)
|
||||
---@type Costume
|
||||
local instance = Avatar.instantiate(Costume, AvatarModule, parent)
|
||||
|
||||
instance.costumeList = {}
|
||||
instance.currentCostume = instance.parent.config:loadConfig("PRIVATE", "costume", 1)
|
||||
instance.isChangingCostume = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Costume
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
for _, costume in ipairs(self.parent.characterData.costume.costumes) do
|
||||
table.insert(self.costumeList, costume.name)
|
||||
end
|
||||
if self.currentCostume >= 2 then
|
||||
if self.costumeList[self.currentCostume] ~= nil then
|
||||
self:setCostume(self.currentCostume)
|
||||
else
|
||||
self.currentCostume = 1
|
||||
if host:isHost() then
|
||||
self.parent.config:saveConfig("PRIVATE", "costume", 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.parent.headBlock:generateHeadModel()
|
||||
self.parent.portrait:generateHeadModel()
|
||||
|
||||
events.TICK:register(function ()
|
||||
self.isChangingCostume = false
|
||||
end)
|
||||
end;
|
||||
|
||||
---設定言語を考慮した、衣装の名前を返す。
|
||||
---@param self Costume
|
||||
---@param costumeId integer ローカル名を取得する衣装のID
|
||||
---@return string localCostumeName 衣装のローカル名
|
||||
getCostumeLocalName = function(self, costumeId)
|
||||
return self.parent.locale:getLocale("costume."..self.costumeList[costumeId])
|
||||
end;
|
||||
|
||||
---メインモデルのテクスチャのオフセット値を設定する。
|
||||
---@param offset integer オフセット値
|
||||
setCostumeTextureOffset = function (offset)
|
||||
for _, modelPart in ipairs({models.models.main.Avatar.UpperBody.Body.Body, models.models.main.Avatar.UpperBody.Body.BodyLayer, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArm, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmLayer, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.RightArmBottom, models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.RightArmBottomLayer, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArm, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmLayer, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.LeftArmBottom, models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.LeftArmBottomLayer, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLeg, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegLayer, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.RightLegBottom, models.models.main.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.RightLegBottomLayer, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLeg, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegLayer, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.LeftLegBottom, models.models.main.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.LeftLegBottomLayer}) do
|
||||
modelPart:setUVPixels(0, offset * 48)
|
||||
end
|
||||
end;
|
||||
|
||||
---コスチュームを設定する。
|
||||
---@param self Costume
|
||||
---@param costume integer 設定するコスチューム
|
||||
setCostume = function(self, costume)
|
||||
self:resetCostume()
|
||||
if self.parent.characterData.costume.callbacks ~= nil and self.parent.characterData.costume.callbacks.onChange ~= nil then
|
||||
self.parent.characterData.costume.callbacks.onChange(self.parent.characterData, self.parent.characterData.costume.costumes[costume].name:upper())
|
||||
end
|
||||
self.parent.headBlock:generateHeadModel()
|
||||
self.parent.portrait:generateHeadModel()
|
||||
if self.parent.armor.isArmorVisible.chestplate then
|
||||
self.parent.armor:setChestplate(self.parent.armor.armorSlotItems[2])
|
||||
end
|
||||
if self.parent.armor.isArmorVisible.leggings then
|
||||
self.parent.armor:setLeggings(self.parent.armor.armorSlotItems[3])
|
||||
end
|
||||
if self.parent.armor.isArmorVisible.boots then
|
||||
self.parent.armor:setBoots(self.parent.armor.armorSlotItems[4])
|
||||
end
|
||||
self.currentCostume = costume
|
||||
end;
|
||||
|
||||
---コスチュームをリセットし、デフォルトのコスチュームにする。
|
||||
---@param self Costume
|
||||
resetCostume = function (self)
|
||||
self.isChangingCostume = true
|
||||
if self.parent.exSkill.transitionCount > 0 then
|
||||
self.parent.exSkill:forceStop()
|
||||
end
|
||||
self.setCostumeTextureOffset(0)
|
||||
if self.parent.characterData.costume.callbacks ~= nil and self.parent.characterData.costume.callbacks.onReset ~= nil then
|
||||
self.parent.characterData.costume.callbacks.onReset(self.parent.characterData)
|
||||
end
|
||||
self.parent.headBlock:generateHeadModel()
|
||||
self.parent.portrait:generateHeadModel()
|
||||
if self.parent.armor.isArmorVisible.chestplate then
|
||||
self.parent.armor:setChestplate(self.parent.armor.armorSlotItems[2])
|
||||
end
|
||||
if self.parent.armor.isArmorVisible.leggings then
|
||||
self.parent.armor:setLeggings(self.parent.armor.armorSlotItems[3])
|
||||
end
|
||||
if self.parent.armor.isArmorVisible.boots then
|
||||
self.parent.armor:setBoots(self.parent.armor.armorSlotItems[4])
|
||||
end
|
||||
self.currentCostume = 1
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
---@class (exact) DeathAnimation : AvatarModule プレイヤーが死亡した際のキャラクターがヘリコプターで回収されるアニメーションを管理するクラス
|
||||
---@field package DEBUG_MODE boolean デバッグモードを有効にするかどうか。デバッグモードモードではxキーでフェーズ1のモデルを、cキーでフェーズ2のモデルを表示できる。
|
||||
---@field public dummyAvatarRoot? ModelPart 死亡アニメーションに使用されるダミーのアバターのルート。アバターが未生成の場合はnilが入っている。
|
||||
---@field package animationCount integer 死亡アニメーションの再生カウンター
|
||||
---@field package animationPos Vector3 アニメーションを再生している場所の座標
|
||||
---@field package animationRot number アニメーションを再生している向き(度数法で示す)
|
||||
---@field package costumeIndex integer 死亡アニメーションのコスチュームのインデックス
|
||||
---@field package isPlayerInvisible boolean プレイヤーモデルが不可視状態かどうか
|
||||
---@field package isScriptLoaded false スクリプトを全て読み込んだかどうか
|
||||
---@field package removeUnsafeModel fun(target?: ModelPart) 存在しないかもしれないモデルパーツを安全に削除する
|
||||
---@field package spawnHelicopterParticles fun(self: DeathAnimation) ヘリコプターの出現/消滅パーティクルを生成する
|
||||
---@field package generateDummyAvatar fun(self: DeathAnimation, parent: ModelPart) 死亡アニメーション用のダミーアバターを生成する
|
||||
---@field package resetDummyAvatar fun(avatarRoot: ModelPart) ダミーアバター状態をリセットする
|
||||
---@field package setPhase1Pose fun(avatarRoot: ModelPart) ダミーアバターをフェーズ1のポーズにする
|
||||
---@field package setPhase2Pose fun(avatarRoot: ModelPart) ダミーアバターをフェーズ2のポーズにする
|
||||
---@field package play fun(self: DeathAnimation) 死亡アニメーションを再生する
|
||||
---@field package stop fun(self: DeathAnimation) 死亡アニメーションを停止する
|
||||
|
||||
DeathAnimation = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return DeathAnimation
|
||||
new = function (parent)
|
||||
---@type DeathAnimation
|
||||
local instance = Avatar.instantiate(DeathAnimation, AvatarModule, parent)
|
||||
|
||||
instance.DEBUG_MODE = false
|
||||
|
||||
instance.dummyAvatarRoot = models.models.death_animation.Avatar
|
||||
instance.animationCount = 0
|
||||
instance.animationPos = vectors.vec3()
|
||||
instance.animationRot = 0
|
||||
instance.costumeIndex = 1
|
||||
instance.isPlayerInvisible = false
|
||||
instance.isScriptLoaded = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self DeathAnimation
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
if self.parent.playerUtils.damageStatus == "DIED" then
|
||||
self:play()
|
||||
models.models.main:setVisible(false)
|
||||
for _, vanillaModel in ipairs({vanilla_model.RIGHT_ITEM, vanilla_model.LEFT_ITEM, vanilla_model.ELYTRA}) do
|
||||
vanillaModel:setVisible(false)
|
||||
end
|
||||
self.isPlayerInvisible = true
|
||||
end
|
||||
if self.isPlayerInvisible and player:getHealth() > 0 then
|
||||
models.models.main:setVisible(true)
|
||||
for _, vanillaModel in ipairs({vanilla_model.RIGHT_ITEM, vanilla_model.LEFT_ITEM, vanilla_model.ELYTRA}) do
|
||||
vanillaModel:setVisible(true)
|
||||
end
|
||||
self.isPlayerInvisible = false
|
||||
end
|
||||
end)
|
||||
|
||||
self.parent.avatarEvents.SCRIPT_INIT:register(function ()
|
||||
self.isScriptLoaded = true
|
||||
end)
|
||||
|
||||
if self.DEBUG_MODE then
|
||||
models:addChild(models:newPart("script_death_animation_debug", "World"))
|
||||
keybinds:newKeybind("[DEBUG] Spawn death animation phase1 model", "key.keyboard.x"):onPress(function ()
|
||||
self.removeUnsafeModel(models.script_death_animation_debug.Avatar)
|
||||
self:generateDummyAvatar(models.script_death_animation_debug)
|
||||
self.resetDummyAvatar(models.script_death_animation_debug.Avatar)
|
||||
self.setPhase1Pose(models.script_death_animation_debug.Avatar)
|
||||
models.script_death_animation_debug.Avatar:setPos(player:getPos():add(0, -0.75, 0):scale(16))
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onPhase1 ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onPhase1(self.parent.characterData, models.script_death_animation_debug.Avatar, self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].name:upper())
|
||||
end
|
||||
end)
|
||||
keybinds:newKeybind("[DEBUG] Spawn death animation phase2 model", "key.keyboard.c"):onPress(function ()
|
||||
self.removeUnsafeModel(models.script_death_animation_debug.Avatar)
|
||||
self:generateDummyAvatar(models.script_death_animation_debug)
|
||||
self.resetDummyAvatar(models.script_death_animation_debug.Avatar)
|
||||
self.setPhase2Pose(models.script_death_animation_debug.Avatar)
|
||||
models.script_death_animation_debug.Avatar:setPos(player:getPos():scale(16))
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onPhase1 ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onPhase1(self.parent.characterData, models.script_death_animation_debug.Avatar, self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].name:upper())
|
||||
end
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onPhase2 ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onPhase2(self.parent.characterData, models.script_death_animation_debug.Avatar, self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].name:upper())
|
||||
end
|
||||
end)
|
||||
end
|
||||
end;
|
||||
|
||||
---存在しないかもしれないモデルパーツを安全に削除する。
|
||||
---@param target? ModelPart 削除対象のモデルパーツ
|
||||
removeUnsafeModel = function (target)
|
||||
if target ~= nil then
|
||||
target:getParent():removeChild(target)
|
||||
target:remove()
|
||||
end
|
||||
end;
|
||||
|
||||
---ヘリコプターの出現/消滅パーティクルを生成する。
|
||||
---@param self DeathAnimation
|
||||
spawnHelicopterParticles = function (self)
|
||||
local helicopterPos = self.parent.modelUtils.getModelWorldPos(models.models.death_animation.Helicopter)
|
||||
for _ = 1, 100 do
|
||||
particles:newParticle(self.parent.compatibilityUtils:checkParticle("minecraft:poof"), helicopterPos:copy():add(vectors.rotateAroundAxis(self.animationRot, math.random() * 9.375 - 4.6875, math.random() * 11.125 - 5.5625, math.random() * 23.875 - 11.9375, 0, math.abs(helicopterPos.y), 0)))
|
||||
end
|
||||
end;
|
||||
|
||||
---死亡アニメーション用のダミーアバターを生成する。
|
||||
---@param self DeathAnimation
|
||||
---@param parent ModelPart ダミーアバターをアタッチする親のモデルパーツ
|
||||
generateDummyAvatar = function (self, parent)
|
||||
local isArmorVisible = {
|
||||
helmet = self.parent.armor.isArmorVisible.helmet;
|
||||
chestplate = self.parent.armor.isArmorVisible.chestplate;
|
||||
leggings = self.parent.armor.isArmorVisible.leggings;
|
||||
boots = self.parent.armor.isArmorVisible.boots;
|
||||
}
|
||||
if isArmorVisible.helmet then
|
||||
self.parent.armor:setHelmet(world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")))
|
||||
end
|
||||
if isArmorVisible.chestplate then
|
||||
self.parent.armor:setChestplate(world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")))
|
||||
end
|
||||
if isArmorVisible.leggings then
|
||||
self.parent.armor:setLeggings(world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")))
|
||||
end
|
||||
if isArmorVisible.boots then
|
||||
self.parent.armor:setBoots(world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")))
|
||||
end
|
||||
self.parent.physics:disable()
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onBeforeModelCopy ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onBeforeModelCopy(self.parent.characterData, self.isScriptLoaded)
|
||||
end
|
||||
|
||||
parent:addChild(self.parent.modelUtils:copyModel(models.models.main.Avatar))
|
||||
parent.Avatar.Head.FaceParts.Eyes.EyeRight:setUVPixels(self.parent.characterData.faceParts.leftEye.TIRED:copy():scale(6))
|
||||
parent.Avatar.Head.FaceParts.Eyes.EyeLeft:setUVPixels(self.parent.characterData.faceParts.rightEye.TIRED:copy():scale(6))
|
||||
parent.Avatar.Head.HeadRing:setRot()
|
||||
for _, modelPart in ipairs({parent.Avatar.UpperBody.Arms.RightArm.RightArmBottom.RightItemPivot, parent.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.LeftItemPivot}) do
|
||||
modelPart:remove()
|
||||
end
|
||||
local unsafeModels = {parent.Avatar.Head.FaceParts.Mouth, parent.Avatar.Head.ArmorH, parent.Avatar.UpperBody.Body.ArmorB, parent.Avatar.UpperBody.Arms.RightArm.ArmorRA, parent.Avatar.UpperBody.Arms.RightArm.RightArmBottom.ArmorRAB, parent.Avatar.UpperBody.Arms.LeftArm.ArmorLA, parent.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.ArmorLAB, parent.Avatar.LowerBody.Legs.RightLeg.ArmorRL, parent.Avatar.LowerBody.Legs.RightLeg.RightLegBottom.ArmorRLB, parent.Avatar.LowerBody.Legs.LeftLeg.ArmorLL, parent.Avatar.LowerBody.Legs.LeftLeg.LeftLegBottom.ArmorLLB}
|
||||
for i = 1, 11 do
|
||||
self.removeUnsafeModel(unsafeModels[i])
|
||||
end
|
||||
if parent.Avatar.UpperBody.Body.Gun ~= nil then
|
||||
if self.parent.characterData.gun.gunPosition.put.type == "BODY" then
|
||||
local leftHanded = player:isLeftHanded()
|
||||
parent.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, 12, 0):add(self.parent.characterData.gun.gunPosition.put.pos[leftHanded and "left" or "right"]))
|
||||
parent.Avatar.UpperBody.Body.Gun:setRot(self.parent.characterData.gun.gunPosition.put.rot[leftHanded and "left" or "right"])
|
||||
else
|
||||
parent.Avatar.UpperBody.Body.Gun:remove()
|
||||
end
|
||||
end
|
||||
|
||||
if isArmorVisible.helmet then
|
||||
self.parent.armor:setHelmet(self.parent.armor.armorSlotItems[1])
|
||||
end
|
||||
if isArmorVisible.chestplate then
|
||||
self.parent.armor:setChestplate(self.parent.armor.armorSlotItems[2])
|
||||
end
|
||||
if isArmorVisible.leggings then
|
||||
self.parent.armor:setLeggings(self.parent.armor.armorSlotItems[3])
|
||||
end
|
||||
if isArmorVisible.boots then
|
||||
self.parent.armor:setBoots(self.parent.armor.armorSlotItems[4])
|
||||
end
|
||||
self.parent.physics:enable()
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onAfterModelCopy ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onAfterModelCopy(self.parent.characterData, self.isScriptLoaded)
|
||||
end
|
||||
end;
|
||||
|
||||
---ダミーアバター状態をリセットする。
|
||||
---@param avatarRoot ModelPart ダミーアバターのルート
|
||||
resetDummyAvatar = function (avatarRoot)
|
||||
for _, modelPart in ipairs({avatarRoot, avatarRoot.Head, avatarRoot.UpperBody, avatarRoot.UpperBody.Body, avatarRoot.UpperBody.Arms, avatarRoot.UpperBody.Arms.RightArm, avatarRoot.UpperBody.Arms.RightArm.RightArmBottom, avatarRoot.UpperBody.Arms.LeftArm, avatarRoot.UpperBody.Arms.LeftArm.LeftArmBottom, avatarRoot.LowerBody, avatarRoot.LowerBody.Legs, avatarRoot.LowerBody.Legs.RightLeg, avatarRoot.LowerBody.Legs.RightLeg.RightLegBottom, avatarRoot.LowerBody.Legs.LeftLeg, avatarRoot.LowerBody.Legs.LeftLeg.LeftLegBottom}) do
|
||||
modelPart:setPos()
|
||||
modelPart:setRot()
|
||||
modelPart:setScale()
|
||||
end
|
||||
end;
|
||||
|
||||
---ダミーアバターをフェーズ1のポーズにする。
|
||||
---@param avatarRoot ModelPart ダミーアバターのルート
|
||||
setPhase1Pose = function (avatarRoot)
|
||||
avatarRoot:setPos(0, -12, 0)
|
||||
avatarRoot.Head:setRot(-30, 0, 0)
|
||||
avatarRoot.UpperBody.Arms.RightArm:setRot(35, 0, -20)
|
||||
avatarRoot.UpperBody.Arms.LeftArm:setRot(35, 0, 20)
|
||||
avatarRoot.LowerBody.Legs.RightLeg:setRot(90, -10, 0)
|
||||
avatarRoot.LowerBody.Legs.LeftLeg:setRot(90, 10, 0)
|
||||
end;
|
||||
|
||||
---ダミーアバターをフェーズ2のポーズにする。
|
||||
---@param avatarRoot ModelPart ダミーアバターのルート
|
||||
setPhase2Pose = function (avatarRoot)
|
||||
avatarRoot:setPos(3, -210, 2)
|
||||
avatarRoot:setRot(105, 75, 90)
|
||||
avatarRoot.Head:setRot(0, -40, 0)
|
||||
avatarRoot.UpperBody.Arms.RightArm:setRot(47.5, 0, 20)
|
||||
avatarRoot.UpperBody.Arms.LeftArm:setRot(-30, 0, -15)
|
||||
avatarRoot.LowerBody.Legs.RightLeg:setRot(80, 0, 0)
|
||||
avatarRoot.LowerBody.Legs.RightLeg.RightLegBottom:setRot(-75, 0, 0)
|
||||
avatarRoot.LowerBody.Legs.LeftLeg:setRot(10, 0, 0)
|
||||
avatarRoot:setLight()
|
||||
end;
|
||||
|
||||
---死亡アニメーションを再生する。
|
||||
---@param self DeathAnimation
|
||||
play = function (self)
|
||||
self:stop()
|
||||
self.costumeIndex = self.parent.costume.currentCostume
|
||||
|
||||
--ダミーアバターを生成する。
|
||||
local unsafeModels = {models.models.death_animation.Avatar, models.models.death_animation.Helicopter.RopeLadder.RopeLadder2.RopeLadder3.RopeLadder4.RopeLadder5.RopeLadder6.RopeLadder7.RopeLadder8.RopeLadder9.RopeLadder10.RopeLadder11.RopeLadder12.RopeLadder13.RopeLadder14.Avatar}
|
||||
for i = 1, 2 do
|
||||
self.removeUnsafeModel(unsafeModels[i])
|
||||
end
|
||||
self:generateDummyAvatar(models.models.death_animation)
|
||||
|
||||
--死亡アニメーションを生成する。
|
||||
self.resetDummyAvatar(models.models.death_animation.Avatar)
|
||||
self.setPhase1Pose(models.models.death_animation.Avatar)
|
||||
self.animationPos = player:getPos()
|
||||
models.models.death_animation:setPos(self.animationPos:copy():scale(16))
|
||||
self.animationRot = (player:getBodyYaw() * -1 + 180) % 360
|
||||
models.models.death_animation:setRot(0, self.animationRot)
|
||||
models.models.death_animation:setVisible(true)
|
||||
animations["models.death_animation"]["death_animation"]:play()
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onPhase1 ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onPhase1(self.parent.characterData, models.models.death_animation.Avatar, self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].name:upper())
|
||||
end
|
||||
|
||||
if events.TICK:getRegisteredCount("death_animation_tick") == 0 then
|
||||
events.TICK:register(function ()
|
||||
local particleAnchorPos = self.parent.modelUtils.getModelWorldPos(models.models.death_animation.DeathAnimationParticleAnchor)
|
||||
for _ = 1, 3 do
|
||||
local particleRot = math.random() * math.pi * 2
|
||||
local particleOffset = math.random() * 3
|
||||
particles:newParticle(self.parent.compatibilityUtils:checkParticle("minecraft:poof"), particleAnchorPos:copy():add(math.cos(particleRot) * particleOffset, 0, math.sin(particleRot) * particleOffset)):setVelocity(math.cos(particleRot), 0, math.sin(particleRot))
|
||||
|
||||
end
|
||||
if self.animationCount % 2 == 1 then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.bamboo_wood_door.close"), self.parent.modelUtils.getModelWorldPos(models.models.death_animation.Helicopter.DeathAnimationSoundAnchor1), 1, 0.5):setAttenuation(2)
|
||||
end
|
||||
if self.animationCount < 120 then
|
||||
models.models.death_animation.Avatar:setLight(world.getLightLevel(self.animationPos))
|
||||
end
|
||||
if self.animationCount == 1 then
|
||||
self:spawnHelicopterParticles()
|
||||
elseif self.animationCount == 10 then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.iron_door.open"), self.parent.modelUtils.getModelWorldPos(models.models.death_animation.Helicopter.DeathAnimationSoundAnchor1), 1, 0.5)
|
||||
elseif self.animationCount >= 57 and self.animationCount < 76 then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.player.attack.sweep"), self.parent.modelUtils.getModelWorldPos(models.models.death_animation.Helicopter.RopeLadder.RopeLadder2.RopeLadder3.RopeLadder4.RopeLadder5.RopeLadder6.RopeLadder7.RopeLadder8.RopeLadder9.RopeLadder10.RopeLadder11.RopeLadder12.RopeLadder13.RopeLadder14), 0.25, -0.056 * (self.animationCount - 57) + 2)
|
||||
elseif self.animationCount == 120 then
|
||||
self.dummyAvatarRoot = models.models.death_animation.Avatar:moveTo(models.models.death_animation.Helicopter.RopeLadder.RopeLadder2.RopeLadder3.RopeLadder4.RopeLadder5.RopeLadder6.RopeLadder7.RopeLadder8.RopeLadder9.RopeLadder10.RopeLadder11.RopeLadder12.RopeLadder13.RopeLadder14)
|
||||
self.setPhase2Pose(models.models.death_animation.Helicopter.RopeLadder.RopeLadder2.RopeLadder3.RopeLadder4.RopeLadder5.RopeLadder6.RopeLadder7.RopeLadder8.RopeLadder9.RopeLadder10.RopeLadder11.RopeLadder12.RopeLadder13.RopeLadder14.Avatar)
|
||||
if self.parent.characterData.deathAnimation.callbacks ~= nil and self.parent.characterData.deathAnimation.callbacks.onPhase2 ~= nil then
|
||||
self.parent.characterData.deathAnimation.callbacks.onPhase2(self.parent.characterData, models.models.death_animation.Helicopter.RopeLadder.RopeLadder2.RopeLadder3.RopeLadder4.RopeLadder5.RopeLadder6.RopeLadder7.RopeLadder8.RopeLadder9.RopeLadder10.RopeLadder11.RopeLadder12.RopeLadder13.RopeLadder14.Avatar, self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].name:upper())
|
||||
end
|
||||
elseif self.animationCount == 180 then
|
||||
models.models.death_animation.Helicopter.RopeLadder.RopeLadder2.RopeLadder3.RopeLadder4.RopeLadder5.RopeLadder6.RopeLadder7.RopeLadder8.RopeLadder9.RopeLadder10.RopeLadder11.RopeLadder12.RopeLadder13.RopeLadder14.Avatar:setVisible(false)
|
||||
elseif self.animationCount == 230 then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.iron_door.close"), self.parent.modelUtils.getModelWorldPos(models.models.death_animation.Helicopter.DeathAnimationSoundAnchor1), 1, 0.5)
|
||||
elseif self.animationCount == 255 then
|
||||
self:spawnHelicopterParticles()
|
||||
self:stop()
|
||||
end
|
||||
end, "death_animation_tick")
|
||||
end
|
||||
if events.WORLD_TICK:getRegisteredCount("death_animation_world_tick") == 0 then
|
||||
events.WORLD_TICK:register(function ()
|
||||
self.animationCount = self.animationCount + 1
|
||||
end, "death_animation_world_tick")
|
||||
end
|
||||
end;
|
||||
|
||||
---死亡アニメーションを停止する。
|
||||
---@param self DeathAnimation
|
||||
stop = function (self)
|
||||
models.models.death_animation:setVisible(false)
|
||||
animations["models.death_animation"]["death_animation"]:stop()
|
||||
events.TICK:remove("death_animation_tick")
|
||||
events.WORLD_TICK:remove("death_animation_world_tick")
|
||||
self.dummyAvatarRoot = nil
|
||||
self.animationCount = 0
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
---@class AbstractEvent : AvatarModule
|
||||
---@field public registerTable {[string]: fun()[]} 登録されたコールバック関数を管理するテーブル
|
||||
---@field public register fun(self: AbstractEvent, callback: fun(...: any), name?: string) イベントにコールバック関数登録する
|
||||
---@field public remove fun(self: AbstractEvent, name: string) 指定した名前のコールバック関数を1つ削除する
|
||||
---@field public getRegisteredCount fun(self: AbstractEvent, name: string): integer 指定した名前で登録されているコールバック関数の数を返す
|
||||
---@field public fire fun(self: AbstractEvent, ...: any) 登録された全てのコールバック関数を呼ぶ
|
||||
|
||||
AbstractEvent = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return AbstractEvent
|
||||
new = function (parent)
|
||||
---@type AbstractEvent
|
||||
local instance = Avatar.instantiate(AbstractEvent, AvatarModule, parent)
|
||||
|
||||
instance.registerTable = {}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---イベントにコールバック関数登録する。
|
||||
---@param self AbstractEvent
|
||||
---@param callback fun(...: any) 登録するコールバック関数
|
||||
---@param name? string コールバック関数の名前
|
||||
register = function (self, callback, name)
|
||||
local callbackName = name ~= nil and name or client.intUUIDToString(client.generateUUID())
|
||||
if self.registerTable[callbackName] ~= nil then
|
||||
table.insert(self.registerTable[callbackName], callback)
|
||||
else
|
||||
local callbackTable = {}
|
||||
table.insert(callbackTable, callback)
|
||||
self.registerTable[callbackName] = callbackTable
|
||||
end
|
||||
end;
|
||||
|
||||
---指定した名前のコールバック関数を1つ削除する。
|
||||
---@param self AbstractEvent
|
||||
---@param name string コールバック関数の名前
|
||||
remove = function (self, name)
|
||||
if self.registerTable[name] ~= nil then
|
||||
table.remove(self.registerTable[name])
|
||||
if #self.registerTable[name] == 0 then
|
||||
self.registerTable[name] = nil
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
---指定した名前で登録されているコールバック関数の数を返す。
|
||||
---@param self AbstractEvent
|
||||
---@param name string コールバック関数の名前
|
||||
---@return integer registeredCount 登録されていたコールバック関数の数
|
||||
getRegisteredCount = function (self, name)
|
||||
if self.registerTable[name] ~= nil then
|
||||
return #self.registerTable[name]
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end;
|
||||
|
||||
---登録された全てのコールバック関数を呼ぶ。
|
||||
---@param self AbstractEvent
|
||||
---@param ... any callbackArgs コールバック引数
|
||||
fire = function (self, ...)
|
||||
for _, eventTable in pairs(self.registerTable) do
|
||||
for _, callback in ipairs(eventTable) do
|
||||
---@diagnostic disable-next-line: redundant-parameter
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
---@class AvatarEvents : AvatarModule アバター独自のイベントを定義し、管理するクラス
|
||||
---@field public SCRIPT_INIT ScriptInitEvent 全てのスクリプトの初期化が完了した後に1度だけ呼ばれるイベント
|
||||
|
||||
AvatarEvents = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return AvatarEvents
|
||||
new = function (parent)
|
||||
---@type AvatarEvents
|
||||
local instance = Avatar.instantiate(AvatarEvents, AvatarModule, parent)
|
||||
|
||||
instance.SCRIPT_INIT = ScriptInitEvent.new(instance.parent)
|
||||
|
||||
return instance
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
---@class ScriptInitEvent : AbstractEvent 全てのスクリプトの初期化が完了した後に1度だけ呼ばれるイベント
|
||||
---@field public register fun(self: AbstractEvent, callback: fun(), name?: string) イベントにコールバック関数登録する
|
||||
---@field public fire fun(self: AbstractEvent) 登録された全てのコールバック関数を呼ぶ
|
||||
|
||||
ScriptInitEvent = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ScriptInitEvent
|
||||
new = function (parent)
|
||||
---@type ScriptInitEvent
|
||||
local instance = Avatar.instantiate(ScriptInitEvent, AbstractEvent, parent)
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---登録された全てのコールバック関数を呼ぶ。
|
||||
---@param self ScriptInitEvent
|
||||
fire = function (self, ...)
|
||||
for _, eventTable in pairs(self.registerTable) do
|
||||
for _, callback in ipairs(eventTable) do
|
||||
callback()
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,434 @@
|
|||
---@alias ExSkill.AutoPlayMode
|
||||
---| "NONE" # 自動再生なし
|
||||
---| "MAIN" # メインExスキル
|
||||
---| "SUB" # サブExスキル
|
||||
|
||||
---@alias ExSkill.TransitionPhase
|
||||
---| "PRE" # Exスキルアニメーション開始前
|
||||
---| "POST" # Exスキルアニメーション終了後
|
||||
|
||||
---@class (exact) ExSkill : AvatarModule Exスキルのアニメーションを管理するクラス
|
||||
---@field package AUTO_PLAY ExSkill.AutoPlayMode アバター読み込み時に自動的にExスキルが再生される。デバッグ用。
|
||||
---@field public frameParticleAmount integer Exスキルフレームのパーティクルの量:1. 標準, 2. 少なめ, 3. なし, 4. Exスキルフレーム非表示、パーティクル量は標準
|
||||
---@field package exSkillIndex integer 現在再生中のExスキルのインデックス番号
|
||||
---@field public animationCount integer Exスキルのアニメーション再生中に増加するカウンター。-1はアニメーション停止中を示す。
|
||||
---@field package animationLength number Exスキルのアニメーションの長さ。スクリプトで自動で代入する。
|
||||
---@field public transitionCount number Exスキルのアニメーション前後のカメラのトランジションの進捗を示すカウンター
|
||||
---@field package keyPressCount integer Exスキルキーを押下し続ける時間を計るカウンター
|
||||
---@field package bodyYaw number[] プレイヤーの体の回転
|
||||
---@field package isDebugInit boolean デバッグモードが初期化されたかどうか
|
||||
---@field public canPlayAnimation fun(self: ExSkill): boolean アニメーションが再生可能かどうかを返す
|
||||
---@field package transition fun(self: ExSkill, direction: ExSkill.TransitionPhase, callback: fun()) Exスキルのアニメーションの前後のカメラのトランジションを行う関数
|
||||
---@field public play fun(self: ExSkill, isSubExSkill: boolean) アニメーションを再生する
|
||||
---@field public stop fun(self: ExSkill) アニメーションを停止する
|
||||
---@field public forceStop fun(self: ExSkill) アニメーションを停止する。終了時のトランジションも無効。
|
||||
|
||||
ExSkill = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ExSkill
|
||||
new = function (parent)
|
||||
---@type ExSkill
|
||||
local instance = Avatar.instantiate(ExSkill, AvatarModule, parent)
|
||||
|
||||
instance.AUTO_PLAY = "NONE"
|
||||
|
||||
instance.frameParticleAmount = instance.parent.config:loadConfig("PRIVATE", "exSkillFrameParticleAmount", 1)
|
||||
instance.exSkillIndex = 1
|
||||
instance.animationCount = -1
|
||||
instance.animationLength = 0
|
||||
instance.transitionCount = 0
|
||||
instance.keyPressCount = 0
|
||||
instance.bodyYaw = {}
|
||||
instance.isDebugInit = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self ExSkill
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if host:isHost() then
|
||||
for _, exSkill in ipairs(self.parent.characterData.exSkill) do
|
||||
local offset = (exSkill.camera.fixMode ~= nil and exSkill.camera.fixMode) and 1 or 0.9375
|
||||
exSkill.camera.start.pos:mul(-1, 1, 1):scale(1 / 16 * offset)
|
||||
exSkill.camera.fin.pos:mul(-1, 1, 1):scale(1 / 16 * offset)
|
||||
end
|
||||
|
||||
local exSkillKey = self.parent.keyManager:register("ex_skill", self.parent.config:loadConfig("PRIVATE", "keybind.ex_skill", "key.keyboard.g"))
|
||||
exSkillKey:setOnPress(function ()
|
||||
while events.TICK:getRegisteredCount("ex_skill_keypress_tick") > 0 do
|
||||
events.TICK:remove("ex_skill_keypress_tick")
|
||||
end
|
||||
events.TICK:register(function ()
|
||||
if self.keyPressCount == 30 then
|
||||
events.TICK:remove("ex_skill_keypress_tick")
|
||||
self.parent.placementObjectManager:removeAll()
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.zombie.break_wooden_door"), player:getPos(), 0.25, 2)
|
||||
self.keyPressCount = 0
|
||||
return
|
||||
end
|
||||
self.keyPressCount = self.keyPressCount + 1
|
||||
end, "ex_skill_keypress_tick")
|
||||
end)
|
||||
exSkillKey:setOnRelease(function ()
|
||||
events.TICK:remove("ex_skill_keypress_tick")
|
||||
if self.keyPressCount > 0 then
|
||||
if self:canPlayAnimation() and self.animationCount == -1 and self.transitionCount == 0 then
|
||||
pings.exSkill()
|
||||
else
|
||||
print(self.parent.locale:getLocale("key_bind.ex_skill.unavailable"..(renderer:isFirstPerson() and "_firstperson" or "")))
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.note_block.bass"), player:getPos(), 1, 0.5)
|
||||
end
|
||||
self.keyPressCount = 0
|
||||
end
|
||||
end)
|
||||
self.parent.keyManager:register("ex_skill_sub", self.parent.config:loadConfig("PRIVATE", "keybind.ex_skill_sub", "key.keyboard.h")):setOnPress(function ()
|
||||
if self:canPlayAnimation() and self.animationCount == -1 and self.transitionCount == 0 and self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill ~= nil then
|
||||
pings.subExSkill()
|
||||
else
|
||||
print(self.parent.locale:getLocale(self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill == nil and "action_wheel.main.action_6.unavailable" or "key_bind.ex_skill.unavailable"..(renderer:isFirstPerson() and "_firstperson" or "")))
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.note_block.bass"), player:getPos(), 1, 0.5)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
table.insert(self.bodyYaw, player:getBodyYaw() % 360)
|
||||
|
||||
events.TICK:register(function ()
|
||||
if not renderer:isFirstPerson() then
|
||||
table.insert(self.bodyYaw, player:getBodyYaw() % 360)
|
||||
if #self.bodyYaw == 3 then
|
||||
table.remove(self.bodyYaw, 1)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if self.AUTO_PLAY ~= "NONE" then
|
||||
events.TICK:register(function ()
|
||||
if not self.isDebugInit then
|
||||
events.TICK:remove("ex_skill_debug_tick")
|
||||
self:play(self.AUTO_PLAY == "SUB")
|
||||
self.isDebugInit = true
|
||||
end
|
||||
end, "ex_skill_debug_tick")
|
||||
end
|
||||
end;
|
||||
|
||||
---アニメーションが再生可能かどうかを返す。
|
||||
---@param self ExSkill
|
||||
---@return boolean canPlayAnimation Exスキルアニメーションが再生可能かどうか
|
||||
canPlayAnimation = function (self)
|
||||
return player:getPose() == "STANDING" and player:getVelocity():length() < 0.01 and self.bodyYaw[1] == self.bodyYaw[2] and player:isOnGround() and not player:isInWater() and not player:isInLava() and player:getFrozenTicks() == 0 and not renderer:isFirstPerson() and self.parent.playerUtils.damageStatus == "NONE" and player:getSwingArm() == nil and player:getActiveItem().id == "minecraft:air" and not self.parent.costume.isChangingCostume
|
||||
end;
|
||||
|
||||
---Exスキルのアニメーションの前後のカメラのトランジションを行う関数
|
||||
---@param self ExSkill
|
||||
---@param direction ExSkill.TransitionPhase カメラのトランジションの向き
|
||||
---@param callback fun() トランジション終了時に呼び出されるコールバック関数
|
||||
transition = function (self, direction, callback)
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
if events.TICK:getRegisteredCount("ex_skill_transition_tick") == 1 then
|
||||
self.transitionCount = direction == "PRE" and math.min(self.transitionCount + 1, 10) or math.max(self.transitionCount - 1, 0)
|
||||
end
|
||||
if (direction == "PRE" and self.transitionCount == 10) or (direction == "POST" and self.transitionCount == 0) then
|
||||
if host:isHost() then
|
||||
local windowSize = client:getScaledWindowSize()
|
||||
models.models.ex_skill_frame.Gui.FrameBar:setPos(0, 0, 0)
|
||||
if direction == "PRE" and self.frameParticleAmount < 4 then
|
||||
for _, modelPart in ipairs({models.models.ex_skill_frame.Gui.Frame.FrameTopLeft, models.models.ex_skill_frame.Gui.Frame.FrameTopRight, models.models.ex_skill_frame.Gui.Frame.FrameBottomLeft, models.models.ex_skill_frame.Gui.Frame.FrameBottomRight}) do
|
||||
modelPart:setVisible(true)
|
||||
end
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTop:setPos(-windowSize.x + 16, -16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTop:setScale(windowSize.x / 16 - 2, 1, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameLeft:setPos(-16, -windowSize.y + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameLeft:setScale(1, windowSize.y / 16 - 2, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottom:setPos(-windowSize.x + 16, -windowSize.y)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottom:setScale(windowSize.x / 16 - 2, 1, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameRight:setPos(-windowSize.x, -windowSize.y + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameRight:setScale(1, windowSize.y / 16 - 2, 1)
|
||||
elseif direction == "POST" then
|
||||
for _, modelPart in ipairs({models.models.ex_skill_frame.Gui.Frame.FrameTopLeft, models.models.ex_skill_frame.Gui.Frame.FrameTopRight, models.models.ex_skill_frame.Gui.Frame.FrameBottomLeft, models.models.ex_skill_frame.Gui.Frame.FrameBottomRight}) do
|
||||
modelPart:setVisible(false)
|
||||
end
|
||||
for _, modelPart in ipairs({models.models.ex_skill_frame.Gui.Frame.FrameTop, models.models.ex_skill_frame.Gui.Frame.FrameLeft, models.models.ex_skill_frame.Gui.Frame.FrameBottom, models.models.ex_skill_frame.Gui.Frame.FrameRight}) do
|
||||
modelPart:setScale(0, 0, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
callback()
|
||||
events.TICK:remove("ex_skill_transition_tick")
|
||||
events.RENDER:remove("ex_skill_transition_render")
|
||||
end
|
||||
if host:isHost() then
|
||||
local barPos = models.models.ex_skill_frame.Gui.FrameBar:getPos().x * -1
|
||||
local windowSizeY = client:getScaledWindowSize().y
|
||||
if self.frameParticleAmount ~= 3 and self.transitionCount >= 1 and self.transitionCount <= 9 then
|
||||
for _ = 1, windowSizeY / (self.frameParticleAmount == 2 and 100 or 20) do
|
||||
local particleOffset = math.random() * windowSizeY
|
||||
self.parent.frameParticleManager:spawn(vectors.vec2(barPos - particleOffset - math.random() * 50, particleOffset):scale(-1), vectors.vec2(500, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "ex_skill_transition_tick")
|
||||
|
||||
events.RENDER:register(function (delta)
|
||||
--カメラのトランジション
|
||||
if not client:isPaused() and host:isHost() then
|
||||
local lookDir = player:getLookDir()
|
||||
local cameraRot = renderer:isCameraBackwards() and vectors.vec3(math.deg(math.asin(lookDir.y)), math.deg(math.atan2(lookDir.z, lookDir.x) + math.pi / 2)) or vectors.vec3(math.deg(math.asin(-lookDir.y)), math.deg(math.atan2(lookDir.z, lookDir.x) - math.pi / 2))
|
||||
local targetCameraPos = vectors.vec3()
|
||||
local targetCameraRot = vectors.vec3()
|
||||
if direction == "PRE" then
|
||||
targetCameraPos = vectors.rotateAroundAxis(self.bodyYaw[2] * -1 + 180, self.parent.characterData.exSkill[self.exSkillIndex].camera.start.pos, 0, 1):add(0, -1.62)
|
||||
targetCameraRot = self.parent.characterData.exSkill[self.exSkillIndex].camera.start.rot:copy():add(0, self.bodyYaw[2], 0)
|
||||
else
|
||||
targetCameraPos = vectors.rotateAroundAxis(self.bodyYaw[2] * -1 + 180, self.parent.characterData.exSkill[self.exSkillIndex].camera.fin.pos, 0, 1):add(0, -1.62)
|
||||
targetCameraRot = self.parent.characterData.exSkill[self.exSkillIndex].camera.fin.rot:copy():add(0, self.bodyYaw[2], 0)
|
||||
end
|
||||
if math.abs(cameraRot.y - targetCameraRot.y) >= 180 then
|
||||
if cameraRot.y < targetCameraRot.y then
|
||||
cameraRot.y = cameraRot.y + 360
|
||||
else
|
||||
targetCameraRot.y = targetCameraRot.y + 360
|
||||
end
|
||||
end
|
||||
local trueDelta = direction == "PRE" and delta or delta * -1
|
||||
self.parent.cameraManager.setCameraPivot(targetCameraPos:scale((self.transitionCount + trueDelta) / 10))
|
||||
self.parent.cameraManager.setCameraRot(targetCameraRot:copy():sub(cameraRot):scale((self.transitionCount + trueDelta) / 10):add(cameraRot))
|
||||
self.parent.cameraManager:setThirdPersonCameraDistance(4 - (self.transitionCount + trueDelta) / 10 * 4)
|
||||
|
||||
--フレーム演出
|
||||
local windowSize = client:getScaledWindowSize()
|
||||
local barPos = (windowSize.x + windowSize.y + math.sqrt(2) * 16) * (direction == "PRE" and (self.transitionCount + trueDelta) / 10 or (1 - (self.transitionCount + trueDelta) / 10))
|
||||
models.models.ex_skill_frame.Gui.FrameBar:setPos(-barPos, 0, 0)
|
||||
|
||||
if self.frameParticleAmount < 4 then
|
||||
local frameTopLength = math.clamp(barPos, 32, windowSize.x)
|
||||
local frameLeftLength = math.clamp(barPos, 32, windowSize.y)
|
||||
local frameBottomLength = math.clamp(barPos - windowSize.y + 16, 32, windowSize.x)
|
||||
local frameRightLength = math.clamp(barPos - windowSize.x + 16, 32, windowSize.y)
|
||||
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTopLeft:setPos(-16, -16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTopRight:setPos(-windowSize.x, -16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottomLeft:setPos(-16, -windowSize.y)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottomRight:setPos(-windowSize.x, -windowSize.y)
|
||||
if direction == "PRE" then
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTopLeft:setVisible(barPos >= 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTopRight:setVisible(barPos >= windowSize.x + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottomLeft:setVisible(barPos >= windowSize.y + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottomRight:setVisible(barPos >= windowSize.x + windowSize.y)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTop:setPos(-frameTopLength + 16, -16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTop:setScale(frameTopLength / 16 - 2, 1, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameLeft:setPos(-16, -frameLeftLength + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameLeft:setScale(1, frameLeftLength / 16 - 2, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottom:setPos(-frameBottomLength + 16, -windowSize.y)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottom:setScale(frameBottomLength / 16 - 2, 1, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameRight:setPos(-windowSize.x, -frameRightLength + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameRight:setScale(1, frameRightLength / 16 - 2, 1)
|
||||
else
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTopLeft:setVisible(barPos < 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTopRight:setVisible(barPos < windowSize.x + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottomLeft:setVisible(barPos < windowSize.y + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottomRight:setVisible(barPos < windowSize.x + windowSize.y)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTop:setPos(-windowSize.x + 16, -16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameTop:setScale((windowSize.x - frameTopLength) / 16, 1, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameLeft:setPos(-16, -windowSize.y + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameLeft:setScale(1, (windowSize.y - frameLeftLength) / 16, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottom:setPos(-windowSize.x + 16, -windowSize.y)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameBottom:setScale((windowSize.x - frameBottomLength) / 16, 1, 1)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameRight:setPos(-windowSize.x, -windowSize.y + 16)
|
||||
models.models.ex_skill_frame.Gui.Frame.FrameRight:setScale(1, (windowSize.y - frameRightLength) / 16, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "ex_skill_transition_render")
|
||||
end;
|
||||
|
||||
---アニメーションを再生する。
|
||||
---@param self ExSkill
|
||||
---@param isSubExSkill boolean サブExスキルを再生するかどうか
|
||||
play = function (self, isSubExSkill)
|
||||
if isSubExSkill then
|
||||
if self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill ~= nil then
|
||||
self.exSkillIndex = self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].subExSkill
|
||||
else
|
||||
return
|
||||
end
|
||||
else
|
||||
self.exSkillIndex = self.parent.characterData.costume.costumes[self.parent.costume.currentCostume].exSkill
|
||||
end
|
||||
|
||||
self.parent.bubble:stop()
|
||||
renderer:setFOV(70 / client:getFOV())
|
||||
renderer:setRenderHUD(false)
|
||||
self.parent.cameraManager:setCameraCollisionDenial(true)
|
||||
models.models.ex_skill_frame.Gui:setColor(self.parent.characterData.exSkill[self.exSkillIndex].formationType == "STRIKER" and vectors.vec3(1, 0.75, 0.75) or vectors.vec3(0.75, 1, 1))
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.player.levelup"), player:getPos(), 5, 2)
|
||||
if self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPreTransition ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPreTransition(self.parent.characterData)
|
||||
end
|
||||
models.models.ex_skill_frame.Gui.FrameBar:setScale(1, client:getScaledWindowSize().y * math.sqrt(2) / 16 + 1, 1)
|
||||
|
||||
events.TICK:register(function ()
|
||||
if not self:canPlayAnimation() then
|
||||
self:forceStop()
|
||||
end
|
||||
end, "ex_skill_tick")
|
||||
|
||||
self:transition("PRE", function ()
|
||||
self.parent.physics:disable()
|
||||
for _, itemModel in ipairs({vanilla_model.RIGHT_ITEM, vanilla_model.LEFT_ITEM}) do
|
||||
itemModel:setVisible(false)
|
||||
end
|
||||
for _, modelPart in ipairs(self.parent.characterData.exSkill[self.exSkillIndex].models) do
|
||||
modelPart:setVisible(true)
|
||||
end
|
||||
for _, modelPart in ipairs(self.parent.characterData.exSkill[self.exSkillIndex].animations) do
|
||||
animations["models."..modelPart]["ex_skill_"..self.exSkillIndex]:play()
|
||||
end
|
||||
if self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPreAnimation ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPreAnimation(self.parent.characterData)
|
||||
end
|
||||
self.parent.cameraManager:setThirdPersonCameraDistance(0)
|
||||
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
if self.animationCount == self.animationLength - 1 then
|
||||
self:stop()
|
||||
elseif self:canPlayAnimation() and animations["models.main"]["ex_skill_"..self.exSkillIndex]:isPlaying() then
|
||||
if self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onAnimationTick ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onAnimationTick(self.parent.characterData, self.animationCount)
|
||||
end
|
||||
self.animationCount = self.animationCount > -1 and self.animationCount + 1 or self.animationCount
|
||||
end
|
||||
|
||||
if host:isHost() then
|
||||
local windowSize = client:getScaledWindowSize()
|
||||
local windowCenter = windowSize:copy():scale(0.5)
|
||||
if self.frameParticleAmount < 3 then
|
||||
for _ = 1, (windowSize.x * 2 + windowSize.y * 2) / (self.frameParticleAmount == 1 and 100 or 500) do
|
||||
local particlePos = vectors.vec2(math.random() * (windowSize.x * 2 + windowSize.y * 2), math.random() * 16)
|
||||
particlePos = particlePos.x <= windowSize.x and particlePos or (particlePos.x <= windowSize.x + windowSize.y and vectors.vec2(windowSize.x - particlePos.y, particlePos.x - windowSize.x) or (particlePos.x <= windowSize.x * 2 + windowSize.y and vectors.vec2(particlePos.x - (windowSize.x + windowSize.y), windowSize.y - particlePos.y) or vectors.vec2(particlePos.y, particlePos.x - (windowSize.x * 2 + windowSize.y))))
|
||||
self.parent.frameParticleManager:spawn(particlePos:copy():scale(-1), windowCenter:copy():sub(particlePos):scale(0.25))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "ex_skill_animation_tick")
|
||||
|
||||
if host:isHost() then
|
||||
events.RENDER:register(function ()
|
||||
if not client:isPaused() then
|
||||
self.parent.cameraManager.setCameraPivot(vectors.rotateAroundAxis(self.bodyYaw[2] * -1 + 180, models.models.main.CameraAnchor:getAnimPos():scale(1 / 16 * ((self.parent.characterData.exSkill[self.exSkillIndex].camera.fixMode ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].camera.fixMode) and 1 or 0.9375)), 0, 1, 0):add(0, -1.62, 0))
|
||||
self.parent.cameraManager.setCameraRot(models.models.main.CameraAnchor:getAnimRot():scale(-1):add(0, self.bodyYaw[2], 0))
|
||||
end
|
||||
end, "ex_skill_animation_render")
|
||||
end
|
||||
self.animationCount = 0
|
||||
self.parent.gun:processGunTick()
|
||||
self.animationLength = math.round(animations["models.main"]["ex_skill_"..self.exSkillIndex]:getLength() * 20)
|
||||
end)
|
||||
end;
|
||||
|
||||
---アニメーションを停止する。
|
||||
---@param self ExSkill
|
||||
stop = function (self)
|
||||
events.TICK:remove("ex_skill_animation_tick")
|
||||
if host:isHost() then
|
||||
events.RENDER:remove("ex_skill_animation_render")
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.player.levelup"), player:getPos(), 5, 2):setAttenuation(100)
|
||||
end
|
||||
|
||||
for _, itemModel in ipairs({vanilla_model.RIGHT_ITEM, vanilla_model.LEFT_ITEM}) do
|
||||
itemModel:setVisible(true)
|
||||
end
|
||||
for _, modelPart in ipairs(self.parent.characterData.exSkill[self.exSkillIndex].models) do
|
||||
modelPart:setVisible(false)
|
||||
end
|
||||
for _, modelPart in ipairs(self.parent.characterData.exSkill[self.exSkillIndex].animations) do
|
||||
animations["models."..modelPart]["ex_skill_"..self.exSkillIndex]:stop()
|
||||
end
|
||||
self.parent.physics:enable()
|
||||
renderer:setFOV()
|
||||
self.animationCount = -1
|
||||
|
||||
if self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostAnimation ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostAnimation(self.parent.characterData, false)
|
||||
end
|
||||
self:transition("POST", function ()
|
||||
events.TICK:remove("ex_skill_tick")
|
||||
self.parent.cameraManager.setCameraPivot()
|
||||
self.parent.cameraManager.setCameraRot()
|
||||
self.parent.cameraManager:setThirdPersonCameraDistance(4)
|
||||
self.parent.cameraManager:setCameraCollisionDenial(false)
|
||||
renderer:setRenderHUD(true)
|
||||
|
||||
if self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostTransition ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostTransition(self.parent.characterData, false)
|
||||
end
|
||||
end)
|
||||
end;
|
||||
|
||||
---アニメーションを停止する。終了時のトランジションも無効。
|
||||
---@param self ExSkill
|
||||
forceStop = function (self)
|
||||
events.RENDER:remove("ex_skill_transition_render")
|
||||
if host:isHost() then
|
||||
events.TICK:remove("ex_skill_transition_tick")
|
||||
events.RENDER:remove("ex_skill_animation_render")
|
||||
end
|
||||
|
||||
for _, itemModel in ipairs({vanilla_model.RIGHT_ITEM, vanilla_model.LEFT_ITEM}) do
|
||||
itemModel:setVisible(true)
|
||||
end
|
||||
for _, modelPart in ipairs(self.parent.characterData.exSkill[self.exSkillIndex].models) do
|
||||
modelPart:setVisible(false)
|
||||
end
|
||||
for _, modelPart in ipairs(self.parent.characterData.exSkill[self.exSkillIndex].animations) do
|
||||
animations["models."..modelPart]["ex_skill_"..self.exSkillIndex]:stop()
|
||||
end
|
||||
for _, eventName in ipairs({"ex_skill_tick", "ex_skill_animation_tick"}) do
|
||||
events.TICK:remove(eventName)
|
||||
end
|
||||
self.parent.physics:enable()
|
||||
models.models.ex_skill_frame.Gui.FrameBar:setPos()
|
||||
for _, modelPart in ipairs({models.models.ex_skill_frame.Gui.Frame.FrameTopLeft, models.models.ex_skill_frame.Gui.Frame.FrameTopRight, models.models.ex_skill_frame.Gui.Frame.FrameBottomLeft, models.models.ex_skill_frame.Gui.Frame.FrameBottomRight}) do
|
||||
modelPart:setVisible(false)
|
||||
end
|
||||
for _, modelPart in ipairs({models.models.ex_skill_frame.Gui.Frame.FrameTop, models.models.ex_skill_frame.Gui.Frame.FrameLeft, models.models.ex_skill_frame.Gui.Frame.FrameBottom, models.models.ex_skill_frame.Gui.Frame.FrameRight}) do
|
||||
modelPart:setScale(0, 0, 0)
|
||||
end
|
||||
self.parent.faceParts:resetEmotion()
|
||||
self.parent.cameraManager.setCameraPivot()
|
||||
self.parent.cameraManager.setCameraRot()
|
||||
self.parent.cameraManager:setThirdPersonCameraDistance(4)
|
||||
self.parent.cameraManager:setCameraCollisionDenial(false)
|
||||
renderer:setRenderHUD(true)
|
||||
renderer:setFOV()
|
||||
|
||||
if self.animationCount >= 0 and self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostAnimation ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostAnimation(self.parent.characterData, true)
|
||||
end
|
||||
if self.parent.characterData.exSkill[self.exSkillIndex].callbacks ~= nil and self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostTransition ~= nil then
|
||||
self.parent.characterData.exSkill[self.exSkillIndex].callbacks.onPostTransition(self.parent.characterData, true)
|
||||
end
|
||||
|
||||
self.animationCount = -1
|
||||
self.transitionCount = 0
|
||||
end;
|
||||
}
|
||||
|
||||
---Exスキルを再生する。
|
||||
function pings.exSkill()
|
||||
AvatarInstance.exSkill:play(false)
|
||||
end
|
||||
|
||||
---サブExスキルを再生する。
|
||||
function pings.subExSkill()
|
||||
AvatarInstance.exSkill:play(true)
|
||||
end
|
|
@ -0,0 +1,68 @@
|
|||
---@class (exact) ExSkillFrameParticle : SpawnObject Exスキルのフレームで使用するパーティクルの単一を管理するクラス
|
||||
---@field package object ModelPart インスタンスで制御するオブジェクト
|
||||
---@field package currentPos Vector2 パーティクルの現在位置
|
||||
---@field package nextPos Vector2 次ティックのパーティクルの位置
|
||||
---@field package velocity Vector2 パーティクルの速度
|
||||
---@field package particleCount integer パーティクルのアニメーションを制御するためのカウンター
|
||||
---@field public new fun(parent: Avatar, pos: Vector2, velocity: Vector2, type: ExSkillFrameParticleManager.ParticleType): ExSkillFrameParticle コンストラクター
|
||||
|
||||
ExSkillFrameParticle = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@param pos Vector2 パーティクルをスポーンさせるスクリーン上の座標。GUIスケールも考慮される。
|
||||
---@param velocity Vector2 パーティクルの秒間移動距離(ピクセル)
|
||||
---@param type ExSkillFrameParticleManager.ParticleType このインスタンスのパーティクルの種類
|
||||
---@return ExSkillFrameParticle
|
||||
new = function (parent, pos, velocity, type)
|
||||
---@type ExSkillFrameParticle
|
||||
local instance = Avatar.instantiate(ExSkillFrameParticle, SpawnObject, parent)
|
||||
|
||||
instance.object = models.models.ex_skill_frame.Particles["Particle"..(type == "NORMAL" and 1 or 2)]:copy(instance.uuid)
|
||||
instance.currentPos = pos
|
||||
instance.nextPos = instance.currentPos
|
||||
instance.velocity = velocity
|
||||
instance.particleCount = 0
|
||||
|
||||
instance.callbacks = {
|
||||
---@param self ExSkillFrameParticle
|
||||
onInit = function (self)
|
||||
models.models.ex_skill_frame.Gui.script_ex_skill_frame_particles:addChild(self.object)
|
||||
self.object:setRot(90, math.random() * 360, 180)
|
||||
end;
|
||||
|
||||
---@param self ExSkillFrameParticle
|
||||
onDeinit = function (self)
|
||||
self.object:remove()
|
||||
end;
|
||||
|
||||
---@param self ExSkillFrameParticle
|
||||
onTick = function (self)
|
||||
--パーティクル位置を強制更新
|
||||
self.currentPos = self.nextPos:copy()
|
||||
self.object:setPos(self.currentPos:copy():augmented(0))
|
||||
self.object:setScale(vectors.vec3(1, 1, 1):scale(1 - self.particleCount / 5))
|
||||
|
||||
--カウンターを更新
|
||||
self.particleCount = self.particleCount + 1
|
||||
if self.particleCount == 5 then
|
||||
self.object:remove()
|
||||
elseif self.particleCount == 6 then
|
||||
self.shouldDeinit = true
|
||||
end
|
||||
|
||||
--次ティックの位置を計算
|
||||
if self.velocity:length() > 0 then
|
||||
self.nextPos = self.currentPos:copy():add(self.velocity:copy():scale(-0.05))
|
||||
end
|
||||
end;
|
||||
|
||||
---@param self ExSkillFrameParticle
|
||||
onRender = function (self, delta)
|
||||
self.object:setPos(self.nextPos:copy():sub(self.currentPos):scale(delta):add(self.currentPos):augmented(0))
|
||||
self.object:setScale(vectors.vec3(1, 1, 1):scale(1 - (self.particleCount + delta) / 5))
|
||||
end;
|
||||
}
|
||||
|
||||
return instance
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
---@alias ExSkillFrameParticleManager.ParticleType
|
||||
---| "NORMAL" # 通常のパーティクル
|
||||
---| "FIGURA" # Figuraマークのパーティクル(穴空き三角形)
|
||||
|
||||
---@class (exact) ExSkillFrameParticleManager : SpawnObjectManager Exスキルのフレームで使用するパーティクルを管理するクラス
|
||||
---@field public objects ExSkillFrameParticle[] インスタンスで制御するオブジェクト
|
||||
---@field public getObject fun(self: ExSkillFrameParticleManager, pos: Vector2, velocity: Vector2): ExSkillFrameParticle Exスキルフレームのパーティクルのインスタンスを生成して返す
|
||||
---@field public spawn fun(self: ExSkillFrameParticleManager, pos: Vector2, velocity: Vector2) Exスキルフレームのパーティクルをスポーンさせる
|
||||
|
||||
ExSkillFrameParticleManager = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ExSkillFrameParticleManager
|
||||
new = function (parent)
|
||||
---@type ExSkillFrameParticleManager
|
||||
local instance = Avatar.instantiate(ExSkillFrameParticleManager, SpawnObjectManager, parent)
|
||||
|
||||
instance.managerName = "ex_skill_frame_particle"
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self ExSkillFrameParticleManager
|
||||
init = function (self)
|
||||
SpawnObjectManager.init(self)
|
||||
|
||||
---@diagnostic disable-next-line: discard-returns
|
||||
models.models.ex_skill_frame.Gui:newPart("script_ex_skill_frame_particles")
|
||||
end;
|
||||
|
||||
---Exスキルフレームのパーティクルのインスタンスを生成して返す。
|
||||
---@param self ExSkillFrameParticleManager
|
||||
---@param pos Vector2 パーティクルをスポーンさせる画面上の座標
|
||||
---@param velocity Vector2 パーティクルの速度
|
||||
---@return ExSkillFrameParticle instance 生成したインスタンス
|
||||
getObject = function (self, pos, velocity)
|
||||
return ExSkillFrameParticle.new(self.parent, pos, velocity, math.random() > 0.9999 and "FIGURA" or "NORMAL")
|
||||
end;
|
||||
|
||||
---Exスキルフレームのパーティクルをスポーンさせる。
|
||||
---@param self ExSkillFrameParticleManager
|
||||
---@param pos Vector2 パーティクルをスポーンさせる画面上の座標
|
||||
---@param velocity Vector2 パーティクルの速度
|
||||
spawn = function (self, pos, velocity)
|
||||
SpawnObjectManager.spawn(self, pos, velocity)
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
---@class (exact) FaceParts : AvatarModule 目と口のテクスチャを管理するクラス
|
||||
---@field package emotionCount integer エモートの時間を計るカウンター
|
||||
---@field public blinkCount integer 瞬きのタイミングを計るカウンター
|
||||
---@field public setEmotion fun(self: FaceParts, rightEye: BlueArchiveCharacter.RightEyeTextures, leftEye: BlueArchiveCharacter.LeftEyeTextures, mouth: BlueArchiveCharacter.MouthTextures, duration: integer, forced?: boolean) 表情を設定する
|
||||
---@field public resetEmotion fun(self: FaceParts) 表情をリセットする
|
||||
|
||||
FaceParts = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return FaceParts
|
||||
new = function (parent)
|
||||
---@type FaceParts
|
||||
local instance = Avatar.instantiate(FaceParts, AvatarModule, parent)
|
||||
|
||||
instance.emotionCount = 0
|
||||
instance.blinkCount = 200
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self FaceParts
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
if self.parent.playerUtils.damageStatus == "DAMAGE" then
|
||||
if self.parent.characterData.faceParts.emotionSet ~= nil and self.parent.characterData.faceParts.emotionSet.onDamage ~= nil then
|
||||
self:setEmotion(self.parent.characterData.faceParts.emotionSet.onDamage.rightEye, self.parent.characterData.faceParts.emotionSet.onDamage.leftEye, self.parent.characterData.faceParts.emotionSet.onDamage.mouth, 8, true)
|
||||
else
|
||||
self:setEmotion("SURPRISED", "SURPRISED", "NORMAL", 8, true)
|
||||
end
|
||||
elseif self.parent.playerUtils.damageStatus == "DIED" then
|
||||
if self.parent.characterData.faceParts.emotionSet ~= nil and self.parent.characterData.faceParts.emotionSet.onDied ~= nil then
|
||||
self:setEmotion(self.parent.characterData.faceParts.emotionSet.onDied.rightEye, self.parent.characterData.faceParts.emotionSet.onDied.leftEye, self.parent.characterData.faceParts.emotionSet.onDied.mouth, 20, true)
|
||||
else
|
||||
self:setEmotion("SURPRISED", "SURPRISED", "NORMAL", 20, true)
|
||||
end
|
||||
elseif self.emotionCount == 0 then
|
||||
self:setEmotion("NORMAL", "NORMAL", "NORMAL", 0)
|
||||
end
|
||||
|
||||
if self.blinkCount == 0 then
|
||||
self:setEmotion("CLOSED", "CLOSED", "NORMAL", 2)
|
||||
self.blinkCount = 200
|
||||
else
|
||||
self.blinkCount = self.blinkCount - 1
|
||||
end
|
||||
|
||||
self.emotionCount = self.emotionCount > 0 and self.emotionCount - 1 or self.emotionCount
|
||||
end
|
||||
end)
|
||||
end;
|
||||
|
||||
---表情を設定する。
|
||||
---@param self FaceParts
|
||||
---@param rightEye BlueArchiveCharacter.RightEyeTextures 設定する右目の名前
|
||||
---@param leftEye BlueArchiveCharacter.LeftEyeTextures 設定する左目の名前
|
||||
---@param mouth BlueArchiveCharacter.MouthTextures 設定する口の名前
|
||||
---@param duration integer この表情を有効にする時間
|
||||
---@param forced? boolean trueにすると以前のエモーションが再生中でも強制的に現在のエモーションを適用させる。
|
||||
setEmotion = function (self, rightEye, leftEye, mouth, duration, forced)
|
||||
if self.emotionCount == 0 or forced then
|
||||
--右目
|
||||
models.models.main.Avatar.Head.FaceParts.Eyes.EyeLeft:setUVPixels(self.parent.characterData.faceParts.rightEye[rightEye]:copy():scale(6))
|
||||
|
||||
--左目
|
||||
models.models.main.Avatar.Head.FaceParts.Eyes.EyeRight:setUVPixels(self.parent.characterData.faceParts.leftEye[leftEye]:copy():scale(6))
|
||||
|
||||
--口
|
||||
if mouth ~= "NORMAL" then
|
||||
models.models.main.Avatar.Head.FaceParts.Mouth:setVisible(true)
|
||||
models.models.main.Avatar.Head.FaceParts.Mouth:setUVPixels(self.parent.characterData.faceParts.mouth[mouth]:copy():mul(16, 8))
|
||||
else
|
||||
models.models.main.Avatar.Head.FaceParts.Mouth:setVisible(false)
|
||||
end
|
||||
|
||||
self.emotionCount = duration
|
||||
end
|
||||
end;
|
||||
|
||||
---表情をリセットする。
|
||||
---@param self FaceParts
|
||||
resetEmotion = function (self)
|
||||
self.emotionCount = 0
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
---@alias Gun.GunPosition
|
||||
---| "NONE" # 構えていない
|
||||
---| "RIGHT" # 右手に構える
|
||||
---| "LEFT" # 左手に構える
|
||||
|
||||
---@alias Gun.HandDirection
|
||||
---| "RIGHT" # 右手
|
||||
---| "LEFT" # 左手
|
||||
|
||||
---@class (exact) Gun : AvatarModule 生徒の銃を制御するクラス
|
||||
---@field public gunItems Minecraft.itemID[] 銃のモデルを適用するアイテム
|
||||
---@field public shouldShowWeaponInFirstPerson boolean 一人称で武器(銃を含む)のモデルを表示するかどうか
|
||||
---@field public currentGunPosition Gun.GunPosition 現在の銃の位置
|
||||
---@field package heldItemsPrev Gun.HeldItemSet 前ティックの手持ちアイテム
|
||||
---@field public isLeftHandedPrev boolean 前ティックに左利きだったかどうか
|
||||
---@field public isGunTickProcessed boolean このティック内で銃ティックを処理したかどうか
|
||||
---@field package setBodyGunPos fun(self: Gun) 背中の銃の位置・向きを設定する
|
||||
---@field public setGunPosition fun(self: Gun, gonPosition: Gun.GunPosition) 銃の位置を変更する
|
||||
---@field public processGunTick fun(self: Gun) 銃ティックを処理する
|
||||
|
||||
---@class (exact) Gun.HeldItemSet 手持ちアイテムを示すデータセット
|
||||
---@field public mainHand ItemStack メインハンド
|
||||
---@field public offHand ItemStack オフハンド
|
||||
|
||||
Gun = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Gun
|
||||
new = function (parent)
|
||||
---@type Gun
|
||||
local instance = Avatar.instantiate(Gun, AvatarModule, parent)
|
||||
|
||||
instance.gunItems = {"minecraft:bow", "minecraft:crossbow"}
|
||||
instance.shouldShowWeaponInFirstPerson = instance.parent.config:loadConfig("PRIVATE", "firstPersonWeapon", true)
|
||||
instance.currentGunPosition = "NONE"
|
||||
instance.heldItemsPrev = {
|
||||
mainHand = world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air"));
|
||||
offHand = world.newItem(instance.parent.compatibilityUtils:checkItem("minecraft:air"));
|
||||
}
|
||||
instance.isLeftHandedPrev = player:isLeftHanded()
|
||||
instance.isGunTickProcessed = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Gun
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
self:processGunTick()
|
||||
self.isGunTickProcessed = false
|
||||
end)
|
||||
|
||||
events.ON_PLAY_SOUND:register(function (id, pos, _, _, _, _, path)
|
||||
if path ~= nil then
|
||||
local velocityDistance = player:getVelocity():length()
|
||||
local distanceFromSound = math.abs(pos:copy():sub(player:getPos()):length() - velocityDistance)
|
||||
if (id == "minecraft:entity.arrow.shoot" or id == "minecraft:item.crossbow.loading_end" or id == "minecraft:item.crossbow.shoot") and math.abs(velocityDistance - distanceFromSound) < 1 then
|
||||
if id == "minecraft:item.crossbow.loading_end" then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.dispenser.fail"), pos, 1, 2)
|
||||
elseif player:isUnderwater() then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.generic.extinguish_fire"), pos, 0.5, 1.5)
|
||||
else
|
||||
local particleAnchor = self.parent.modelUtils.getModelWorldPos(renderer:isFirstPerson() and (Gun.CurrentGunPosition == "RIGHT" and models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.RightItemPivot or models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.LeftItemPivot) or models.models.main.Avatar.UpperBody.Body.Gun.MuzzleAnchor)
|
||||
for _ = 1, 5 do
|
||||
particles:newParticle(self.parent.compatibilityUtils:checkParticle("minecraft:smoke"), particleAnchor)
|
||||
end
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound(self.parent.characterData.gun.sound.name), pos, 1, self.parent.characterData.gun.sound.pitch)
|
||||
end
|
||||
return true
|
||||
elseif (id == "minecraft:item.crossbow.loading_start" or id == "minecraft:item.crossbow.loading_middle" or id:match("^minecraft:item%.crossbow%.quick_charge_[1-3]$") ~= nil) and distanceFromSound < 1 and player:getActiveItem().id == "minecraft:crossbow" then
|
||||
local activeItemTime = player:getActiveItemTime()
|
||||
local quickChargeLevel = 0
|
||||
local activeItem = player:getActiveItem()
|
||||
if activeItem.tag.Enchantments ~= nil then
|
||||
for _, enchant in ipairs(activeItem.tag.Enchantments) do
|
||||
if enchant.id == "minecraft:quick_charge" then
|
||||
quickChargeLevel = enchant.lvl
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if (quickChargeLevel <= 4 and activeItemTime + quickChargeLevel >= 4 and activeItemTime + quickChargeLevel <= 6) or (quickChargeLevel == 5 and activeItemTime <= 2) then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:item.flintandsteel.use"), pos, 1, 2)
|
||||
return true
|
||||
elseif id == "minecraft:item.crossbow.loading_middle" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local this = self --Figuraにスクリプトを再構築させると参照がおかしくなることに対処しているコード
|
||||
events.ITEM_RENDER:register(function (item, mode, _, _, _, leftHanded)
|
||||
self = this
|
||||
if mode ~= "HEAD" and self.currentGunPosition == (leftHanded and "LEFT" or "RIGHT") and (self.shouldShowWeaponInFirstPerson or mode =="THIRD_PERSON_LEFT_HAND" or mode == "THIRD_PERSON_RIGHT_HAND") then
|
||||
for _, gunItem in ipairs(self.gunItems) do
|
||||
if item.id == gunItem then
|
||||
if leftHanded then
|
||||
if mode == "FIRST_PERSON_LEFT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonPos.left ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.firstPersonPos.left
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonRot.left ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.firstPersonRot.left
|
||||
end
|
||||
local activeItemId = player:getActiveItem().id
|
||||
if activeItemId == "minecraft:bow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -2.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(20, -7.5, -5):add(offsetRot))
|
||||
elseif activeItemId == "minecraft:crossbow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, 0.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
elseif item.id == "minecraft:crossbow" and item.tag.Charged == 1 then
|
||||
if player:isLeftHanded() then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(-10, -1.25, 6):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 10, 0):add(offsetRot))
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
elseif mode == "THIRD_PERSON_LEFT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.left ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.left
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.left ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.left
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -4.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
else
|
||||
if mode == "FIRST_PERSON_RIGHT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonPos.right ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.firstPersonPos.right
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonRot.right ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.firstPersonRot.right
|
||||
end
|
||||
local activeItemId = player:getActiveItem().id
|
||||
if activeItemId == "minecraft:bow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -2.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(20, 7.5, 5):add(offsetRot))
|
||||
elseif activeItemId == "minecraft:crossbow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, 0.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
elseif item.id == "minecraft:crossbow" and item.tag.Charged == 1 then
|
||||
if player:isLeftHanded() then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(10, -1.25, 6):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, -10, 0):add(offsetRot))
|
||||
end
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
elseif mode == "THIRD_PERSON_RIGHT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.right ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.right
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.right ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.right
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -4.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
end
|
||||
return models.models.main.Avatar.UpperBody.Body.Gun
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setScale(vectors.vec3(1, 1, 1):scale(self.parent.characterData.gun.scale))
|
||||
self:setGunPosition("NONE")
|
||||
if self.parent.characterData.gun.callbacks ~= nil and self.parent.characterData.gun.callbacks.onMainHandChange ~= nil then
|
||||
self.parent.characterData.gun.callbacks.onMainHandChange(self.parent.characterData, self.isLeftHandedPrev and "LEFT" or "RIGHT")
|
||||
end
|
||||
end;
|
||||
|
||||
---背中の銃の位置・向きを設定する。
|
||||
---@param self Gun
|
||||
setBodyGunPos = function (self)
|
||||
if models.models.main.Avatar.UpperBody.Body.Gun ~= nil then
|
||||
local offsetPos = vectors.vec3()
|
||||
local offsetRot = vectors.vec3()
|
||||
if player:isLeftHanded() then
|
||||
if self.parent.characterData.gun.gunPosition.put.pos ~= nil and self.parent.characterData.gun.gunPosition.put.pos.left ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.put.pos.left
|
||||
end
|
||||
if self.parent.characterData.gun.gunPosition.put.rot ~= nil and self.parent.characterData.gun.gunPosition.put.rot.left ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.put.rot.left
|
||||
end
|
||||
else
|
||||
if self.parent.characterData.gun.gunPosition.put.pos ~= nil and self.parent.characterData.gun.gunPosition.put.pos.right ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.put.pos.right
|
||||
end
|
||||
if self.parent.characterData.gun.gunPosition.put.rot ~= nil and self.parent.characterData.gun.gunPosition.put.rot.right ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.put.rot.right
|
||||
end
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, 12, 0):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(offsetRot)
|
||||
end
|
||||
end;
|
||||
|
||||
---銃の位置を変更する。
|
||||
---@param self Gun
|
||||
---@param gunPosition Gun.GunPosition 変更先の構え位置
|
||||
setGunPosition = function (self, gunPosition)
|
||||
if gunPosition == "NONE" then
|
||||
for _, tickName in ipairs({"right_gun_tick", "left_gun_tick"}) do
|
||||
events.TICK:remove(tickName)
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setParentType("None")
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setSecondaryRenderType("NONE")
|
||||
if self.parent.characterData.gun.gunPosition.hold.type == "NORMAL" then
|
||||
self.parent.arms:setArmState(0, 0)
|
||||
elseif self.parent.characterData.gun.gunPosition.hold.type == "CUSTOM" then
|
||||
for _, animationName in ipairs({"gun_hold_right", "gun_hold_left"}) do
|
||||
animations["models.main"][animationName]:stop()
|
||||
end
|
||||
end
|
||||
if self.parent.characterData.gun.gunPosition.put.type == "BODY" then
|
||||
self:setBodyGunPos()
|
||||
elseif self.parent.characterData.gun.gunPosition.put.type == "HIDDEN" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setVisible(false)
|
||||
end
|
||||
elseif gunPosition == "RIGHT" then
|
||||
events.TICK:remove("left_gun_tick")
|
||||
if events.TICK:getRegisteredCount("right_gun_tick") == 0 then
|
||||
events.TICK:register(function ()
|
||||
local heldItem = player:getHeldItem(player:isLeftHanded())
|
||||
local hasGlint = false
|
||||
for _, gunItem in ipairs(self.gunItems) do
|
||||
if gunItem == heldItem.id and heldItem:hasGlint() then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setSecondaryRenderType("GLINT")
|
||||
hasGlint = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hasGlint then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setSecondaryRenderType("NONE")
|
||||
end
|
||||
end, "right_gun_tick")
|
||||
end
|
||||
if self.parent.characterData.gun.gunPosition.hold.type == "NORMAL" then
|
||||
self.parent.arms:setArmState(1, 2)
|
||||
elseif self.parent.characterData.gun.gunPosition.hold.type == "CUSTOM" then
|
||||
animations["models.main"]["gun_hold_left"]:stop()
|
||||
animations["models.main"]["gun_hold_right"]:play()
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setVisible(true)
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setParentType("Item")
|
||||
if not client:isPaused() then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:item.flintandsteel.use"), player:getPos(), 1, 2)
|
||||
end
|
||||
elseif gunPosition == "LEFT" then
|
||||
events.TICK:remove("right_gun_tick")
|
||||
if events.TICK:getRegisteredCount("left_gun_tick") == 0 then
|
||||
events.TICK:register(function ()
|
||||
local heldItem = player:getHeldItem(not player:isLeftHanded())
|
||||
local hasGlint = false
|
||||
for _, gunItem in ipairs(self.gunItems) do
|
||||
if gunItem == heldItem.id and heldItem:hasGlint() then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setSecondaryRenderType("GLINT")
|
||||
hasGlint = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hasGlint then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setSecondaryRenderType("NONE")
|
||||
end
|
||||
end, "left_gun_tick")
|
||||
end
|
||||
if self.parent.characterData.gun.gunPosition.hold.type == "NORMAL" then
|
||||
self.parent.arms:setArmState(2, 1)
|
||||
elseif self.parent.characterData.gun.gunPosition.hold.type == "CUSTOM" then
|
||||
animations["models.main"]["gun_hold_right"]:stop()
|
||||
animations["models.main"]["gun_hold_left"]:play()
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setVisible(true)
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setParentType("Item")
|
||||
if not client:isPaused() then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:item.flintandsteel.use"), player:getPos(), 1, 2)
|
||||
end
|
||||
end
|
||||
self.currentGunPosition = gunPosition
|
||||
end;
|
||||
|
||||
---銃ティックを処理する。
|
||||
---@param self Gun
|
||||
processGunTick = function (self)
|
||||
if not self.isGunTickProcessed then
|
||||
local heldItems = {}
|
||||
if player:getPose() ~= "SLEEPING" and self.parent.exSkill.animationCount == -1 then
|
||||
heldItems.mainHand = player:getHeldItem(false)
|
||||
heldItems.offHand = player:getHeldItem(true)
|
||||
else
|
||||
heldItems.mainHand = world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air"))
|
||||
heldItems.offHand = world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air"))
|
||||
end
|
||||
local targetItemFound = false
|
||||
local isLeftHanded = player:isLeftHanded()
|
||||
if heldItems.mainHand.id ~= self.heldItemsPrev.mainHand.id or heldItems.offHand.id ~= self.heldItemsPrev.offHand.id or isLeftHanded ~= self.isLeftHandedPrev then
|
||||
for _, gunItem in ipairs(self.gunItems) do
|
||||
if heldItems.mainHand.id == gunItem then
|
||||
--メインハンドに対象アイテムを持つ
|
||||
targetItemFound = true
|
||||
if isLeftHanded then
|
||||
if self.currentGunPosition ~= "LEFT" then
|
||||
self:setGunPosition("LEFT")
|
||||
end
|
||||
else
|
||||
if self.currentGunPosition ~= "RIGHT" then
|
||||
self:setGunPosition("RIGHT")
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
if not targetItemFound then
|
||||
for _, gunItem in ipairs(self.gunItems) do
|
||||
if heldItems.offHand.id == gunItem then
|
||||
--オフハンドに対象アイテムを持つ
|
||||
targetItemFound = true
|
||||
if isLeftHanded then
|
||||
if self.currentGunPosition ~= "RIGHT" then
|
||||
self:setGunPosition("RIGHT")
|
||||
end
|
||||
else
|
||||
if self.currentGunPosition ~= "LEFT" then
|
||||
self:setGunPosition("LEFT")
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not targetItemFound then
|
||||
--対象アイテムは持たない
|
||||
if self.currentGunPosition ~= "NONE" then
|
||||
self:setGunPosition("NONE")
|
||||
end
|
||||
end
|
||||
if isLeftHanded ~= self.leftHandedPrev then
|
||||
if self.currentGunPosition == "NONE" then
|
||||
self:setBodyGunPos()
|
||||
end
|
||||
if self.parent.characterData.gun.callbacks ~= nil and self.parent.characterData.gun.callbacks.onMainHandChange ~= nil then
|
||||
self.parent.characterData.gun.callbacks.onMainHandChange(self.parent.characterData, isLeftHanded and "LEFT" or "RIGHT")
|
||||
end
|
||||
self.isLeftHandedPrev = isLeftHanded
|
||||
end
|
||||
self.heldItemsPrev = heldItems
|
||||
end
|
||||
self.isGunTickProcessed = true
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
---@class (exact) HeadBlock : HeadModelGenerator 頭ブロックのモデルを制御するクラス
|
||||
---@field package forceGenerateCount integer 強制的に頭ブロックを生成するまでのカウンター。これが発火するのはアバタープレイヤーがオフラインの時のみ。
|
||||
|
||||
HeadBlock = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return HeadBlock
|
||||
new = function (parent)
|
||||
---@type HeadBlock
|
||||
local instance = Avatar.instantiate(HeadBlock, HeadModelGenerator, parent)
|
||||
|
||||
instance.processData = instance.parent.characterData.headBlock
|
||||
instance.parentName = "head_block"
|
||||
instance.parentType = "Skull"
|
||||
instance.forceGenerateCount = 2
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self HeadBlock
|
||||
init = function (self)
|
||||
HeadModelGenerator.init(self)
|
||||
|
||||
events.WORLD_TICK:register(function ()
|
||||
self.forceGenerateCount = self.forceGenerateCount - 1
|
||||
if self.forceGenerateCount == 0 then
|
||||
self:generateHeadModel()
|
||||
events.WORLD_TICK:remove("head_block_world_tick")
|
||||
end
|
||||
end, "head_block_world_tick")
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
---@class (exact) HeadModelGenerator : AvatarModule 頭のモデルの生成を含む抽象クラス
|
||||
---@field public processData BlueArchiveCharacter.HeadBlockStruct 頭モデルを生成する過程で参照するデータ
|
||||
---@field public parentName string モデルを生成する名前空間
|
||||
---@field public parentType ModelPart.parentType コピーした頭モデルに適用する親タイプ
|
||||
---@field public generateHeadModel fun(self: HeadModelGenerator) 頭モデルのコピーを生成する
|
||||
---@field package isScriptLoaded boolean スクリプトが全てロードされているかどうか
|
||||
|
||||
HeadModelGenerator = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return HeadModelGenerator
|
||||
new = function (parent)
|
||||
---@type HeadModelGenerator
|
||||
local instance = Avatar.instantiate(HeadModelGenerator, AvatarModule, parent)
|
||||
|
||||
instance.isScriptLoaded = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self HeadModelGenerator
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
---@diagnostic disable-next-line: discard-returns
|
||||
models:newPart("script_"..self.parentName, "None")
|
||||
|
||||
self.parent.avatarEvents.SCRIPT_INIT:register(function ()
|
||||
self.isScriptLoaded = true
|
||||
end)
|
||||
end;
|
||||
|
||||
---頭モデルのコピーを生成する。
|
||||
---@param self HeadModelGenerator
|
||||
generateHeadModel = function (self)
|
||||
--既存の頭ブロックのモデルを削除する。
|
||||
if models["script_"..self.parentName].Head ~= nil then
|
||||
models["script_"..self.parentName].Head:remove()
|
||||
end
|
||||
|
||||
--ヘルメットを着けている場合は外しておく。
|
||||
local isHelmetVisible = self.isScriptLoaded and self.parent.armor.isArmorVisible.helmet or false
|
||||
if isHelmetVisible then
|
||||
self.parent.armor:setHelmet(world.newItem(self.parent.compatibilityUtils:checkItem("minecraft:air")))
|
||||
end
|
||||
|
||||
if self.isScriptLoaded then
|
||||
self.parent.physics:disable()
|
||||
end
|
||||
if self.processData.callbacks ~= nil and self.processData.callbacks.onBeforeModelCopy ~= nil then
|
||||
self.processData.callbacks.onBeforeModelCopy(self.parent.characterData, self.isScriptLoaded)
|
||||
end
|
||||
|
||||
--現在の衣装を基に新たな頭ブロックのモデルを生成する。
|
||||
local copiedPart = self.parent.modelUtils:copyModel(models.models.main.Avatar.Head)
|
||||
if copiedPart ~= nil then
|
||||
models["script_"..self.parentName]:addChild(copiedPart)
|
||||
models["script_"..self.parentName].Head:setParentType(self.parentType)
|
||||
models["script_"..self.parentName].Head:setPos(0, -24, 0)
|
||||
models["script_"..self.parentName].Head.HeadRing:setRot(self.parent.headRing.initialHaloRot)
|
||||
models["script_"..self.parentName].Head.HeadRing:setLight(15)
|
||||
for _, modelPart in ipairs({models["script_"..self.parentName].Head.FaceParts.Eyes.EyeRight, models["script_"..self.parentName].Head.FaceParts.Eyes.EyeLeft}) do
|
||||
modelPart:setUVPixels()
|
||||
end
|
||||
for _, modelPart in ipairs(self.processData.includeModels) do
|
||||
local copiedIncludePart = self.parent.modelUtils:copyModel(modelPart)
|
||||
if copiedIncludePart ~= nil then
|
||||
models["script_"..self.parentName].Head:addChild(copiedIncludePart)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--非表示にしたモデルを元に戻す。
|
||||
if isHelmetVisible then
|
||||
self.parent.armor:setHelmet(self.parent.armor.armorSlotItems[1])
|
||||
end
|
||||
if self.isScriptLoaded then
|
||||
self.parent.physics:enable()
|
||||
end
|
||||
if self.processData.callbacks ~= nil and self.processData.callbacks.onAfterModelCopy ~= nil then
|
||||
self.processData.callbacks.onAfterModelCopy(self.parent.characterData, self.isScriptLoaded)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
---@class (exact) HeadRing : AvatarModule ヘイロー(頭の輪っか)を制御するクラス
|
||||
---@field public initialHaloRot number ヘイローの初期角度
|
||||
---@field package headRotData number[] 一定期間内の頭の角度を保持するテーブル
|
||||
---@field package headRotAverage number[] 頭の角度の移動平均値
|
||||
---@field package floatCount integer ヘイローが上下するアニメーションのカウンター
|
||||
|
||||
HeadRing = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return HeadRing
|
||||
new = function (parent)
|
||||
---@type HeadRing
|
||||
local instance = Avatar.instantiate(HeadRing, AvatarModule, parent)
|
||||
|
||||
instance.initialHaloRot = models.models.main.Avatar.Head.HeadRing:getRot().x
|
||||
instance.haloRotPrev = instance.initialHaloRot
|
||||
instance.headRotData = {}
|
||||
instance.headRotAverage = {0, 0}
|
||||
instance.floatCount = 0
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self HeadRing
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
--移動平均値の算出
|
||||
local headRot = self.parent.exSkill.animationCount > -1 and models.models.main.Avatar.Head:getAnimRot().x or math.deg(math.asin(player:getLookDir().y))
|
||||
local headRotAverage = self.headRotAverage[2]
|
||||
headRotAverage = (#self.headRotData * headRotAverage + headRot) / (#self.headRotData + 1)
|
||||
table.insert(self.headRotData, headRot)
|
||||
--古いデータの切り捨て
|
||||
if #self.headRotData > 5 then
|
||||
headRotAverage = (#self.headRotData * headRotAverage - self.headRotData[1]) / (#self.headRotData - 1)
|
||||
table.remove(self.headRotData, 1)
|
||||
end
|
||||
table.insert(self.headRotAverage, headRotAverage)
|
||||
table.remove(self.headRotAverage, 1)
|
||||
end
|
||||
end)
|
||||
|
||||
events.WORLD_TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
self.floatCount = self.floatCount + 1
|
||||
self.floatCount = self.floatCount == 80 and 0 or self.floatCount
|
||||
end
|
||||
end)
|
||||
|
||||
events.RENDER:register(function (delta)
|
||||
if not client:isPaused() then
|
||||
--ヘイローの位置・角度を設定
|
||||
local playerPose = player:getPose()
|
||||
local headRot = self.parent.physics.getValueBetweenTicks(self.headRotAverage, delta)
|
||||
local floatOffset = math.sin((self.floatCount + delta) / 80 * 2 * math.pi) * 0.25
|
||||
if playerPose == "SWIMMING" or playerPose == "FALL_FLYING" then
|
||||
models.models.main.Avatar.Head.HeadRing:setPos(self.parent.physics.velocityAverage[3][2] * 3, math.cos(math.rad(headRot)) * self.parent.physics.velocityAverage[1][2] * -1 + math.sin(math.rad(headRot)) * self.parent.physics.velocityAverage[2][2] * -1 + floatOffset, math.cos(math.rad(headRot)) * self.parent.physics.velocityAverage[2][2] * -3 + math.sin(math.rad(headRot)) * self.parent.physics.velocityAverage[1][2])
|
||||
else
|
||||
models.models.main.Avatar.Head.HeadRing:setPos(self.parent.physics.velocityAverage[3][2] * -3, math.cos(math.rad(headRot)) * self.parent.physics.velocityAverage[2][2] * -1 + math.sin(math.rad(headRot)) * self.parent.physics.velocityAverage[1][2] + floatOffset, math.cos(math.rad(headRot)) * self.parent.physics.velocityAverage[1][2] * 3 + math.sin(math.rad(headRot)) * self.parent.physics.velocityAverage[2][2])
|
||||
end
|
||||
if self.parent.deathAnimation.dummyAvatarRoot ~= nil then
|
||||
self.parent.deathAnimation.dummyAvatarRoot.Head.HeadRing:setPos(0, floatOffset, 0)
|
||||
end
|
||||
models.models.main.Avatar.Head.HeadRing:setRot(headRot - (self.parent.exSkill.animationCount > -1 and models.models.main.Avatar.Head:getAnimRot().x or math.deg(math.asin(player:getLookDir().y))) + self.initialHaloRot, 0, 0)
|
||||
end
|
||||
end)
|
||||
|
||||
events.WORLD_RENDER:register(function (delta)
|
||||
if not client:isPaused() and models.script_head_block.Head ~= nil and models.script_head_block.Head.HeadRing ~= nil then
|
||||
models.script_head_block.Head.HeadRing:setPos(0, math.sin((self.floatCount + delta) / 80 * 2 * math.pi) * 0.25, 0)
|
||||
end
|
||||
end)
|
||||
|
||||
models.models.main.Avatar.Head.HeadRing:setLight(15)
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
---@class (exact) HypixelZombies : AvatarModule Hypixelの「Zombies」にアバターを対応させるパッチ
|
||||
---@field package isEnabled boolean Zombiesモードが有効かどうか
|
||||
---@field package damagerPercentPrev number[] 前ティック以前のツールの耐久度割合
|
||||
---@field public enable fun(self: HypixelZombies) Zombiesモードを有効にする
|
||||
---@field public disable fun(self: HypixelZombies) Zombiesモードを無効にする
|
||||
|
||||
HypixelZombies = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return HypixelZombies
|
||||
new = function (parent)
|
||||
---@type HypixelZombies
|
||||
local instance = Avatar.instantiate(HypixelZombies, AvatarModule, parent)
|
||||
|
||||
instance.isEnabled = false
|
||||
instance.damagerPercentPrev = {1, 1}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---Zombiesモードを有効にする。
|
||||
---@param self HypixelZombies
|
||||
enable = function (self)
|
||||
if not self.isEnabled then
|
||||
self.parent.gun.gunItems = {"minecraft:bow", "minecraft:crossbow", "minecraft:wooden_hoe", "minecraft:stone_hoe", "minecraft:iron_hoe", "minecraft:wooden_shovel", "minecraft:stone_shovel", "minecraft:shears", "minecraft:diamond_hoe", "minecraft:golden_hoe", "minecraft:iron_shovel", "minecraft:diamond_pickaxe", "minecraft:golden_pickaxe", "minecraft:golden_shovel", "minecraft:flint_and_steel"}
|
||||
|
||||
events.TICK:register(function ()
|
||||
local heldItem = player:getHeldItem()
|
||||
local maxDamage = heldItem:getMaxDamage()
|
||||
local damagePercent = (maxDamage - heldItem:getDamage()) / maxDamage
|
||||
if maxDamage > 0 then
|
||||
if damagePercent - self.damagerPercentPrev[1] > 0 and damagePercent - self.damagerPercentPrev[1] <= 0.2 then
|
||||
if self.parent.bubble.bubbleCount == 0 then
|
||||
self.parent.bubble:play("RELOAD", -1, vectors.vec2(), 0, false)
|
||||
end
|
||||
elseif self.parent.bubble.emoji == "RELOAD" then
|
||||
self.parent.bubble:stop()
|
||||
end
|
||||
table.insert(self.damagerPercentPrev, damagePercent)
|
||||
else
|
||||
if self.parent.bubble.emoji == "RELOAD" then
|
||||
self.parent.bubble:stop()
|
||||
end
|
||||
table.insert(self.damagerPercentPrev, 1)
|
||||
end
|
||||
table.remove(self.damagerPercentPrev, 1)
|
||||
end, "hypixel_zombies_tick")
|
||||
|
||||
if host:isHost() then
|
||||
print(self.parent.locale:getLocale("avatar.zombies_mode.enable"))
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.lightning_bolt.thunder"), player:getPos(), 0.5, 2)
|
||||
end
|
||||
|
||||
self.isEnabled = true
|
||||
elseif host:isHost() then
|
||||
print(self.parent.locale:getLocale("avatar.zombies_mode.already_enabled"))
|
||||
end
|
||||
end;
|
||||
|
||||
---Zombiesモードを無効にする。
|
||||
---@param self HypixelZombies
|
||||
disable = function (self)
|
||||
if self.isEnabled then
|
||||
events.TICK:remove("hypixel_zombies_tick")
|
||||
self.parent.gun.gunItems = {"minecraft:bow", "minecraft:crossbow"}
|
||||
|
||||
if host:isHost() then
|
||||
print(self.parent.locale:getLocale("avatar.zombies_mode.disable"))
|
||||
end
|
||||
elseif host:isHost() then
|
||||
print(self.parent.locale:getLocale("avatar.zombies_mode.already_disabled"))
|
||||
end
|
||||
self.isEnabled = false
|
||||
end
|
||||
}
|
||||
|
||||
---Zombiesモードを有効にする。
|
||||
function pings.enableZombiesMode()
|
||||
AvatarInstance.hypixelZombies:enable()
|
||||
end
|
||||
|
||||
---Zombiesモードを無効にする。
|
||||
function pings.disableZombiesMode()
|
||||
AvatarInstance.hypixelZombies:disable()
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
---@class (exact) KeyManager : AvatarModule アバターのキー割り当てを管理するクラス。ここで管理する割り当ては設定で変更された場合はそれが保存される。
|
||||
---@field public keyMappings {[string]: Keybind} キーの割り当てのテーブル
|
||||
---@field public register fun(self: KeyManager, assignName: string, keyName: Minecraft.keyCode): Keybind キー割り当てを登録する
|
||||
|
||||
KeyManager = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return KeyManager
|
||||
new = function (parent)
|
||||
---@type KeyManager
|
||||
local instance = Avatar.instantiate(KeyManager, AvatarModule, parent)
|
||||
|
||||
instance.keyMappings = {}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self KeyManager
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if host:isHost() then
|
||||
events.TICK:register(function ()
|
||||
for key, value in pairs(self.keyMappings) do
|
||||
if not value:isDefault() then
|
||||
local newKey = value:getKey()
|
||||
self.parent.config:saveConfig("PRIVATE", "keybind."..key, newKey)
|
||||
value:setKey(newKey)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end;
|
||||
|
||||
---キー割り当てを登録する。
|
||||
---@param self KeyManager
|
||||
---@param assignName string 割り当ての名前
|
||||
---@param keyName Minecraft.keyCode 割当先のキー
|
||||
---@return Keybind assignedKey キーマネージャーによって登録がされたキーバインド
|
||||
register = function (self, assignName, keyName)
|
||||
self.keyMappings[assignName] = keybinds:newKeybind(self.parent.locale:getLocale("key_name."..assignName), self.parent.config:loadConfig("PRIVATE", "keybind."..assignName, keyName))
|
||||
return self.keyMappings[assignName]
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
---@alias Locale.Language
|
||||
---| "en_us" # 英語(米国)
|
||||
---| "ja_jp" # 日本語
|
||||
|
||||
---@class (exact) Locale : AvatarModule アバターの表示言語を管理するクラス
|
||||
---@field public localeData {[Locale.Language]: {[string]: string}} 言語データ
|
||||
---@field public getLocale fun(self: Locale, key: string): string 翻訳キーに対する訳文を返す。設定言語が存在しない場合は英語の文が返される。また、指定したキーの訳が無い場合は英語->キーそのままが返される。
|
||||
|
||||
Locale = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Locale
|
||||
new = function (parent)
|
||||
---@type Locale
|
||||
local instance = Avatar.instantiate(Locale, AvatarModule, parent)
|
||||
|
||||
instance.localeData = {
|
||||
en_us = {};
|
||||
ja_jp = {};
|
||||
}
|
||||
|
||||
---言語データのリスト
|
||||
---1. キー名, 2. 英語(米国), 3. 日本語
|
||||
local localeStrings = {
|
||||
{"avatar.old_version_warning", "For the best experience, playing with 1.20.1 or higher is recommended!", "生徒さんとより良い時間を過ごすためにバージョン1.20.1以上でのプレイをおすすめします!"};
|
||||
{"avatar.zombies_mode.enable", "Enabled Zombies mode.", "Zombiesモードを有効にしました。"};
|
||||
{"avatar.zombies_mode.already_enabled", "Already enabled Zombies mode.", "Zombiesモードは既に有効化されています。"};
|
||||
{"avatar.zombies_mode.disable", "Disabled Zombies mode.", "Zombiesモードを無効にしました。"};
|
||||
{"avatar.zombies_mode.already_disabled", "Already disabled Zombies mode.", "Zombiesモードは既に無効化されています。"};
|
||||
{"action_wheel.toggle_off", "off", "オフ"};
|
||||
{"action_wheel.toggle_on", "on", "オン"};
|
||||
{"action_wheel.main.action_1.title", "Change costume: ", "衣装を変更:"};
|
||||
{"action_wheel.main.action_1.unavailable", "There is no costume available.", "利用可能な衣装はありません。"};
|
||||
{"action_wheel.main.action_1.done_first", "Changed costume to §b", "衣装を§b"};
|
||||
{"action_wheel.main.action_1.done_last", "§r.", "§rに変更しました。"};
|
||||
{"action_wheel.main.action_2.title", "Change display name: ", "表示名を変更:"};
|
||||
{"action_wheel.main.action_2.title_2", "Show club name: ", "部活名を表示:"};
|
||||
{"action_wheel.main.action_2.done_first", "Changed display name to §b", "表示名を§b"};
|
||||
{"action_wheel.main.action_2.done_last", "§r.", "§rに変更しました。"};
|
||||
{"action_wheel.main.action_3.title", "Show armors: ", "防具を表示:"};
|
||||
{"action_wheel.main.action_4.title", "Show weapon models in first person: ", "一人称視点で武器モデルを表示:"};
|
||||
{"action_wheel.main.action_5.title", "Amount of particles in Ex skill frame: ", "Exスキルフレームのパーティクルの量:"};
|
||||
{"action_wheel.main.action_5.option_1", "Standard", "標準"};
|
||||
{"action_wheel.main.action_5.option_2", "Minimum", "少なめ"};
|
||||
{"action_wheel.main.action_5.option_3", "None", "なし"};
|
||||
{"action_wheel.main.action_5.option_4", "Hide Ex skill frame", "スキルフレーム非表示"};
|
||||
{"action_wheel.main.action_5.done_first", "Changed amount of particles in Ex skill frame to§b", "Exスキルフレームのパーティクルの量を§b"};
|
||||
{"action_wheel.main.action_5.done_last", "§r.", "§rに変更しました。"};
|
||||
{"action_wheel.main.action_6.title", "Replace vehicle models: ", "乗り物のモデルを置き換え:"};
|
||||
{"action_wheel.main.action_6.unavailable", "This option is unavailable for this character.", "この生徒さんでは利用できません。"};
|
||||
{"action_wheel.main.action_7.title_1", "Check for new FBAC Updates ", "FBACアップデートの確認"};
|
||||
{"action_wheel.main.action_7.title_2", "(Left-click)", "(左クリック)"};
|
||||
{"action_wheel.main.action_7.title_3", "Copy URL for the latest FBAC ", "最新FBACバージョンのURLをコピー"};
|
||||
{"action_wheel.main.action_7.title_4", "(Right-click)", "(右クリック)"};
|
||||
{"action_wheel.main.action_7.networking_api", "To enable update checking, you need to allow Figura's Networking and put \"api.github.com\" in the Network Filter from Figura settings!", "アップデート確認機能を有効にするにはFiguraの設定より、FiguraのNetworkingの使用を許可し、\"api.github.com\"を許可リストに入れる必要があります!"};
|
||||
{"action_wheel.main.action_7.ongoing", "Checking for updates is ongoing. Please DO NOT click repeatedly!", "現在アップデートの確認中です。連打しないでください!"};
|
||||
{"action_wheel.main.action_7.copied", "Coped the link to the latest FBAC to your clipboard. Please open the link in your browser.", "最新のFBACへのリンクをクリップボードにコピーしました。ブラウザでリンクを開いてください。"};
|
||||
{"action_wheel.main.action_7.cannot_check_latest", "Cannot get the link because cannot check the latest FBAC.", "FBACの最新バージョンを確認できないため、リンクを取得できません。"};
|
||||
{"action_wheel.gui.bubble_guide.title", "§0Bubble emote guide", "§0吹き出しエモートガイド"};
|
||||
{"action_wheel.gui.ex_skill_guide.title", "§0Ex skill guide", "§0Exスキルガイド"};
|
||||
{"action_wheel.gui.ex_skill_guide.key_pre", "Press \"", "\""};
|
||||
{"action_wheel.gui.ex_skill_guide.key_post", "\"key to play", "\"キーで再生"};
|
||||
{"action_wheel.gui.update_check.checking", "Checking for updates...", "アップデートを確認中..."};
|
||||
{"action_wheel.gui.update_check.latest", "No FBAC update available", "最新のFBACを使用中です"};
|
||||
{"action_wheel.gui.update_check.update_available", "New FBAC update is available: ", "FBACのアップデートが利用可能です:"};
|
||||
{"action_wheel.gui.update_check.error_not_allowed", "Failed to check for updates - Networking API not allowed", "アップデート確認失敗 - ネットワーキング機能が不許可"};
|
||||
{"action_wheel.gui.update_check.error_network_err", "Failed to check for updates - Network error", "アップデート確認失敗 - ネットワークエラー"};
|
||||
{"action_wheel.gui.update_check.error_request_failed", "Failed to check for updates - Request failure ", "アップデート確認失敗 - リクエスト失敗 "};
|
||||
{"action_wheel.gui.update_check.error_invalid_json_syntax", "Failed to check for updates - Json parsing failure", "アップデート確認失敗 - リクエスト解析失敗"};
|
||||
{"action_wheel.gui.update_check.error_invalid_json", "Failed to check for updates - Unexpected Request", "アップデート確認失敗 - 予期しないリクエスト"};
|
||||
{"key_name.ex_skill", "Ex Skill", "Exスキル"};
|
||||
{"key_name.ex_skill_sub", "Sub Ex Skill", "サブExスキル"};
|
||||
{"key_name.bubble_1", "Bubble: Good", "吹き出し:いいね"};
|
||||
{"key_name.bubble_2", "Bubble: Heart", "吹き出し:ハート"};
|
||||
{"key_name.bubble_3", "Bubble: Note", "吹き出し:音符"};
|
||||
{"key_name.bubble_4", "Bubble: Question", "吹き出し:はてな"};
|
||||
{"key_name.bubble_5", "Bubble: Sweat", "吹き出し:汗"};
|
||||
{"key_bind.ex_skill.unavailable", "You cannot do this now.", "今は再生できません。"};
|
||||
{"key_bind.ex_skill.unavailable_firstperson", "You cannot do this in first person perspective.", "一人称視点では再生できません。"};
|
||||
}
|
||||
|
||||
for _, localeSet in ipairs(localeStrings) do
|
||||
instance.localeData.en_us[localeSet[1]] = localeSet[2]
|
||||
instance.localeData.ja_jp[localeSet[1]] = localeSet[3]
|
||||
end
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Locale
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
self.localeData.en_us["nameplate.club_name"] = self.parent.characterData.basic.clubName.en_us
|
||||
self.localeData.ja_jp["nameplate.club_name"] = self.parent.characterData.basic.clubName.ja_jp
|
||||
|
||||
if host:isHost() then
|
||||
for index, exSkill in ipairs(self.parent.characterData.exSkill) do
|
||||
self.localeData.en_us["action_wheel.gui.ex_skill_guide.ex_skill_"..index..".name"] = exSkill.name.en_us
|
||||
self.localeData.ja_jp["action_wheel.gui.ex_skill_guide.ex_skill_"..index..".name"] = exSkill.name.ja_jp
|
||||
end
|
||||
for _, costume in ipairs(self.parent.characterData.costume.costumes) do
|
||||
self.localeData.en_us["costume."..costume.name] = costume.displayName.en_us
|
||||
self.localeData.ja_jp["costume."..costume.name] = costume.displayName.ja_jp
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
---翻訳キーに対する訳文を返す。設定言語が存在しない場合は英語の文が返される。また、指定したキーの訳が無い場合は英語->キーそのままが返される。
|
||||
---@param self Locale
|
||||
---@param key string 翻訳キー
|
||||
---@return string translatedString 翻訳キーに対する翻訳データ。設定言語での翻訳が存在しない場合は英文が返される。英文すら存在しない場合は翻訳キーがそのまま返される。
|
||||
getLocale = function (self, key)
|
||||
local activeLanguage = client:getActiveLang()
|
||||
return (self.localeData[activeLanguage] ~= nil and self.localeData[activeLanguage][key] ~= nil) and self.localeData[activeLanguage][key] or (self.localeData.en_us[key] and self.localeData.en_us[key] or key)
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
---@class (exact) Nameplate : AvatarModule プレイヤーの表示名を制御するクラス
|
||||
---@field public currentName integer 現在の表示名:1. プレイヤー名, 2. 名のみ(英語), 3. 名のみ(日本語), 4. 名性(英語), 5. 性名(英語), 6. 性名(日本語)
|
||||
---@field public shouldShowClubName boolean 部活名を表示するかどうか
|
||||
---@field package localePrev string 前ティックの設定言語
|
||||
---@field public getName fun(self: Nameplate, typeId: integer): string 指定されたtypeIdでの表示名を返す
|
||||
---@field public setName fun(self: Nameplate, typeId: integer, shouldShowClubName: boolean) 入力された設定で表示名を設定する
|
||||
|
||||
Nameplate = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Nameplate
|
||||
new = function (parent)
|
||||
---@type Nameplate
|
||||
local instance = Avatar.instantiate(Nameplate, AvatarModule, parent)
|
||||
|
||||
instance.currentName = instance.parent.config:loadConfig("PRIVATE", "name", 1)
|
||||
instance.shouldShowClubName = instance.parent.config:loadConfig("PRIVATE", "showClubName", false)
|
||||
instance.localePrev = client:getActiveLang()
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---指定されたtypeIdでの表示名を返す。
|
||||
---@param self Nameplate
|
||||
---@param typeId integer 表示名の種類:1. プレイヤー名, 2. 名のみ(英語), 3. 名のみ(日本語), 4. 名性(英語), 5. 性名(英語), 6. 性名(日本語)
|
||||
---@return string displayName 指定されたtypeIdでの表示名
|
||||
getName = function (self, typeId)
|
||||
local displayName = typeId == 1 and player:getName() or ((typeId == 2 or typeId == 4) and self.parent.characterData.basic.firstName.en_us or (typeId == 5 and self.parent.characterData.basic.lastName.en_us or (typeId == 3 and self.parent.characterData.basic.firstName.ja_jp or self.parent.characterData.basic.lastName.ja_jp)))
|
||||
if typeId >= 4 then
|
||||
displayName = displayName.." "..(typeId == 4 and self.parent.characterData.basic.lastName.en_us or (typeId == 5 and self.parent.characterData.basic.firstName.en_us or self.parent.characterData.basic.firstName.ja_jp))
|
||||
end
|
||||
return displayName
|
||||
end;
|
||||
|
||||
---入力された設定で表示名を設定する。
|
||||
---@param self Nameplate
|
||||
---@param typeId integer 表示名の種類:1. プレイヤー名, 2. 名のみ(英語), 3. 名のみ(日本語), 4. 名性(英語), 5. 性名(英語), 6. 性名(日本語)
|
||||
---@param shouldShowClubName boolean 部活名を表示するかどうか
|
||||
setName = function (self, typeId, shouldShowClubName)
|
||||
local date = client:getDate()
|
||||
local displayName = self:getName(typeId)..((typeId >= 2 and date.month == self.parent.characterData.basic.birth.month and date.day == self.parent.characterData.basic.birth.day) and " :cake:" or "")
|
||||
nameplate.ALL:setText(displayName)
|
||||
if typeId >= 2 and shouldShowClubName then
|
||||
nameplate.ENTITY:setText(displayName.."\n§7"..self.parent.locale:getLocale("nameplate.club_name"))
|
||||
end
|
||||
self.currentName = typeId
|
||||
self.shouldShowClubName = shouldShowClubName
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Nameplate
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
local locale = client:getActiveLang()
|
||||
if locale ~= self.localePrev then
|
||||
if self.shouldShowClubName then
|
||||
self:setName(self.currentName, true)
|
||||
end
|
||||
self.localePrev = locale
|
||||
end
|
||||
end)
|
||||
|
||||
events.RENDER:register(function (delta, context)
|
||||
if context ~= "PAPERDOLL" then
|
||||
nameplate.ENTITY:setPivot(self.parent.modelUtils.getModelWorldPos(models.models.main.Avatar.UpperBody.Body.NameplateAnchor):sub(player:getPos(delta)):add(0, self.parent.barrier.isBarrierVisible and 1.095 or 0.895, 0))
|
||||
else
|
||||
nameplate.ENTITY:setPivot()
|
||||
end
|
||||
end)
|
||||
|
||||
if self.currentName >= 2 then
|
||||
self:setName(self.currentName, self.shouldShowClubName)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
---@class (exact) Physics : AvatarModule 物理演算を制御するクラス
|
||||
---@field public velocityData number[][] 速度データ:1. 頭前後, 2. 上下, 3. 頭左右, 4. 頭角速度, 5. 体前後, 6. 体左右, 7. 体角速度
|
||||
---@field public velocityAverage number[][] 速度の平均値:1. 頭前後, 2. 上下, 3. 頭左右, 4. 頭角速度, 5. 体前後, 6. 体左右, 7. 体角速度
|
||||
---@field package directionPrev number[] 前ティックのdirectionテーブル
|
||||
---@field public getValueBetweenTicks fun(tickData: number[], delta: number): number 2つのティックデータの間からレンダーのデルタ値を加味した値を返す
|
||||
---@field package decomposeHorizontalVelocity fun(self: Physics, direction: number, index: integer): number, number, number 速度を指定された方向から見て前後方向、左右方向に分解する
|
||||
---@field package getPhysicRot fun(self: Physics, physicData: BlueArchiveCharacter.PhysicCoreData, delta: number): number 物理演算で計算した角度を返す
|
||||
---@field public enable fun(self: Physics) 物理演算を初期化し、有効にする
|
||||
---@field public disable fun(self: Physics) 物理演算を無効にする。物理演算で管理していたモデルの回転をリセットする
|
||||
|
||||
Physics = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Physics
|
||||
new = function (parent)
|
||||
---@type Physics
|
||||
local instance = Avatar.instantiate(Physics, AvatarModule, parent)
|
||||
|
||||
instance.velocityData = {{}, {}, {}, {}, {}, {}, {}}
|
||||
instance.velocityAverage = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}
|
||||
instance.directionPrev = {}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---2つのティックデータの間からレンダーのデルタ値を加味した値を返す。
|
||||
---@param tickData number[] ティックデータ:1. 前ティック, 2. 現ティック
|
||||
---@param delta number デルタ値
|
||||
---@return number deltaValue 2つのティックデータの間からデルタ値で補完した値
|
||||
getValueBetweenTicks = function (tickData, delta)
|
||||
return tickData[1] + (tickData[2] - tickData[1]) * delta
|
||||
end;
|
||||
|
||||
---速度を指定された方向から見て前後方向、左右方向に分解する。
|
||||
---@param self Physics
|
||||
---@param direction number 基準にする方向
|
||||
---@param index integer データ管理用のインデックス番号(呼び出しの度に異なるインデックス番号になるようにする)
|
||||
---@return number velocityFront 指定された方向から見た前後方向の速度
|
||||
---@return number velocityRight 指定された方向から見た左右方向の速度
|
||||
---@return number velocityRot 指定された方向を基準とした角速度
|
||||
decomposeHorizontalVelocity = function (self, direction, index)
|
||||
local velocity = player:getVelocity()
|
||||
if self.directionPrev[index] == nil then
|
||||
self.directionPrev[index] = 0
|
||||
end
|
||||
local velocityRot = math.deg(math.atan2(velocity.z, velocity.x))
|
||||
velocityRot = velocityRot < 0 and 360 + velocityRot or velocityRot
|
||||
local directionAbsFront = math.abs(velocityRot - (direction) % 360)
|
||||
directionAbsFront = directionAbsFront > 180 and 360 - directionAbsFront or directionAbsFront
|
||||
local directionAbsRight = math.abs(velocityRot - (direction + 90) % 360)
|
||||
directionAbsRight = directionAbsRight > 180 and 360 - directionAbsRight or directionAbsRight
|
||||
local directionDelta = direction - self.directionPrev[index]
|
||||
directionDelta = directionDelta > 180 and (360 - directionDelta) * 20 or (directionDelta < -180 and (360 + directionDelta) * 20 or directionDelta * 20)
|
||||
self.directionPrev[index] = direction
|
||||
return math.sqrt(velocity.x ^ 2 + velocity.z ^ 2) * math.cos(math.rad(directionAbsFront)), math.sqrt(velocity.x ^ 2 + velocity.z ^ 2) * math.cos(math.rad(directionAbsRight)), directionDelta
|
||||
end;
|
||||
|
||||
---物理演算で計算した角度を返す。
|
||||
---@param self Physics
|
||||
---@param physicData BlueArchiveCharacter.PhysicCoreData 物理演算データ
|
||||
---@param delta number デルタ値
|
||||
---@return number physicDirection 物理演算で計算したモデルの角度
|
||||
getPhysicRot = function (self, physicData, delta)
|
||||
local rot = physicData.neutral
|
||||
local waterMultiplayer = player:isUnderwater() and 2 or 1
|
||||
if physicData.headX ~= nil then
|
||||
rot = rot + math.clamp(self.getValueBetweenTicks(self.velocityAverage[1], delta) * physicData.headX.multiplayer * waterMultiplayer, physicData.headX.min - physicData.neutral, physicData.headX.max - physicData.neutral)
|
||||
end
|
||||
if physicData.headZ ~= nil then
|
||||
rot = rot + math.clamp(self.getValueBetweenTicks(self.velocityAverage[3], delta) * physicData.headZ.multiplayer * waterMultiplayer, physicData.headZ.min - physicData.neutral, physicData.headZ.max - physicData.neutral)
|
||||
end
|
||||
if physicData.headRot ~= nil then
|
||||
rot = rot + math.clamp(math.abs(self.getValueBetweenTicks(self.velocityAverage[4], delta)) * -1 * physicData.headRot.multiplayer, physicData.headRot.min - physicData.neutral, physicData.headRot.max - physicData.neutral)
|
||||
end
|
||||
if physicData.bodyX ~= nil then
|
||||
rot = rot + math.clamp(self.getValueBetweenTicks(self.velocityAverage[5], delta) * physicData.bodyX.multiplayer * waterMultiplayer, physicData.bodyX.min - physicData.neutral, physicData.bodyX.max - physicData.neutral)
|
||||
end
|
||||
if physicData.bodyY ~= nil then
|
||||
rot = rot + math.clamp(self.getValueBetweenTicks(self.velocityAverage[2], delta) * physicData.bodyY.multiplayer * waterMultiplayer, physicData.bodyY.min - physicData.neutral, physicData.bodyY.max - physicData.neutral)
|
||||
end
|
||||
if physicData.bodyZ ~= nil then
|
||||
rot = rot + math.clamp(self.getValueBetweenTicks(self.velocityAverage[player:getVehicle() == nil and 6 or 3], delta) * physicData.bodyZ.multiplayer * waterMultiplayer, physicData.bodyZ.min - physicData.neutral, physicData.bodyZ.max - physicData.neutral)
|
||||
end
|
||||
if physicData.bodyRot ~= nil then
|
||||
rot = rot + math.clamp(-math.abs(self.getValueBetweenTicks(self.velocityAverage[7], delta)) * physicData.bodyRot.multiplayer, physicData.bodyRot.min - physicData.neutral, physicData.bodyRot.max - physicData.neutral)
|
||||
end
|
||||
rot = math.clamp(rot, physicData.min, physicData.max)
|
||||
if physicData.headRotMultiplayer ~= nil then
|
||||
rot = rot + math.deg(math.asin(player:getLookDir().y)) * physicData.headRotMultiplayer
|
||||
end
|
||||
if physicData.sneakOffset ~= nil and player:isCrouching() then
|
||||
rot = rot + physicData.sneakOffset
|
||||
end
|
||||
return rot
|
||||
end;
|
||||
|
||||
---物理演算を初期化し、有効にする。
|
||||
---@param self Physics
|
||||
enable = function (self)
|
||||
self.velocityData = {{}, {}, {}, {}, {}, {}, {}}
|
||||
self.velocityAverage = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}
|
||||
self.directionPrev = {}
|
||||
|
||||
if events.TICK:getRegisteredCount("physics_tick") == 0 then
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
local lookDir = player:getLookDir()
|
||||
local velocityHeadFront, velocityHeadRight, velocityHeadRot = self:decomposeHorizontalVelocity(math.deg(math.atan2(lookDir.z, lookDir.x)), 1)
|
||||
local velocityAverage = {self.velocityAverage[1][2], self.velocityAverage[2][2], self.velocityAverage[3][2], self.velocityAverage[4][2], self.velocityAverage[5][2], self.velocityAverage[6][2], self.velocityAverage[7][2]}
|
||||
local velocityY = player:getVelocity().y
|
||||
velocityAverage[1] = (#self.velocityData[1] * velocityAverage[1] + velocityHeadFront) / (#self.velocityData[1] + 1)
|
||||
table.insert(self.velocityData[1], velocityHeadFront)
|
||||
velocityAverage[2] = (#self.velocityData[2] * velocityAverage[2] + velocityY) / (#self.velocityData[2] + 1)
|
||||
table.insert(self.velocityData[2], velocityY)
|
||||
velocityAverage[3] = (#self.velocityData[3] * velocityAverage[3] + velocityHeadRight) / (#self.velocityData[3] + 1)
|
||||
table.insert(self.velocityData[3], velocityHeadRight)
|
||||
velocityAverage[4] = (#self.velocityData[4] * velocityAverage[4] + velocityHeadRot) / (#self.velocityData[4] + 1)
|
||||
table.insert(self.velocityData[4], velocityHeadRot)
|
||||
local velocityBodyFront, velocityBodyRight, velocityBodyRot = self:decomposeHorizontalVelocity((player:getBodyYaw() + models.models.main.Avatar.UpperBody:getTrueRot().y - 90) % 360 - 180, 2)
|
||||
velocityAverage[5] = (#self.velocityData[5] * velocityAverage[5] + velocityBodyFront) / (#self.velocityData[5] + 1)
|
||||
table.insert(self.velocityData[5], velocityBodyFront)
|
||||
velocityAverage[6] = (#self.velocityData[6] * velocityAverage[6] + velocityBodyRight) / (#self.velocityData[6] + 1)
|
||||
table.insert(self.velocityData[6], velocityBodyRight)
|
||||
velocityAverage[7] = (#self.velocityData[7] * velocityAverage[7] + velocityBodyRot) / (#self.velocityData[7] + 1)
|
||||
table.insert(self.velocityData[7], velocityBodyRot)
|
||||
--古いデータの切り捨て
|
||||
for index, velocityTable in ipairs(self.velocityData) do
|
||||
if #velocityTable > 5 then
|
||||
velocityAverage[index] = (#velocityTable * velocityAverage[index] - velocityTable[1]) / (#velocityTable - 1)
|
||||
table.remove(velocityTable, 1)
|
||||
end
|
||||
table.insert(self.velocityAverage[index], velocityAverage[index])
|
||||
table.remove(self.velocityAverage[index], 1)
|
||||
end
|
||||
end
|
||||
end, "physics_tick")
|
||||
|
||||
events.RENDER:register(function (delta)
|
||||
local playerPose = player:getPose()
|
||||
local isHorizontal = playerPose == "SWIMMING" or playerPose == "FALL_FLYING"
|
||||
|
||||
for _, physicData in ipairs(self.parent.characterData.physics.physicData) do
|
||||
for _, modelPart in ipairs(physicData.models) do
|
||||
if modelPart:getVisible() then
|
||||
local rot = vectors.vec3()
|
||||
if physicData.x ~= nil then
|
||||
if isHorizontal and physicData.x.horizontal then
|
||||
rot.x = self:getPhysicRot(physicData.x.horizontal, delta)
|
||||
elseif not isHorizontal and physicData.x.vertical then
|
||||
rot.x = self:getPhysicRot(physicData.x.vertical, delta)
|
||||
end
|
||||
end
|
||||
if physicData.y ~= nil then
|
||||
if isHorizontal and physicData.y.horizontal then
|
||||
rot.y = self:getPhysicRot(physicData.y.horizontal, delta)
|
||||
elseif not isHorizontal and physicData.y.vertical then
|
||||
rot.y = self:getPhysicRot(physicData.y.vertical, delta)
|
||||
end
|
||||
end
|
||||
if physicData.z ~= nil then
|
||||
if isHorizontal and physicData.z.horizontal then
|
||||
rot.z = self:getPhysicRot(physicData.z.horizontal, delta)
|
||||
elseif not isHorizontal and physicData.z.vertical then
|
||||
rot.z = self:getPhysicRot(physicData.z.vertical, delta)
|
||||
end
|
||||
end
|
||||
modelPart:setRot(rot)
|
||||
if self.parent.characterData.physics.callbacks ~= nil and self.parent.characterData.physics.callbacks.onPhysicPerformed ~= nil then
|
||||
self.parent.characterData.physics.callbacks.onPhysicPerformed(self.parent.characterData, modelPart)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "physics_render")
|
||||
end
|
||||
end;
|
||||
|
||||
---物理演算を無効にする。物理演算で管理していたモデルの回転をリセットする。
|
||||
---@param self Physics
|
||||
disable = function (self)
|
||||
events.TICK:remove("physics_tick")
|
||||
events.RENDER:remove("physics_render")
|
||||
for _, physicData in ipairs(self.parent.characterData.physics.physicData) do
|
||||
local initialRot = vectors.vec3()
|
||||
if physicData.x ~= nil and physicData.x.vertical ~= nil then
|
||||
initialRot.x = physicData.x.vertical.neutral
|
||||
end
|
||||
if physicData.y ~= nil and physicData.y.vertical ~= nil then
|
||||
initialRot.y = physicData.y.vertical.neutral
|
||||
end
|
||||
if physicData.z ~= nil and physicData.z.vertical ~= nil then
|
||||
initialRot.z = physicData.z.vertical.neutral
|
||||
end
|
||||
for _, modelPart in ipairs(physicData.models) do
|
||||
modelPart:setRot(initialRot)
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
---@class (exact) PlacementObject : SpawnObject 単一の設置物を管理するクラス
|
||||
---@field package object ModelPart インスタンスで制御するオブジェクト
|
||||
---@field public index integer 設置物データのインデックス番号。設置物のデータを参照するときに使用する。
|
||||
---@field package boundingBox Vector3 設置物の当たり判定
|
||||
---@field package gravity number この設置物に働く重力の大きさ
|
||||
---@field package hasFireResistance boolean この設置物に炎耐性を付けるかどうか
|
||||
---@field public removeReason PlacementObjectManager.RemoveReason この設置物のインスタンスが破棄される理由
|
||||
---@field package modelOffsetPos Vector3 設置物"モデル"の位置オフセット値
|
||||
---@field package offsetPos Vector3 設置物の中心座標のオフセット値
|
||||
---@field public currentPos Vector3 設置物の現在位置
|
||||
---@field package nextPos Vector3 設置物の次ティックの位置
|
||||
---@field package fallingSpeed number 設置物の落下速度
|
||||
---@field package isOnGround boolean 設置物が接地しているかどうか
|
||||
---@field public new fun(parent: Avatar, model: ModelPart, index: integer, data: BlueArchiveCharacter.PlacementObjectStruct, pos: Vector3, rot: number): PlacementObject コンストラクター
|
||||
|
||||
PlacementObject = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@param index integer 設置物データのインデックス番号。設置物のデータを参照するときに使用する。
|
||||
---@param pos Vector3 設置物を設置するワールド座標
|
||||
---@param rot number 設置物のワールド方向
|
||||
---@return PlacementObject
|
||||
new = function (parent, index, pos, rot)
|
||||
---@type PlacementObject
|
||||
local instance = Avatar.instantiate(PlacementObject, SpawnObject, parent)
|
||||
|
||||
instance.index = index
|
||||
instance.object = instance.parent.characterData.placementObjects[instance.index].placementMode == "COPY" and instance.parent.characterData.placementObjects[instance.index].model:copy(instance.uuid) or instance.parent.characterData.placementObjects[instance.index].model
|
||||
instance.boundingBox = instance.parent.characterData.placementObjects[instance.index].boundingBox.size:copy():scale(0.0625)
|
||||
instance.gravity = instance.parent.characterData.placementObjects[instance.index].gravity ~= nil and instance.parent.characterData.placementObjects[instance.index].gravity or 1
|
||||
instance.hasFireResistance = instance.parent.characterData.placementObjects[instance.index].hasFireResistance ~= nil and instance.parent.characterData.placementObjects[instance.index].hasFireResistance or false
|
||||
instance.removeReason = "REMOVED_BY_SCRIPTS"
|
||||
instance.modelOffsetPos = vectors.vec3()
|
||||
instance.offsetPos = vectors.vec3()
|
||||
instance.currentPos = pos
|
||||
instance.nextPos = instance.currentPos
|
||||
instance.fallingSpeed = 0
|
||||
instance.isOnGround = false
|
||||
|
||||
instance.callbacks = {
|
||||
---@param self PlacementObject
|
||||
onInit = function (self)
|
||||
if self.parent.characterData.placementObjects[self.index].placementMode == "COPY" then
|
||||
models.script_placement_object:addChild(self.object)
|
||||
end
|
||||
local objectOffset = vectors.vec3()
|
||||
if self.parent.characterData.placementObjects[self.index].boundingBox.offsetPos ~= nil then
|
||||
objectOffset = self.parent.characterData.placementObjects[self.index].boundingBox.offsetPos:copy():scale(-0.0625)
|
||||
end
|
||||
if self.gravity >= 0 then
|
||||
self.modelOffsetPos = objectOffset:copy():add(0, 0.075, 0)
|
||||
else
|
||||
self.modelOffsetPos = objectOffset:copy():add(0, -0.075, 0)
|
||||
end
|
||||
--self.objectModel:setPos(self.currentPos:copy():add(self.modelOffsetPos):scale(16))
|
||||
self.object:setRot(0, rot, 0)
|
||||
self.object:setVisible(true)
|
||||
if self.parent.characterData.placementObjects[self.index].callbacks ~= nil and self.parent.characterData.placementObjects[self.index].callbacks.onInit ~= nil then
|
||||
self.parent.characterData.placementObjects[self.index].callbacks.onInit(self.parent.characterData, self)
|
||||
end
|
||||
end;
|
||||
|
||||
---@param self PlacementObject
|
||||
onDeinit = function (self)
|
||||
if self.parent.characterData.placementObjects[self.index].placementMode == "COPY" then
|
||||
models.script_placement_object:removeChild(self.object)
|
||||
self.object:remove()
|
||||
else
|
||||
self.object:setVisible(false)
|
||||
end
|
||||
if self.parent.characterData.placementObjects[self.index].callbacks ~= nil and self.parent.characterData.placementObjects[self.index].callbacks.onDeinit ~= nil then
|
||||
self.parent.characterData.placementObjects[self.index].callbacks.onDeinit(self.parent.characterData, self)
|
||||
end
|
||||
end;
|
||||
|
||||
---@param self PlacementObject
|
||||
onTick = function (self)
|
||||
--設置物の位置を強制更新
|
||||
self.currentPos = self.nextPos
|
||||
self.object:setPos(self.currentPos:copy():add(self.modelOffsetPos):scale(16))
|
||||
|
||||
--当たり判定同士が重複しているか確認
|
||||
local boundingBoxStartPos = vectors.vec3(self.currentPos.x - self.boundingBox.x / 2, self.currentPos.y, self.currentPos.z - self.boundingBox.z / 2)
|
||||
local boundingBoxEndPos = vectors.vec3(self.currentPos.x + self.boundingBox.x / 2, self.currentPos.y + self.boundingBox.y, self.currentPos.z + self.boundingBox.z / 2)
|
||||
local boundingBoxCenter = boundingBoxEndPos:copy():sub(boundingBoxStartPos):scale(0.5):add(boundingBoxStartPos)
|
||||
for z = math.floor(boundingBoxStartPos.z), math.floor(boundingBoxEndPos.z) do
|
||||
for y = math.floor(boundingBoxStartPos.y), math.floor(boundingBoxEndPos.y) do
|
||||
for x = math.floor(boundingBoxStartPos.x), math.floor(boundingBoxEndPos.x) do
|
||||
for _, collisionBox in ipairs( world.getBlockState(x, y, z):getCollisionShape()) do
|
||||
local collisionStartPos = collisionBox[1]:copy():add(x, y, z)
|
||||
local collisionEndPos = collisionBox[2]:copy():add(x, y, z)
|
||||
local collisionBoxCenter = collisionStartPos:copy():add(collisionEndPos:copy():sub(collisionStartPos):scale(0.5))
|
||||
if math.abs(collisionBoxCenter.x - boundingBoxCenter.x) < ((collisionEndPos.x - collisionStartPos.x) + (boundingBoxEndPos.x - boundingBoxStartPos.x)) / 2 and math.abs(collisionBoxCenter.y - boundingBoxCenter.y) < ((collisionEndPos.y - collisionStartPos.y) + (boundingBoxEndPos.y - boundingBoxStartPos.y)) / 2 and math.abs(collisionBoxCenter.z - boundingBoxCenter.z) < ((collisionEndPos.z - collisionStartPos.z) + (boundingBoxEndPos.z - boundingBoxStartPos.z)) / 2 then
|
||||
self.removeReason = "OVERLAPPED"
|
||||
self.shouldDeinit = true
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--落下速度を更新
|
||||
local fluidTags = world.getBlockState(self.currentPos):getFluidTags()
|
||||
if fluidTags[2] == "c:water" then
|
||||
if self.gravity >= 0 then
|
||||
self.fallingSpeed = math.max(self.fallingSpeed - 0.1 * self.gravity, 0.1 * self.gravity)
|
||||
else
|
||||
self.fallingSpeed = math.min(self.fallingSpeed - 0.1 * self.gravity, 0.1 * self.gravity)
|
||||
end
|
||||
elseif fluidTags[2] == "c:lava" then
|
||||
if self.gravity >= 0 then
|
||||
self.fallingSpeed = math.max(self.fallingSpeed - 0.1 * self.gravity, 0.02 * self.gravity)
|
||||
else
|
||||
self.fallingSpeed = math.min(self.fallingSpeed - 0.1 * self.gravity, 0.02 * self.gravity)
|
||||
end
|
||||
else
|
||||
if self.gravity >= 0 then
|
||||
self.fallingSpeed = math.min(self.fallingSpeed + 0.035 * self.gravity, 3.575 * self.gravity)
|
||||
else
|
||||
self.fallingSpeed = math.max(self.fallingSpeed + 0.035 * self.gravity, 3.575 * self.gravity)
|
||||
end
|
||||
end
|
||||
self.nextPos = self.currentPos:copy():add(0, self.fallingSpeed * -1, 0)
|
||||
|
||||
--現ティックと次ティックから直方体を算出
|
||||
local nextBoxStartPos = vectors.vec3()
|
||||
local nextBoxEndPos = vectors.vec3()
|
||||
if self.gravity >= 0 then
|
||||
nextBoxStartPos = vectors.vec3(self.currentPos.x - self.boundingBox.x / 2, math.min(self.currentPos.y, self.currentPos.y - self.fallingSpeed), self.currentPos.z - self.boundingBox.z / 2)
|
||||
nextBoxEndPos = vectors.vec3(self.currentPos.x + self.boundingBox.x / 2, math.max(self.currentPos.y, self.currentPos.y - self.fallingSpeed), self.currentPos.z + self.boundingBox.z / 2)
|
||||
else
|
||||
nextBoxStartPos = vectors.vec3(self.currentPos.x - self.boundingBox.x / 2, math.min(self.currentPos.y, self.currentPos.y - self.fallingSpeed) + self.boundingBox.y, self.currentPos.z - self.boundingBox.z / 2)
|
||||
nextBoxEndPos = vectors.vec3(self.currentPos.x + self.boundingBox.x / 2, math.max(self.currentPos.y, self.currentPos.y - self.fallingSpeed) + self.boundingBox.y, self.currentPos.z + self.boundingBox.z / 2)
|
||||
end
|
||||
local nextBoxCenter = nextBoxStartPos:copy():add(nextBoxEndPos:copy():sub(nextBoxStartPos):scale(0.5))
|
||||
|
||||
--直方体と重なるブロック座標を全て算出
|
||||
local collisionDetected = false
|
||||
if self.gravity >= 0 then
|
||||
local collisionYPos = math.floor(nextBoxStartPos.y)
|
||||
for y = math.floor(nextBoxEndPos.y), math.floor(nextBoxStartPos.y) - 1, -1 do
|
||||
for z = math.floor(nextBoxStartPos.z), math.floor(nextBoxEndPos.z) do
|
||||
for x = math.floor(nextBoxStartPos.x), math.floor(nextBoxEndPos.x) do
|
||||
for _, collisionBox in ipairs( world.getBlockState(x, y, z):getCollisionShape()) do
|
||||
local collisionStartPos = collisionBox[1]:copy():add(x, y, z)
|
||||
local collisionEndPos = collisionBox[2]:copy():add(x, y, z)
|
||||
local collisionBoxCenter = collisionStartPos:copy():add(collisionEndPos:copy():sub(collisionStartPos):scale(0.5))
|
||||
if math.abs(collisionBoxCenter.x - nextBoxCenter.x) < ((collisionEndPos.x - collisionStartPos.x) + (nextBoxEndPos.x - nextBoxStartPos.x)) / 2 and math.abs(collisionBoxCenter.y - nextBoxCenter.y) < ((collisionEndPos.y - collisionStartPos.y) + (nextBoxEndPos.y - nextBoxStartPos.y)) / 2 and math.abs(collisionBoxCenter.z - nextBoxCenter.z) < ((collisionEndPos.z - collisionStartPos.z) + (nextBoxEndPos.z - nextBoxStartPos.z)) / 2 then
|
||||
if collisionEndPos.y > self.nextPos.y then
|
||||
self.nextPos.y = collisionEndPos.y
|
||||
collisionYPos = y
|
||||
self.fallingSpeed = 0
|
||||
collisionDetected = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if y == collisionYPos - 1 then
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
local collisionYPos = math.floor(nextBoxEndPos.y)
|
||||
for y = math.floor(nextBoxStartPos.y), math.floor(nextBoxEndPos.y), 1 do
|
||||
for z = math.floor(nextBoxStartPos.z), math.floor(nextBoxEndPos.z) do
|
||||
for x = math.floor(nextBoxStartPos.x), math.floor(nextBoxEndPos.x) do
|
||||
for _, collisionBox in ipairs( world.getBlockState(x, y, z):getCollisionShape()) do
|
||||
local collisionStartPos = collisionBox[1]:copy():add(x, y, z)
|
||||
local collisionEndPos = collisionBox[2]:copy():add(x, y, z)
|
||||
local collisionBoxCenter = collisionStartPos:copy():add(collisionEndPos:copy():sub(collisionStartPos):scale(0.5))
|
||||
if math.abs(collisionBoxCenter.x - nextBoxCenter.x) < ((collisionEndPos.x - collisionStartPos.x) + (nextBoxEndPos.x - nextBoxStartPos.x)) / 2 and math.abs(collisionBoxCenter.y - nextBoxCenter.y) < ((collisionEndPos.y - collisionStartPos.y) + (nextBoxEndPos.y - nextBoxStartPos.y)) / 2 and math.abs(collisionBoxCenter.z - nextBoxCenter.z) < ((collisionEndPos.z - collisionStartPos.z) + (nextBoxEndPos.z - nextBoxStartPos.z)) / 2 then
|
||||
if collisionStartPos.y < self.nextPos.y + self.boundingBox.y then
|
||||
self.nextPos.y = collisionStartPos.y - self.boundingBox.y
|
||||
collisionYPos = y
|
||||
self.fallingSpeed = 0
|
||||
collisionDetected = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if y == collisionYPos + 1 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if collisionDetected and not self.isOnGround and self.parent.characterData.placementObjects[self.index].callbacks ~= nil and self.parent.characterData.placementObjects[self.index].callbacks.onGround ~= nil then
|
||||
self.parent.characterData.placementObjects[self.index].callbacks.onGround(self.parent.characterData, self)
|
||||
end
|
||||
self.isOnGround = collisionDetected
|
||||
local nextBlock = world.getBlockState(self.nextPos)
|
||||
local isNextBlockFire = false
|
||||
for _, tag in ipairs(nextBlock:getTags()) do
|
||||
if tag == "minecraft:fire" then
|
||||
isNextBlockFire = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if self.nextPos.y < -128 then
|
||||
self.removeReason = "TOO_LOW"
|
||||
self.shouldDeinit = true
|
||||
elseif self.nextPos.y > 384 then
|
||||
self.removeReason = "TOO_HIGH"
|
||||
self.shouldDeinit = true
|
||||
elseif not self.hasFireResistance and (nextBlock:getFluidTags()[2] == "c:lava" or isNextBlockFire) then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.fire.extinguish"), self.nextPos)
|
||||
for _ = 0, self.boundingBox.x * self.boundingBox.y * self.boundingBox.z * 8 do
|
||||
particles:newParticle(self.parent.compatibilityUtils:checkParticle("minecraft:smoke"), vectors.vec3(self.nextPos.x + math.random() * self.boundingBox.x - self.boundingBox.x / 2, self.nextPos.y + math.random() * self.boundingBox.y, self.nextPos.z + math.random() * self.boundingBox.z - self.boundingBox.z / 2))
|
||||
end
|
||||
self.removeReason = "BURNT"
|
||||
self.shouldDeinit = true
|
||||
end
|
||||
if self.parent.characterData.placementObjects[self.index].callbacks ~= nil and self.parent.characterData.placementObjects[self.index].callbacks.onTick ~= nil then
|
||||
self.parent.characterData.placementObjects[self.index].callbacks.onTick(self.parent.characterData, self)
|
||||
end
|
||||
end;
|
||||
|
||||
---@param self PlacementObject
|
||||
onRender = function (self, delta, context)
|
||||
self.object:setPos(self.nextPos:copy():sub(self.currentPos):scale(delta):add(self.currentPos):add(self.modelOffsetPos):scale(16))
|
||||
if self.parent.characterData.placementObjects[self.index].callbacks ~= nil and self.parent.characterData.placementObjects[self.index].callbacks.onRender ~= nil then
|
||||
self.parent.characterData.placementObjects[self.index].callbacks.onRender(self.parent.characterData, self)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return instance
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
---設置物の設置モードの列挙型
|
||||
---@alias PlacementObjectManager.PlacementMode
|
||||
---| "COPY" # コピーモード。BBアニメーションは使えないが、複数の設置物を設置可能。
|
||||
---| "MOVE" # 移動モード。同時に1つしか設置物を設置できないが、BBアニメーションが使える。
|
||||
|
||||
---設置物が削除された理由の列挙型
|
||||
---@alias PlacementObjectManager.RemoveReason
|
||||
---| "REMOVED_BY_SCRIPTS" # スクリプトによって削除
|
||||
---| "OVERLAPPED" # 設置物がブロックと重なって削除
|
||||
---| "BURNT" # 炎に焼かれて削除
|
||||
---| "TOO_LOW" # 設置物のY座標が低過ぎて削除
|
||||
---| "TOO_HIGH" # 設置物のY座標が高過ぎて削除
|
||||
|
||||
---@class (exact) PlacementObjectManager : SpawnObjectManager 設置物を管理するマネージャークラス
|
||||
---@field public objects PlacementObject[] インスタンスで制御するオブジェクト
|
||||
---@field public getObject fun(self: PlacementObjectManager, index: integer, pos: Vector3, rot: number): PlacementObject 設置物のインスタンスを生成して返す
|
||||
---@field public spawn fun(self: PlacementObjectManager, index: integer, pos: Vector3, rot: number) 設置物を設置する
|
||||
---@field public applyFunc fun(self: PlacementObjectManager, index: integer, func: fun(object: PlacementObject, i: integer)) 設置済み設置物の指定した設置物データのインデックス番号のみに関数を適用する
|
||||
|
||||
PlacementObjectManager = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return PlacementObjectManager
|
||||
new = function (parent)
|
||||
---@type PlacementObjectManager
|
||||
local instance = Avatar.instantiate(PlacementObjectManager, SpawnObjectManager, parent)
|
||||
|
||||
instance.managerName = "placement_object"
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self PlacementObjectManager
|
||||
init = function (self)
|
||||
SpawnObjectManager.init(self)
|
||||
|
||||
---@diagnostic disable-next-line: discard-returns
|
||||
models:newPart("script_placement_object", "World")
|
||||
for _, data in ipairs(self.parent.characterData.placementObjects) do
|
||||
if data.placementMode == "MOVE" then
|
||||
data.model = data.model:moveTo(models.script_placement_object)
|
||||
data.model:setVisible(false)
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
|
||||
---設置物のインスタンスを生成して返す。
|
||||
---@param self PlacementObjectManager
|
||||
---@param index integer 設置物データのインデックス番号
|
||||
---@param pos Vector3 設置物を設置するワールド座標
|
||||
---@param rot number 設置物を設置するワールド方向(Y軸のみ)
|
||||
---@return PlacementObject instance 生成したインスタンス
|
||||
getObject = function (self, index, pos, rot)
|
||||
if self.parent.characterData.placementObjects[index].placementMode == "MOVE" then
|
||||
for i, placementObject in ipairs(self.objects) do
|
||||
if placementObject.index == index then
|
||||
self:remove(i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return PlacementObject.new(self.parent, index, pos, rot)
|
||||
end;
|
||||
|
||||
---設置物を設置する。
|
||||
---@param self PlacementObjectManager
|
||||
---@param index integer 設置物データのインデックス番号
|
||||
---@param pos Vector3 設置物を設置するワールド座標
|
||||
---@param rot number 設置物を設置するワールド方向(Y軸のみ)
|
||||
spawn = function (self, index, pos, rot)
|
||||
SpawnObjectManager.spawn(self, index, pos, rot)
|
||||
end;
|
||||
|
||||
---設置済み設置物の、指定した設置物データのインデックス番号のみに関数を適用する。
|
||||
---@param self PlacementObjectManager
|
||||
---@param index integer 関数実行対象の設置物データのインデックス番号
|
||||
---@param func fun(object: PlacementObject, i: integer) 実行する関数
|
||||
applyFunc = function (self, index, func)
|
||||
for i, obj in ipairs(self.objects) do
|
||||
if obj.index == index then
|
||||
func(obj, i)
|
||||
end
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
---@class (exact) Portrait : HeadModelGenerator ポートレートのモデルを管理するクラス
|
||||
|
||||
Portrait = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Portrait
|
||||
new = function (parent)
|
||||
---@type Portrait
|
||||
local instance = Avatar.instantiate(Portrait, HeadModelGenerator, parent)
|
||||
|
||||
instance.processData = instance.parent.characterData.portrait
|
||||
instance.parentName = "portrait"
|
||||
instance.parentType = "Portrait"
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Portrait
|
||||
init = function (self)
|
||||
HeadModelGenerator.init(self)
|
||||
end;
|
||||
|
||||
---頭モデルのコピーを生成する。
|
||||
---@param self HeadBlock
|
||||
generateHeadModel = function (self)
|
||||
HeadModelGenerator.generateHeadModel(self)
|
||||
if models.script_portrait.Head ~= nil then
|
||||
models.script_portrait.Head.HeadRing:remove()
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
---@class Skirt : AvatarModule スカートを制御するクラス
|
||||
|
||||
Skirt = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Skirt
|
||||
new = function (parent)
|
||||
---@type Skirt
|
||||
local instance = Avatar.instantiate(Skirt, AvatarModule, parent)
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self AvatarModule
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
if self.parent.characterData.skirt.skirtModels ~= nil and #self.parent.characterData.skirt.skirtModels > 0 then
|
||||
events.TICK:register(function ()
|
||||
local isCrouching = player:isCrouching()
|
||||
for _, skirtModel in ipairs(self.parent.characterData.skirt.skirtModels) do
|
||||
if skirtModel:getVisible() then
|
||||
skirtModel:setRot(isCrouching and 30 or 0, 0, 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
---レジストリの種類を示す列挙型
|
||||
---@alias CompatibilityUtils.RegistryType
|
||||
---| "BLOCK" # ブロック名
|
||||
---| "ITEM" # アイテム名
|
||||
---| "PARTICLE" # パーティクル名
|
||||
---| "SOUND" # サウンド名
|
||||
|
||||
---@class (exact) CompatibilityUtils : AvatarModule Minecraftのゲームバージョンが異なっていてもある程度互換性を確保するためのユーティリティクラス
|
||||
---@field package registries {block: Minecraft.blockID[], item: Minecraft.itemID[], particle: Minecraft.particleID[], sound: Minecraft.soundID[]} ゲームから取得した全アイテム名を保持するテーブル
|
||||
---@field package checkedTable {block: {[Minecraft.blockID]: boolean}, item: {[Minecraft.itemID]: boolean}, particle: {[Minecraft.particleID]: boolean}, sound: {[Minecraft.soundID]: boolean}} レジストリへの確認が済んでいるIDを保持するテーブル
|
||||
---@field package find fun(self: CompatibilityUtils, registryType: CompatibilityUtils.RegistryType, target: string): boolean 指定されたターゲットがレジストリに登録されているかどうかを返す。
|
||||
---@field public checkBlock fun(self: CompatibilityUtils, block: Minecraft.blockID): Minecraft.blockID 指定されたブロックIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:dirt"を返す。
|
||||
---@field public checkItem fun(self: CompatibilityUtils, item: Minecraft.itemID): Minecraft.itemID 指定されたアイテムIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:barrier"を返す。
|
||||
---@field public checkParticle fun(self: CompatibilityUtils, particle: Minecraft.particleID): Minecraft.particleID 指定されたパーティクルIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:poof"を返す。
|
||||
---@field public checkSound fun(self: CompatibilityUtils, sound: Minecraft.soundID): Minecraft.soundID 指定されたサウンドIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:empty"を返す。
|
||||
---@field public getBlockParticleId fun(block: Minecraft.blockID): string ブロックの破片のパーティクルを示す文字列を返す。Minecraftのバージョン違いを吸収するための関数。
|
||||
---@field public getDustParticleId fun(color: Vector3, size: number): string dustパーティクルを示す文字列を返す。Minecraftのバージョン違いを吸収するための関数。
|
||||
---@field public setPostEffect fun(effect?: Minecraft.shaderName) renderer:setPostEffect()のラッパー関数。1.20.5でレンダーエフェクトが削除されたことによる対応。
|
||||
|
||||
CompatibilityUtils = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return CompatibilityUtils
|
||||
new = function (parent)
|
||||
---@type CompatibilityUtils
|
||||
local instance = Avatar.instantiate(CompatibilityUtils, AvatarModule, parent)
|
||||
|
||||
instance.registries = {}
|
||||
instance.checkedTable = {
|
||||
block = {};
|
||||
item = {};
|
||||
particle = {};
|
||||
sound = {};
|
||||
}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self CompatibilityUtils
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
self.registries.block = client.getRegistry("minecraft:block")
|
||||
self.registries.item = client.getRegistry("minecraft:item")
|
||||
self.registries.particle = client.getRegistry("minecraft:particle_type")
|
||||
self.registries.sound = client.getRegistry("minecraft:sound_event")
|
||||
for name, _ in pairs(self.registries) do
|
||||
table.sort(self.registries[name])
|
||||
end
|
||||
self.checkedTable.block["minecraft:dirt"] = true
|
||||
self.checkedTable.item["minecraft:barrier"] = true
|
||||
self.checkedTable.particle["minecraft:poof"] = true
|
||||
self.checkedTable.sound["minecraft:empty"] = true
|
||||
if host:isHost() and client:getVersion() < "1.20.1" then
|
||||
print(self.parent.locale:getLocale("avatar.old_version_warning"))
|
||||
end
|
||||
end;
|
||||
|
||||
---指定されたターゲットがレジストリに登録されているかどうかを返す。
|
||||
---@param self CompatibilityUtils
|
||||
---@param registryType CompatibilityUtils.RegistryType 検索をかける対象のレジストリ
|
||||
---@param target string 検索対象名。"minecraft:"を抜かないこと。
|
||||
---@return boolean idFound 指定されたターゲットがレジストリで見つかったかどうか
|
||||
find = function (self, registryType, target)
|
||||
---リスト内の中央の要素(偶数の場合は中央から1つ左の要素)と指定されたターゲットのUnicode順を比較する。
|
||||
---@param from integer リストの検索開始のインデックス番号
|
||||
---@param to integer リストの検索終了のインスタンス番号(指定したインデックス番号の要素も検索に含む)
|
||||
---@return integer compareResult 比較結果。0は同じ文字列、1はターゲットの方が大きい、-1はターゲットの方が小さいことを表す。
|
||||
local function compareToCenterElement(from, to)
|
||||
local centerIndex = math.floor((to - from) / 2) + from
|
||||
if self.registries[registryType:lower()][centerIndex] < target then
|
||||
return 1
|
||||
elseif self.registries[registryType:lower()][centerIndex] > target then
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
local startIndex = 1
|
||||
local endIndex = #self.registries[registryType:lower()]
|
||||
while startIndex < endIndex do
|
||||
local compareResult = compareToCenterElement(startIndex, endIndex)
|
||||
if compareResult == 1 then
|
||||
startIndex = math.floor((endIndex - startIndex) / 2) + startIndex + 1
|
||||
elseif compareResult == -1 then
|
||||
endIndex = math.floor((endIndex - startIndex) / 2) + startIndex
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if startIndex == endIndex then
|
||||
return compareToCenterElement(startIndex, endIndex) == 0
|
||||
else
|
||||
return true
|
||||
end
|
||||
end;
|
||||
|
||||
---指定されたブロックIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:dirt"を返す。
|
||||
---@param self CompatibilityUtils
|
||||
---@param block Minecraft.blockID 確認対象のブロックID
|
||||
---@param blockState string? ブロックステートを示す文字列
|
||||
---@return Minecraft.blockID blockID レジストリに登録してある場合は確認対象のブロックIDをそのまま返し、未登録の場合は"minecraft:dirt"が返す。
|
||||
checkBlock = function (self, block, blockState)
|
||||
if self.checkedTable.block[block] == nil then
|
||||
self.checkedTable.block[block] = self:find("BLOCK", block)
|
||||
end
|
||||
local state = blockState ~= nil and blockState or ""
|
||||
return self.checkedTable.block[block] and block..state or "minecraft:dirt"
|
||||
end;
|
||||
|
||||
---指定されたアイテムIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:barrier"を返す。
|
||||
---@param self CompatibilityUtils
|
||||
---@param item Minecraft.itemID 確認対象のアイテムID
|
||||
---@return Minecraft.itemID blockID レジストリに登録してある場合は確認対象のアイテムIDをそのまま返し、未登録の場合は"minecraft:barrier"が返す。
|
||||
checkItem = function (self, item)
|
||||
if self.checkedTable.item[item] == nil then
|
||||
self.checkedTable.item[item] = self:find("ITEM", item)
|
||||
end
|
||||
return self.checkedTable.item[item] and item or "minecraft:barrier"
|
||||
end;
|
||||
|
||||
---指定されたパーティクルIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:poof"を返す。
|
||||
---@param self CompatibilityUtils
|
||||
---@param particle Minecraft.particleID 確認対象のパーティクルID
|
||||
---@return Minecraft.particleID particleID レジストリに登録してある場合は確認対象のパーティクルIDをそのまま返し、未登録の場合は"minecraft:poof"が返す。
|
||||
checkParticle = function (self, particle)
|
||||
if self.checkedTable.particle[particle] == nil then
|
||||
self.checkedTable.particle[particle] = self:find("PARTICLE", particle)
|
||||
end
|
||||
return self.checkedTable.particle[particle] and particle or "minecraft:poof"
|
||||
end;
|
||||
|
||||
---指定されたサウンドIDがレジストリに登録されているか確認する。レジストリに未登録の場合は"minecraft:empty"を返す。
|
||||
---@param self CompatibilityUtils
|
||||
---@param sound Minecraft.soundID 確認対象のサウンドID
|
||||
---@return Minecraft.soundID particleID レジストリに登録してある場合は確認対象のサウンドIDをそのまま返し、未登録の場合は"minecraft:empty"が返す。
|
||||
checkSound = function (self, sound)
|
||||
if self.checkedTable.sound[sound] == nil then
|
||||
self.checkedTable.sound[sound] = self:find("SOUND", sound)
|
||||
end
|
||||
return self.checkedTable.sound[sound] and sound or "minecraft:empty"
|
||||
end;
|
||||
|
||||
---ブロックの破片のパーティクルを示す文字列を返す。Minecraftのバージョン違いを吸収するための関数。
|
||||
---@param block Minecraft.blockID ブロックの破片パーティクルとして表示するブロックのID。レジストリへの確認は行わない。
|
||||
---@return string particleData ブロックの破片のパーティクルを示す文字列
|
||||
getBlockParticleId = function (block)
|
||||
return client:getVersion() >= "1.20.5" and "minecraft:block{block_state:\""..block.."\"}" or "minecraft:block "..block
|
||||
end;
|
||||
|
||||
---dustパーティクルを示す文字列を返す。Minecraftのバージョン違いを吸収するための関数。
|
||||
---@param color Vector3 dustの色
|
||||
---@param size number dustの大きさ
|
||||
---@return string particleData dustの破片のパーティクルを示す文字列
|
||||
getDustParticleId = function (color, size)
|
||||
return client:getVersion() >= "1.20.5" and "minecraft:dust{color:["..color.x..","..color.y..","..color.z.."],scale:"..math.clamp(size, 0.01, 4).."}" or "minecraft:dust "..color.x.." "..color.y.." "..color.z.." "..size
|
||||
end;
|
||||
|
||||
---renderer:setPostEffect()のラッパー関数
|
||||
---1.20.5でレンダーエフェクトが削除されたことによる対応
|
||||
---@param effect? Minecraft.shaderName 適用するエフェクト
|
||||
setPostEffect = function (effect)
|
||||
if client:getVersion() < "1.20.5" then
|
||||
renderer:setPostEffect(effect)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
---@class (exact) ModelUtils : AvatarModule モデルに関するユーティリティ関数群
|
||||
---@field public getModelWorldPos fun(model: ModelPart): Vector3 指定したモデルのワールド位置を返す。
|
||||
---@field public copyModel fun(self: ModelUtils, modelPart: ModelPart, name?: string, forceCopy?: boolean): ModelPart|nil モデルパーツをディープコピーする。
|
||||
---@field public copyModel fun(self: ModelUtils, modelPart: ModelPart, name?: string, forceCopy?: true): ModelPart モデルパーツをディープコピーする。
|
||||
---@field public moveTo fun(target: ModelPart, destination: ModelPart, originalParent: ModelPart) モデルパーツを別の親に移動させる。組み込みmoveTo()で何故かモデルパーツが残ってしまう問題に対処済み。
|
||||
|
||||
ModelUtils = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ModelUtils
|
||||
new = function (parent)
|
||||
---@type ModelUtils
|
||||
local instance = Avatar.instantiate(ModelUtils, AvatarModule, parent)
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---指定したモデルのワールド位置を返す。
|
||||
---@param model ModelPart ワールド位置を取得するモデルパーツ
|
||||
---@return Vector3 worldPos モデルのワールド位置
|
||||
getModelWorldPos = function(model)
|
||||
local modelMatrix = model:partToWorldMatrix()
|
||||
return vectors.vec3(modelMatrix[4][1], modelMatrix[4][2], modelMatrix[4][3])
|
||||
end;
|
||||
|
||||
---モデルパーツをディープコピーする。
|
||||
---非表示のモデルパーツはコピーしない。
|
||||
---@param modelPart ModelPart コピーするモデルパーツ
|
||||
---@param name? string コピーしたモデルパーツの名前。省略した際はコピー元と同じ名前になる。
|
||||
---@param forceCopy? boolean 非表示のモデルも強制的にコピーするかどうか
|
||||
---@return ModelPart|nil copiedModelPart コピーされたモデルパーツ。入力されたモデルパーツが非表示の場合はnilが返る。
|
||||
copyModel = function (self, modelPart, name, forceCopy)
|
||||
if modelPart:getVisible() or forceCopy then
|
||||
local copy = modelPart:copy(name ~= nil and name or modelPart:getName())
|
||||
copy:setParentType("None")
|
||||
for _, child in ipairs(copy:getChildren()) do
|
||||
copy:removeChild(child)
|
||||
local childModel = self:copyModel(child)
|
||||
if childModel ~= nil then
|
||||
copy:addChild(childModel)
|
||||
end
|
||||
end
|
||||
return copy
|
||||
end
|
||||
end;
|
||||
|
||||
---モデルパーツを別の親に移動させる。
|
||||
---組み込みmoveTo()で何故かモデルパーツが残ってしまう問題に対処済み。
|
||||
---@param target ModelPart 移動させる対象のモデルパーツ
|
||||
---@param destination ModelPart 移動先の親
|
||||
---@param originalParent ModelPart 移動元の親
|
||||
moveTo = function (target, destination, originalParent)
|
||||
target:moveTo(destination)
|
||||
local modelName = target:getName()
|
||||
if originalParent[modelName] ~= nil then
|
||||
originalParent:removeChild(target)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
---@alias PlayerUtils.DamageStatus
|
||||
---| "NONE" # ダメージなし
|
||||
---| "DAMAGE" # ダメージを受けた
|
||||
---| "DIED" # 死亡した
|
||||
|
||||
---@class (exact) PlayerUtils : AvatarModule プレイヤーに関するユーティリティ関数群
|
||||
---@field public damageStatus PlayerUtils.DamageStatus 現在のティックのダメージステータス
|
||||
---@field package healthPrev integer 前ティックのHP量
|
||||
|
||||
PlayerUtils = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return PlayerUtils
|
||||
new = function (parent)
|
||||
---@type PlayerUtils
|
||||
local instance = Avatar.instantiate(PlayerUtils, AvatarModule, parent)
|
||||
|
||||
instance.damageStatus = "NONE"
|
||||
instance.healthPrev = player:getHealth()
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self PlayerUtils
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
local health = player:getHealth()
|
||||
self.damageStatus = self.healthPrev > health and (health == 0 and "DIED" or "DAMAGE") or "NONE"
|
||||
self.healthPrev = health
|
||||
end)
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
---@class (exact) SpawnObject : AvatarModule オブジェクト(設置物、独自定義パーティクル、bbモデルetc.)クラス
|
||||
---@field public object any インスタンスで制御するオブジェクト。ModelPartやRenderTaskを想定している。
|
||||
---@field public uuid string このインスタンスのUUID。オブジェクトの名前付けなどにどうぞ。
|
||||
---@field public shouldDeinit boolean このオブジェクトを破棄するかどうか。trueにするとオブジェクトが破棄され、その際に、onDeinit()コールバック関数が呼ばれる。
|
||||
---@field public callbacks? SpawnObject.CallbackSet スポーンオブジェクトのコールバック関数
|
||||
|
||||
---@class (exact) SpawnObject.CallbackSet スポーンオブジェクトのコールバック関数のセット
|
||||
---@field public onInit? fun(self: SpawnObject) オブジェクトの初期化直後に呼ばれる関数
|
||||
---@field public onDeinit? fun(self: SpawnObject) オブジェクトの破棄直前に呼ばれる関数
|
||||
---@field public onTick? fun(self: SpawnObject) 各ティック毎に呼ばれる関数
|
||||
---@field public onRender? fun(self: SpawnObject, delta: number, context: Event.Render.context) 各レンダーティック毎に呼ばれる関数
|
||||
|
||||
SpawnObject = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return SpawnObject
|
||||
new = function (parent)
|
||||
---@type SpawnObject
|
||||
local instance = Avatar.instantiate(SpawnObject, AvatarModule, parent)
|
||||
|
||||
instance.uuid = client.intUUIDToString(client.generateUUID())
|
||||
instance.shouldDeinit = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
---@class (exact) SpawnObjectManager : AvatarModule オブジェクト(設置物、独自定義パーティクル、bbモデルetc.)をスポーンさせ、管理するマネージャークラス
|
||||
---@field public managerName string このマネージャーの名前
|
||||
---@field public objects SpawnObject[] スポーンさせたオブジェクトを保持するテーブル
|
||||
---@field public getObject fun(self: SpawnObjectManager): SpawnObject スポーンオブジェクトのインスタンスを生成して返す
|
||||
---@field public spawn fun(self: SpawnObjectManager, ...: any) オブジェクトをスポーンさせる
|
||||
---@field public remove fun(self: SpawnObjectManager, index: integer) オブジェクトを1つ削除する
|
||||
---@field public removeAll fun(self: SpawnObjectManager) オブジェクトをすべて削除する
|
||||
|
||||
SpawnObjectManager = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return SpawnObjectManager
|
||||
new = function (parent)
|
||||
---@type SpawnObjectManager
|
||||
local instance = Avatar.instantiate(SpawnObjectManager, AvatarModule, parent)
|
||||
|
||||
instance.managerName = "spawn_object"
|
||||
instance.objects = {}
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---スポーンオブジェクトのインスタンスを生成して返す。
|
||||
---@param self SpawnObjectManager
|
||||
---@return SpawnObject instance 生成したスポーンオブジェクト
|
||||
getObject = function (self)
|
||||
return SpawnObject.new(self.parent)
|
||||
end;
|
||||
|
||||
---オブジェクトをスポーンさせる。
|
||||
---@param self SpawnObjectManager
|
||||
---@param ... any インスタンス生成時の引数
|
||||
spawn = function (self, ...)
|
||||
---@diagnostic disable-next-line: redundant-parameter
|
||||
local instance = self:getObject(...)
|
||||
table.insert(self.objects, instance)
|
||||
if instance.callbacks ~= nil and instance.callbacks.onInit ~= nil then
|
||||
instance.callbacks.onInit(instance)
|
||||
end
|
||||
|
||||
if #self.objects == 1 then
|
||||
events.TICK:register(function ()
|
||||
if not client:isPaused() then
|
||||
for index, ins in ipairs(self.objects) do
|
||||
if ins.callbacks ~= nil and ins.callbacks.onTick ~= nil then
|
||||
ins.callbacks.onTick(ins)
|
||||
end
|
||||
if ins.shouldDeinit then
|
||||
if ins.callbacks ~= nil and ins.callbacks.onDeinit ~= nil then
|
||||
ins.callbacks.onDeinit(ins)
|
||||
end
|
||||
table.remove(self.objects, index)
|
||||
if #self.objects == 0 then
|
||||
events.TICK:remove(self.managerName.."_tick")
|
||||
events.RENDER:remove(self.managerName.."_render")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end, self.managerName.."_tick")
|
||||
|
||||
events.RENDER:register(function (delta, ctx)
|
||||
if not client:isPaused() then
|
||||
for _, ins in ipairs(self.objects) do
|
||||
if ins.callbacks ~= nil and ins.callbacks.onRender ~= nil then
|
||||
ins.callbacks.onRender(ins, delta, ctx)
|
||||
end
|
||||
end
|
||||
end
|
||||
end, self.managerName.."_render")
|
||||
end
|
||||
end;
|
||||
|
||||
---オブジェクトを1つ削除する。
|
||||
---@param self SpawnObjectManager
|
||||
---@param index integer 削除するオブジェクトのインデックス番号
|
||||
remove = function (self, index)
|
||||
if self.objects[index] ~= nil then
|
||||
if self.objects[index].callbacks ~= nil and self.objects[index].callbacks.onDeinit ~= nil then
|
||||
self.objects[index].callbacks.onDeinit(self.objects[index])
|
||||
end
|
||||
table.remove(self.objects, index)
|
||||
if #self.objects == 0 then
|
||||
events.TICK:remove(self.managerName.."_tick")
|
||||
events.RENDER:remove(self.managerName.."_render")
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
---オブジェクトをすべて削除する。
|
||||
---@param self SpawnObjectManager
|
||||
removeAll = function (self)
|
||||
while #self.objects > 0 do
|
||||
if self.objects[1].callbacks ~= nil and self.objects[1].callbacks.onDeinit ~= nil then
|
||||
self.objects[1].callbacks.onDeinit(self.objects[1])
|
||||
end
|
||||
table.remove(self.objects, 1)
|
||||
end
|
||||
events.TICK:remove(self.managerName.."_tick")
|
||||
events.RENDER:remove(self.managerName.."_render")
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
---@class (exact) VanillaModel : AvatarModule バニラーのモデルの管理クラス
|
||||
|
||||
VanillaModel = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return VanillaModel
|
||||
new = function (parent)
|
||||
---@type VanillaModel
|
||||
local instance = Avatar.instantiate(VanillaModel, AvatarModule, parent)
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self AvatarModule
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
for _, vanillaModel in ipairs({vanilla_model.PLAYER, vanilla_model.CHESTPLATE_RIGHT_ARM, vanilla_model.CHESTPLATE_LEFT_ARM, vanilla_model.LEGGINGS_RIGHT_LEG, vanilla_model.LEGGINGS_LEFT_LEG, vanilla_model.BOOTS}) do
|
||||
vanillaModel:setVisible(false)
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
---@class ExSkill2WaveParticle : SpawnObject 水着のExスキルアニメーション後の波を表現するパーティクルの1グループを管理するクラス
|
||||
---@field package object Particle[] インスタンスで制御するオブジェクト
|
||||
---@field package pos Vector3 パーティクルの基準位置
|
||||
---@field package rot number パーティクルの向き(度数法)
|
||||
---@field package animationCount integer パーティクルの速度調整のためのティックカウンター
|
||||
---@field public new fun(parent: Avatar, pos: Vector3, rot: number): ExSkill2WaveParticle コンストラクタ
|
||||
|
||||
ExSkill2WaveParticle = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@param pos Vector3 パーティクルの基準位置
|
||||
---@param rot number パーティクルの向き(度数法)
|
||||
---@return ExSkill2WaveParticle
|
||||
new = function (parent, pos, rot)
|
||||
---@type ExSkill2WaveParticle
|
||||
local instance = Avatar.instantiate(ExSkill2WaveParticle, SpawnObject, parent)
|
||||
|
||||
instance.object = {}
|
||||
instance.pos = pos:copy()
|
||||
instance.rot = rot
|
||||
instance.animationCount = 0
|
||||
|
||||
instance.callbacks = {
|
||||
---@param self ExSkill2WaveParticle
|
||||
onInit = function (self)
|
||||
local particlePos = vectors.rotateAroundAxis(self.rot * -1 + 120, 0, 0, 0.5, 0, 1, 0):add(self.pos)
|
||||
local particleOffset = vectors.rotateAroundAxis(self.rot * -1 + 120, -1, 0, 0, 0, 1, 0)
|
||||
for i = -0.5, 0.5, 0.5 do
|
||||
for _ = 1, 3 do
|
||||
local colorFactor = math.random()
|
||||
table.insert(instance.object, particles:newParticle(self.parent.compatibilityUtils.getDustParticleId(vectors.vec3(1000000000, 1000000000, 1000000000), 2), particlePos:copy():add(particleOffset:copy():scale(i))):setVelocity(vectors.rotateAroundAxis(self.rot * -1 + 120, math.random() * 0.1 - 0.05, 0, 0.3, 0, 1, 0)):setColor(colorFactor, 1, 1):setLifetime(colorFactor * 25 + 5))
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
---@param self ExSkill2WaveParticle
|
||||
onTick = function (self)
|
||||
local velocityAddition = vectors.rotateAroundAxis(self.rot * -1 + 120, 0, math.cos(self.animationCount / 60 * 2 * math.pi) * 0.03, math.cos(self.animationCount / 80 * math.pi) * -0.02, 0, 1, 0)
|
||||
for _, particle in ipairs(self.object) do
|
||||
particle:setVelocity(particle:getVelocity():add(velocityAddition))
|
||||
end
|
||||
if self.animationCount == 30 then
|
||||
self.shouldDeinit = true
|
||||
else
|
||||
self.animationCount = self.animationCount + 1
|
||||
end
|
||||
end;
|
||||
}
|
||||
|
||||
return instance
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
---@class ExSkill2WaveParticleManager : SpawnObjectManager 水着のExスキルアニメーション後の波を表現するパーティクルを管理するクラス
|
||||
---@field package animationCount integer パーティクルの再生タイミングを計るカウンター
|
||||
---@field public getObject fun(self: ExSkill2WaveParticleManager, pos: Vector3, rot: number): ExSkill2WaveParticleManager パーティクルのインスタンスを生成して返す
|
||||
---@field public spawn fun(self: ExSkill2WaveParticleManager, pos: Vector3, rot: number) パーティクルを生成する
|
||||
---@field public play fun(self: ExSkill2WaveParticleManager) 波のパーティクルを再生する
|
||||
|
||||
ExSkill2WaveParticleManager = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return ExSkill2WaveParticleManager
|
||||
new = function (parent)
|
||||
---@type ExSkill2WaveParticleManager
|
||||
local instance = Avatar.instantiate(ExSkill2WaveParticleManager, SpawnObjectManager, parent)
|
||||
|
||||
instance.managerName = "ex_skill_2_particles"
|
||||
instance.animationCount = 0
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---パーティクルのインスタンスを生成して返す。
|
||||
---@param self ExSkill2WaveParticleManager
|
||||
---@param pos Vector3 パーティクルの基準位置
|
||||
---@param rot number パーティクルの向き(度数法)
|
||||
---@return ExSkill2WaveParticle instance 生成したインスタンス
|
||||
getObject = function (self, pos, rot)
|
||||
return ExSkill2WaveParticle.new(self.parent, pos, rot)
|
||||
end;
|
||||
|
||||
---パーティクルを生成する。
|
||||
---@param self ExSkill2WaveParticleManager
|
||||
---@param pos Vector3 パーティクルの基準位置
|
||||
---@param rot number パーティクルの向き(度数法)
|
||||
spawn = function (self, pos, rot)
|
||||
SpawnObjectManager.spawn(self, pos, rot)
|
||||
end;
|
||||
|
||||
---波のパーティクルを再生する。
|
||||
---@param self ExSkill2WaveParticleManager
|
||||
play = function (self)
|
||||
events.TICK:remove("ex_skill_2_particles_play_tick")
|
||||
|
||||
local playerPos = player:getPos()
|
||||
local bodyYaw = player:getBodyYaw()
|
||||
events.TICK:register(function ()
|
||||
self:spawn(playerPos, bodyYaw)
|
||||
if self.animationCount == 20 then
|
||||
events.TICK:remove("ex_skill_2_particles_play_tick")
|
||||
self.animationCount = 0
|
||||
else
|
||||
self.animationCount = self.animationCount + 1
|
||||
end
|
||||
end, "ex_skill_2_particles_play_tick")
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
---@class (exact) GunHoshino : Gun ホシノ専用gun.lua
|
||||
|
||||
GunHoshino = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return GunHoshino
|
||||
new = function (parent)
|
||||
---@type GunHoshino
|
||||
local instance = Avatar.instantiate(GunHoshino, Gun, parent)
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self GunHoshino
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
self:processGunTick()
|
||||
self.isGunTickProcessed = false
|
||||
end)
|
||||
|
||||
events.ON_PLAY_SOUND:register(function (id, pos, _, _, _, _, path)
|
||||
if path ~= nil then
|
||||
local velocityDistance = player:getVelocity():length()
|
||||
local distanceFromSound = math.abs(pos:copy():sub(player:getPos()):length() - velocityDistance)
|
||||
if (id == "minecraft:entity.arrow.shoot" or id == "minecraft:item.crossbow.loading_end" or id == "minecraft:item.crossbow.shoot") and math.abs(velocityDistance - distanceFromSound) < 1 then
|
||||
if id == "minecraft:item.crossbow.loading_end" then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.dispenser.fail"), pos, 1, 2)
|
||||
elseif player:isUnderwater() then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.generic.extinguish_fire"), pos, 0.5, 1.5)
|
||||
else
|
||||
local gunPosition = self.parent.gun.currentGunPosition
|
||||
if self.parent.subGun.hasSubGun and math.random() >= 0.5 then
|
||||
gunPosition = gunPosition == "RIGHT" and "LEFT" or "RIGHT"
|
||||
end
|
||||
local particleAnchor = ModelUtils.getModelWorldPos(renderer:isFirstPerson() and (gunPosition == "RIGHT" and models.models.main.Avatar.UpperBody.Arms.RightArm.RightArmBottom.RightItemPivot or models.models.main.Avatar.UpperBody.Arms.LeftArm.LeftArmBottom.LeftItemPivot) or (self.parent.gun.currentGunPosition == gunPosition and models.models.main.Avatar.UpperBody.Body.Gun.MuzzleAnchor or models.models.main.Avatar.UpperBody.Body.SubGun.MuzzleAnchor2))
|
||||
for _ = 1, 5 do
|
||||
particles:newParticle(self.parent.compatibilityUtils:checkParticle("minecraft:smoke"), particleAnchor)
|
||||
end
|
||||
if self.parent.gun.currentGunPosition == gunPosition then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound(self.parent.characterData.gun.sound.name), pos, 1, self.parent.characterData.gun.sound.pitch)
|
||||
else
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:entity.iron_golem.hurt"), pos, 1, 2)
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif (id == "minecraft:item.crossbow.loading_start" or id == "minecraft:item.crossbow.loading_middle" or id:match("^minecraft:item%.crossbow%.quick_charge_[1-3]$") ~= nil) and distanceFromSound < 1 and player:getActiveItem().id == "minecraft:crossbow" then
|
||||
local activeItemTime = player:getActiveItemTime()
|
||||
local quickChargeLevel = 0
|
||||
local activeItem = player:getActiveItem()
|
||||
if activeItem.tag.Enchantments ~= nil then
|
||||
for _, enchant in ipairs(activeItem.tag.Enchantments) do
|
||||
if enchant.id == "minecraft:quick_charge" then
|
||||
quickChargeLevel = enchant.lvl
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if (quickChargeLevel <= 4 and activeItemTime + quickChargeLevel >= 4 and activeItemTime + quickChargeLevel <= 6) or (quickChargeLevel == 5 and activeItemTime <= 2) then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:item.flintandsteel.use"), pos, 1, 2)
|
||||
return true
|
||||
elseif id == "minecraft:item.crossbow.loading_middle" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
events.ITEM_RENDER:register(function (item, mode, _, _, _, leftHanded)
|
||||
if mode ~= "HEAD" and self.currentGunPosition == (leftHanded and "LEFT" or "RIGHT") and (self.shouldShowWeaponInFirstPerson or mode =="THIRD_PERSON_LEFT_HAND" or mode == "THIRD_PERSON_RIGHT_HAND") then
|
||||
for _, gunItem in ipairs(self.gunItems) do
|
||||
if item.id == gunItem then
|
||||
if leftHanded then
|
||||
if mode == "FIRST_PERSON_LEFT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonPos.left ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.firstPersonPos.left
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonRot.left ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.firstPersonRot.left
|
||||
end
|
||||
local activeItemId = player:getActiveItem().id
|
||||
if activeItemId == "minecraft:bow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -2.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(20, -7.5, -5):add(offsetRot))
|
||||
elseif activeItemId == "minecraft:crossbow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, 0.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
elseif item.id == "minecraft:crossbow" and item.tag.Charged == 1 then
|
||||
if player:isLeftHanded() then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(-10, -1.25, 6):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 10, 0):add(offsetRot))
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
elseif mode == "THIRD_PERSON_LEFT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.left ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.left
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.left ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.left
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -4.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
else
|
||||
if mode == "FIRST_PERSON_RIGHT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonPos.right ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.firstPersonPos.right
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.firstPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.firstPersonRot.right ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.firstPersonRot.right
|
||||
end
|
||||
local activeItemId = player:getActiveItem().id
|
||||
if activeItemId == "minecraft:bow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -2.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(20, 7.5, 5):add(offsetRot))
|
||||
elseif activeItemId == "minecraft:crossbow" then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, 0.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
elseif item.id == "minecraft:crossbow" and item.tag.Charged == 1 then
|
||||
if player:isLeftHanded() then
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(10, -1.25, 6):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, -10, 0):add(offsetRot))
|
||||
end
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -1.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
elseif mode == "THIRD_PERSON_RIGHT_HAND" then
|
||||
local offsetPos = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonPos ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.right ~= nil then
|
||||
offsetPos = self.parent.characterData.gun.gunPosition.hold.thirdPersonPos.right
|
||||
end
|
||||
local offsetRot = vectors.vec3()
|
||||
if self.parent.characterData.gun.gunPosition.hold.thirdPersonRot ~= nil and self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.right ~= nil then
|
||||
offsetRot = self.parent.characterData.gun.gunPosition.hold.thirdPersonRot.right
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setPos(vectors.vec3(0, -4.25, 4.25):add(offsetPos))
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setRot(vectors.vec3(0, 0, 0):add(offsetRot))
|
||||
end
|
||||
end
|
||||
return models.models.main.Avatar.UpperBody.Body.Gun
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
models.models.main.Avatar.UpperBody.Body.Gun:setScale(vectors.vec3(1, 1, 1):scale(self.parent.characterData.gun.scale))
|
||||
self:setGunPosition("NONE")
|
||||
if self.parent.characterData.gun.callbacks ~= nil and self.parent.characterData.gun.callbacks.onMainHandChange ~= nil then
|
||||
self.parent.characterData.gun.callbacks.onMainHandChange(self.parent.characterData, self.isLeftHandedPrev and "LEFT" or "RIGHT")
|
||||
end
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
---@class (exact) Shield : AvatarModule 盾を制御するクラス
|
||||
---@field public hasShield boolean 盾を手に持っているかどうか
|
||||
---@field public setShield fun(self: Shield, value: boolean, shouldPlayShieldSound: boolean) 盾の展開状態を設定する
|
||||
|
||||
Shield = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return Shield
|
||||
new = function (parent)
|
||||
---@type Shield
|
||||
local instance = Avatar.instantiate(Shield, AvatarModule, parent)
|
||||
|
||||
instance.hasShield = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self Shield
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
|
||||
events.TICK:register(function ()
|
||||
self:setShield((player:getHeldItem().id == "minecraft:shield" or player:getHeldItem(true).id == "minecraft:shield") and self.parent.exSkill.animationCount == -1, true)
|
||||
end)
|
||||
|
||||
events.ITEM_RENDER:register(function (item, mode)
|
||||
if item.id == "minecraft:shield" and mode ~= "HEAD" and self.hasShield and (self.parent.gun.shouldShowWeaponInFirstPerson or mode == "THIRD_PERSON_LEFT_HAND" or mode == "THIRD_PERSON_RIGHT_HAND") then
|
||||
if mode == "FIRST_PERSON_LEFT_HAND" then
|
||||
local leftHanded = player:isLeftHanded()
|
||||
if player:getActiveItemTime() > 0 and ((player:getActiveHand() == "OFF_HAND" and not leftHanded) or (player:getActiveHand() == "MAIN_HAND" and leftHanded)) then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(8, -20.25, 2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(0, 0, -5)
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(6, -22.5, 2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(0, 0, 5)
|
||||
end
|
||||
elseif mode == "FIRST_PERSON_RIGHT_HAND" then
|
||||
local leftHanded = player:isLeftHanded()
|
||||
if player:getActiveItemTime() > 0 and ((player:getActiveHand() == "MAIN_HAND" and not leftHanded) or (player:getActiveHand() == "OFF_HAND" and leftHanded)) then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(0, -19.25, 2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(0, 0, 5)
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(2, -22.5, 2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(0, 0, -5)
|
||||
end
|
||||
elseif mode == "THIRD_PERSON_LEFT_HAND" then
|
||||
if self.parent.arms.armState.left == 4 then
|
||||
if player:isCrouching() then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(3.5, -19.5, 0)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(80, 5, 30)
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(2, -20.5, -1)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(55, 20, 25)
|
||||
end
|
||||
else
|
||||
local leftHanded = player:isLeftHanded()
|
||||
if player:getActiveItemTime() > 0 and ((player:getActiveHand() == "OFF_HAND" and not leftHanded) or (player:getActiveHand() == "MAIN_HAND" and leftHanded)) then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(2, -20.5, -2)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(50, 30, 30)
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(2, -20.5, 2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(5, 90, 0)
|
||||
end
|
||||
end
|
||||
elseif mode == "THIRD_PERSON_RIGHT_HAND" then
|
||||
if self.parent.arms.armState.right == 4 then
|
||||
if player:isCrouching() then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(4.5, -19.5, 0)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(80, -5, -30)
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(6, -20.5, -1)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(55, -20, -25)
|
||||
end
|
||||
else
|
||||
local leftHanded = player:isLeftHanded()
|
||||
if player:getActiveItemTime() > 0 and ((player:getActiveHand() == "MAIN_HAND" and not leftHanded) or (player:getActiveHand() == "OFF_HAND" and leftHanded)) then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(6, -20.5, -2)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(50, -30, -30)
|
||||
else
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos(6, -20.5, 2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(5, -90, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setSecondaryRenderType(item:hasGlint() and "GLINT" or "NONE")
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setVisible(true)
|
||||
return models.models.main.Avatar.UpperBody.Body.Shield
|
||||
end
|
||||
end)
|
||||
|
||||
events.ON_PLAY_SOUND:register(function (id, pos, _, _, _, _, path)
|
||||
if path ~= nil then
|
||||
if id == "minecraft:item.shield.block" and math.abs(pos:copy():sub(player:getPos()):length() - player:getVelocity():length()) < 0.2 and player:getActiveItem().id == "minecraft:shield" then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.anvil.place"), pos, 1, 4)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end)
|
||||
end;
|
||||
|
||||
---盾の展開状態を設定する。
|
||||
---@param self Shield
|
||||
---@param value boolean 新しい値
|
||||
---@param shouldPlayShieldSound boolean 盾の展開音を再生するかどうか
|
||||
setShield = function (self, value, shouldPlayShieldSound)
|
||||
if value and not self.hasShield then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setParentType("Item")
|
||||
models.models.main.Avatar.UpperBody.Body.Shield.Section2.ShoulderBelt:setVisible(false)
|
||||
for _, modelPart in ipairs({models.models.main.Avatar.UpperBody.Body.Shield.Section2, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1}) do
|
||||
modelPart:setRot()
|
||||
end
|
||||
for _, modelPart in ipairs({models.models.main.Avatar.UpperBody.Body.Shield.Section2.GasCylinder3.GasPiston3, models.models.main.Avatar.UpperBody.Body.Shield.Section2.GasCylinder4.GasPiston4, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1.GasCylinder1.GasPiston1, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1.GasCylinder2.GasPiston2}) do
|
||||
modelPart:setPos(0, -1.4, 0)
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Shield.Section3.Handle2:setPos(0, 0.25, 0)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1.Handle:setPos(0, -0.25, 0)
|
||||
if shouldPlayShieldSound then
|
||||
sounds:playSound(self.parent.compatibilityUtils:checkSound("minecraft:block.anvil.place"), player:getPos(), 0.1, 2)
|
||||
end
|
||||
elseif not value and self.hasShield then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setVisible(self.parent.costume.currentCostume ~= 3)
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setParentType("None")
|
||||
if self.parent.exSkill.animationCount == -1 then
|
||||
models.models.main.Avatar.UpperBody.Body.Shield.Section2.ShoulderBelt:setVisible(true)
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setPos()
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setRot(5, 90, 0)
|
||||
for _, modelPart in ipairs({models.models.main.Avatar.UpperBody.Body.Shield.Section2, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1}) do
|
||||
modelPart:setRot(-180, 0, 0)
|
||||
end
|
||||
for _, modelPart in ipairs({models.models.main.Avatar.UpperBody.Body.Shield.Section2.GasCylinder3.GasPiston3, models.models.main.Avatar.UpperBody.Body.Shield.Section2.GasCylinder4.GasPiston4, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1.GasCylinder1.GasPiston1, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1.GasCylinder2.GasPiston2, models.models.main.Avatar.UpperBody.Body.Shield.Section3.Handle2, models.models.main.Avatar.UpperBody.Body.Shield.Section2.Section1.Handle}) do
|
||||
modelPart:setPos()
|
||||
end
|
||||
models.models.main.Avatar.UpperBody.Body.Shield:setSecondaryRenderType("NONE")
|
||||
end
|
||||
self.hasShield = value
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
---@class (exact) SubGun : AvatarModule 臨戦衣装の拳銃を制御するクラス
|
||||
---@field public hasSubGun boolean サブハンドガンを持っているかどうか
|
||||
---@field public enable fun(self: SubGun) サブハンドガンを有効にする
|
||||
---@field public disable fun() サブハンドガンを無効にする
|
||||
|
||||
SubGun = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return SubGun
|
||||
new = function (parent)
|
||||
---@type SubGun
|
||||
local instance = Avatar.instantiate(SubGun, AvatarModule, parent)
|
||||
|
||||
instance.hasSubGun = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---サブハンドガンを有効にする。
|
||||
---@param self SubGun
|
||||
enable = function (self)
|
||||
events.TICK:register(function ()
|
||||
if self.parent.gun.currentGunPosition ~= "NONE" then
|
||||
local isLeftHanded = player:isLeftHanded()
|
||||
local heldItem = player:getHeldItem(self.parent.gun.currentGunPosition == "RIGHT" ~= isLeftHanded)
|
||||
self.hasSubGun = false
|
||||
for _, gunItem in ipairs(self.parent.gun.gunItems) do
|
||||
if gunItem == heldItem.id then
|
||||
self.hasSubGun = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.hasSubGun and self.parent.exSkill.animationCount == -1 then
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setScale(1.5, 1.5, 1.5)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setParentType("Item")
|
||||
elseif self.parent.exSkill.animationCount == -1 then
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setPos(-1, 17.5, -1.9)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setRot(-30, 90, 0)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setScale()
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setParentType("None")
|
||||
end
|
||||
end, "sun_gun_tick")
|
||||
events.ITEM_RENDER:register(function (_, mode)
|
||||
if self.hasSubGun then
|
||||
if self.parent.gun.currentGunPosition == "RIGHT" then
|
||||
if mode == "FIRST_PERSON_LEFT_HAND" then
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setPos(-1, 0.5, -2.5)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setRot()
|
||||
return models.models.main.Avatar.UpperBody.Body.SubGun
|
||||
elseif mode == "THIRD_PERSON_LEFT_HAND" then
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setPos(0, -2, -1)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setRot()
|
||||
return models.models.main.Avatar.UpperBody.Body.SubGun
|
||||
end
|
||||
elseif self.parent.gun.currentGunPosition == "LEFT" then
|
||||
if mode == "FIRST_PERSON_RIGHT_HAND" then
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setPos(-1, 0.5, -1)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setRot()
|
||||
return models.models.main.Avatar.UpperBody.Body.SubGun
|
||||
elseif mode == "THIRD_PERSON_RIGHT_HAND" then
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setPos(0, -2, -1)
|
||||
models.models.main.Avatar.UpperBody.Body.SubGun:setRot()
|
||||
return models.models.main.Avatar.UpperBody.Body.SubGun
|
||||
end
|
||||
end
|
||||
end
|
||||
end, "sun_gun_item_render")
|
||||
end;
|
||||
|
||||
---サブハンドガンを無効にする。
|
||||
---@param self SubGun
|
||||
disable = function (self)
|
||||
events.TICK:remove("sun_gun_tick")
|
||||
events.ITEM_RENDER:remove("sun_gun_item_render")
|
||||
self.hasSubGun = false
|
||||
end;
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
---@class (exact) WhaleFloat : AvatarModule クジラフロートを制御するクラス
|
||||
---@field public whaleFloatEnabled boolean クジラフローに乗っているか
|
||||
---@field package whaleFloatEnabledPrev boolean 前ティックにクジラフロートに乗っていたかどうか
|
||||
---@field package lookDirPrev Vector3 前ティックに見ていた方法
|
||||
---@field package whaleFloatAfkCount integer クジラフロート上でのAFKカウンター
|
||||
---@field public isAfk boolean AFK中かどうか
|
||||
---@field public enable fun(self: WhaleFloat) クジラフローを有効にする
|
||||
---@field public disable fun(self: WhaleFloat) クジラフローを無効にする
|
||||
|
||||
WhaleFloat = {
|
||||
---コンストラクタ
|
||||
---@param parent Avatar アバターのメインクラスへの参照
|
||||
---@return WhaleFloat
|
||||
new = function (parent)
|
||||
---@type WhaleFloat
|
||||
local instance = Avatar.instantiate(WhaleFloat, AvatarModule, parent)
|
||||
|
||||
instance.whaleFloatEnabled = false
|
||||
instance.whaleFloatEnabledPrev = false
|
||||
instance.lookDirPrev = player:getLookDir()
|
||||
instance.whaleFloatAfkCount = 0
|
||||
instance.isAfk = false
|
||||
|
||||
return instance
|
||||
end;
|
||||
|
||||
---初期化関数
|
||||
---@param self AvatarModule
|
||||
init = function (self)
|
||||
AvatarModule.init(self)
|
||||
end;
|
||||
|
||||
---クジラフローを有効にする。
|
||||
---@param self WhaleFloat
|
||||
enable = function (self)
|
||||
events.TICK:register(function ()
|
||||
local vehicle = player:getVehicle()
|
||||
if vehicle ~= nil then
|
||||
local id = vehicle:getType()
|
||||
local whaleFloatEnabled = self.parent.actionWheel.shouldReplaceVehicleModels and (id == "minecraft:boat" or id == "minecraft:chest_boat") and #vehicle:getPassengers() == 1
|
||||
if whaleFloatEnabled then
|
||||
if not self.whaleFloatEnabledPrev then
|
||||
models.models.main.Avatar.LowerBody.WhaleFloat:setVisible(true)
|
||||
renderer:setRenderVehicle(false)
|
||||
models.models.main.Avatar.Head:setRot(10, 0, 0)
|
||||
if self.parent.gun.currentGunPosition == "RIGHT" then
|
||||
self.parent.arms:setArmState(1, 2)
|
||||
elseif self.parent.gun.currentGunPosition == "LEFT" then
|
||||
self.parent.arms:setArmState(2, 1)
|
||||
else
|
||||
self.parent.arms:setArmState(5, 5)
|
||||
end
|
||||
for _, animationModel in ipairs({"models.main", "models.ex_skill_2"}) do
|
||||
animations[animationModel]["float_ride"]:play()
|
||||
end
|
||||
|
||||
events.TICK:register(function ()
|
||||
if world.getBlockState(player:getPos()).id == "minecraft:water" then
|
||||
animations["models.main"]["whale_float"]:setPlaying(true)
|
||||
if self.parent.physics.velocityAverage[5][2] >= 0.35 then
|
||||
self.parent.faceParts:setEmotion("CLOSED", "CLOSED", "W", 1)
|
||||
end
|
||||
if self.parent.physics.velocityAverage[5][2] >= 0.1 then
|
||||
local bodyYaw = player:getBodyYaw()
|
||||
local anchorPos = ModelUtils.getModelWorldPos(models.models.main.Avatar.LowerBody.WhaleFloat.WhaleParticleAnchor1):add(vectors.rotateAroundAxis(bodyYaw * -1, 0.1875, 0, 0, 0, 1, 0))
|
||||
for _ = 1, 5 do
|
||||
local particleDirection = math.random() * 60 - 30
|
||||
particleDirection = particleDirection > 0 and particleDirection + 30 or particleDirection - 30
|
||||
particles:newParticle(self.parent.compatibilityUtils.getDustParticleId(vectors.vec3(1000000000, 1000000000, 1000000000), 3), anchorPos):setVelocity(vectors.rotateAroundAxis(bodyYaw * -1 + particleDirection + 150, vectors.vec3(1, 1, 1), 0, 1, 0):scale(math.random()):normalize():scale(self.parent.physics.velocityAverage[5][2])):setGravity(0.5):setLifetime(10)
|
||||
end
|
||||
end
|
||||
|
||||
if player:getVehicle():getNbt().Type == "bamboo" then
|
||||
models.models.main.Avatar:setPos(0, -6, 0)
|
||||
self.parent.cameraManager.setCameraPivot(vectors.vec3(0, 0.1875, 0))
|
||||
renderer:setEyeOffset(0, 0.1875, 0)
|
||||
else
|
||||
models.models.main.Avatar:setPos()
|
||||
self.parent.cameraManager.setCameraPivot(vectors.vec3(0, 0.5625, 0))
|
||||
renderer:setEyeOffset(0, 0.5625, 0)
|
||||
end
|
||||
else
|
||||
animations["models.main"]["whale_float"]:setPlaying(false)
|
||||
if player:getVehicle():getNbt().Type == "bamboo" then
|
||||
models.models.main.Avatar:setPos(0, -9, 0)
|
||||
self.parent.cameraManager.setCameraPivot(vectors.vec3())
|
||||
renderer:setEyeOffset()
|
||||
else
|
||||
models.models.main.Avatar:setPos(0, -3, 0)
|
||||
self.parent.cameraManager.setCameraPivot(vectors.vec3(0, 0.375, 0))
|
||||
renderer:setEyeOffset(0, 0.375, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local lookDir = player:getLookDir()
|
||||
if player:getVelocity():length() < 0.01 and self.lookDirPrev:copy():sub(lookDir):length() == 0 and not player:isSwingingArm() and self.parent.playerUtils.damageStatus == "NONE" and player:getActiveItem().id == "minecraft:air" then
|
||||
self.whaleFloatAfkCount = self.whaleFloatAfkCount + 1
|
||||
if self.whaleFloatAfkCount == 2400 then
|
||||
self.isAfk = true
|
||||
for _, animationModel in ipairs({"models.main", "models.costume_swimsuit", "models.ex_skill_2"}) do
|
||||
animations[animationModel]["float_afk"]:setSpeed(1)
|
||||
animations[animationModel]["float_afk"]:play()
|
||||
end
|
||||
self.parent.arms:setArmState(0, 0)
|
||||
self.parent.physics:disable()
|
||||
elseif self.whaleFloatAfkCount >= 2430 then
|
||||
self.parent.faceParts:setEmotion("CLOSED2", "CLOSED2", "YAWN", 1, false)
|
||||
end
|
||||
else
|
||||
if self.isAfk then
|
||||
self.isAfk = false
|
||||
for _, animationModel in ipairs({"models.main", "models.costume_swimsuit", "models.ex_skill_2"}) do
|
||||
animations[animationModel]["float_afk"]:setSpeed(-1)
|
||||
end
|
||||
events.TICK:remove("whale_float_afk_end_tick")
|
||||
events.TICK:register(function ()
|
||||
if animations["models.main"]["float_afk"]:getTime() == 0 then
|
||||
for _, animationModel in ipairs({"models.main", "models.costume_swimsuit", "models.ex_skill_2"}) do
|
||||
animations[animationModel]["float_afk"]:stop()
|
||||
end
|
||||
if self.parent.gun.currentGunPosition == "RIGHT" then
|
||||
self.parent.arms:setArmState(1, 2)
|
||||
elseif self.parent.gun.currentGunPosition == "LEFT" then
|
||||
self.parent.arms:setArmState(2, 1)
|
||||
else
|
||||
self.parent.arms:setArmState(5, 5)
|
||||
end
|
||||
self.parent.physics:enable()
|
||||
events.TICK:remove("whale_float_afk_end_tick")
|
||||
end
|
||||
end, "whale_float_afk_end_tick")
|
||||
end
|
||||
self.lookDirPrev = lookDir
|
||||
end
|
||||
end, "whale_float_tick_2")
|
||||
end
|
||||
self.whaleFloatEnabledPrev = true
|
||||
elseif self.whaleFloatEnabledPrev then
|
||||
self:disable()
|
||||
end
|
||||
elseif self.whaleFloatEnabledPrev then
|
||||
self.whaleFloatEnabled = false
|
||||
self:disable()
|
||||
end
|
||||
end, "whale_float_tick")
|
||||
end;
|
||||
|
||||
---クジラフロートを無効にする。
|
||||
---@param self WhaleFloat
|
||||
disable = function (self)
|
||||
for _, eventName in ipairs({"whale_float_tick", "whale_float_tick_2", "whale_float_afk_end_tick"}) do
|
||||
events.TICK:remove(eventName)
|
||||
end
|
||||
models.models.main.Avatar.LowerBody.WhaleFloat:setVisible(false)
|
||||
renderer:setRenderVehicle(true)
|
||||
models.models.main.Avatar.Head:setRot()
|
||||
if self.parent.gun.currentGunPosition == "RIGHT" then
|
||||
self.parent.arms:setArmState(1, 2)
|
||||
elseif self.parent.gun.currentGunPosition == "LEFT" then
|
||||
self.parent.arms:setArmState(2, 1)
|
||||
else
|
||||
self.parent.arms:setArmState(0, 0)
|
||||
end
|
||||
for _, animationModel in ipairs({"models.main", "models.ex_skill_2"}) do
|
||||
animations[animationModel]["float_ride"]:stop()
|
||||
animations[animationModel]["float_afk"]:stop()
|
||||
end
|
||||
animations["models.costume_swimsuit"]["float_afk"]:stop()
|
||||
animations["models.main"]["whale_float"]:stop()
|
||||
models.models.main.Avatar:setPos()
|
||||
self.parent.cameraManager.setCameraPivot(vectors.vec3())
|
||||
renderer:setEyeOffset()
|
||||
self.whaleFloatAfkCount = 0
|
||||
self.isAfk = false
|
||||
self.whaleFloatEnabledPrev = false
|
||||
end;
|
||||
}
|
BIN
config/yosbr/config/figura/avatars/Hoshino/textures/barrier.png
Normal file
After Width: | Height: | Size: 103 B |
BIN
config/yosbr/config/figura/avatars/Hoshino/textures/bubble.png
Normal file
After Width: | Height: | Size: 203 B |
After Width: | Height: | Size: 494 B |
BIN
config/yosbr/config/figura/avatars/Hoshino/textures/bullet.png
Normal file
After Width: | Height: | Size: 210 B |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 281 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 160 B |
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 220 B |
After Width: | Height: | Size: 219 B |
After Width: | Height: | Size: 223 B |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 169 B |
After Width: | Height: | Size: 224 B |
After Width: | Height: | Size: 272 B |
After Width: | Height: | Size: 507 B |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 204 B |
After Width: | Height: | Size: 410 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 184 B |
After Width: | Height: | Size: 322 B |
After Width: | Height: | Size: 181 B |
After Width: | Height: | Size: 970 B |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 207 B |
After Width: | Height: | Size: 161 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1,007 B |
BIN
config/yosbr/config/figura/avatars/Hoshino/textures/gun.png
Normal file
After Width: | Height: | Size: 1.4 KiB |