diff --git a/exported_done/aip_dou_totem/aip_dou_totem.scml b/exported/aip_dou_totem/aip_dou_totem.scml
similarity index 86%
rename from exported_done/aip_dou_totem/aip_dou_totem.scml
rename to exported/aip_dou_totem/aip_dou_totem.scml
index f6ec2306..2c8d7082 100644
--- a/exported_done/aip_dou_totem/aip_dou_totem.scml
+++ b/exported/aip_dou_totem/aip_dou_totem.scml
@@ -8,6 +8,12 @@
+
+
+
+
+
+
@@ -15,11 +21,22 @@
+
+
-
+
+
+
+
+
+
+
+
+
+
@@ -36,8 +53,24 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -85,10 +118,10 @@
-
+
-
+
diff --git a/exported/aip_dou_totem/fire_marker_left/fire_marker_left.png b/exported/aip_dou_totem/fire_marker_left/fire_marker_left.png
new file mode 100644
index 00000000..293bb3ff
Binary files /dev/null and b/exported/aip_dou_totem/fire_marker_left/fire_marker_left.png differ
diff --git a/exported/aip_dou_totem/fire_marker_right/fire_marker_right.png b/exported/aip_dou_totem/fire_marker_right/fire_marker_right.png
new file mode 100644
index 00000000..293bb3ff
Binary files /dev/null and b/exported/aip_dou_totem/fire_marker_right/fire_marker_right.png differ
diff --git a/exported_done/aip_dou_totem/totem/full.png b/exported/aip_dou_totem/totem/full.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/full.png
rename to exported/aip_dou_totem/totem/full.png
diff --git a/exported_done/aip_dou_totem/totem/half.png b/exported/aip_dou_totem/totem/half.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/half.png
rename to exported/aip_dou_totem/totem/half.png
diff --git a/exported_done/aip_dou_totem/totem/half.sai2 b/exported/aip_dou_totem/totem/half.sai2
similarity index 100%
rename from exported_done/aip_dou_totem/totem/half.sai2
rename to exported/aip_dou_totem/totem/half.sai2
diff --git a/exported_done/aip_dou_totem/totem/head.png b/exported/aip_dou_totem/totem/head.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/head.png
rename to exported/aip_dou_totem/totem/head.png
diff --git a/exported_done/aip_dou_totem/totem/left.png b/exported/aip_dou_totem/totem/left.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/left.png
rename to exported/aip_dou_totem/totem/left.png
diff --git a/exported_done/aip_dou_totem/totem/nest.png b/exported/aip_dou_totem/totem/nest.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/nest.png
rename to exported/aip_dou_totem/totem/nest.png
diff --git a/exported_done/aip_dou_totem/totem/nest.sai2 b/exported/aip_dou_totem/totem/nest.sai2
similarity index 100%
rename from exported_done/aip_dou_totem/totem/nest.sai2
rename to exported/aip_dou_totem/totem/nest.sai2
diff --git a/exported_done/aip_dou_totem/totem/right.png b/exported/aip_dou_totem/totem/right.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/right.png
rename to exported/aip_dou_totem/totem/right.png
diff --git a/exported_done/aip_dou_totem/totem/totem.sai2 b/exported/aip_dou_totem/totem/totem.sai2
similarity index 100%
rename from exported_done/aip_dou_totem/totem/totem.sai2
rename to exported/aip_dou_totem/totem/totem.sai2
diff --git a/exported_done/aip_dou_totem/totem/up.png b/exported/aip_dou_totem/totem/up.png
similarity index 100%
rename from exported_done/aip_dou_totem/totem/up.png
rename to exported/aip_dou_totem/totem/up.png
diff --git a/exported/aip_fake_fly_totem/aip_fake_fly_totem.scml b/exported/aip_fake_fly_totem/aip_fake_fly_totem.scml
new file mode 100644
index 00000000..54df2f5e
--- /dev/null
+++ b/exported/aip_fake_fly_totem/aip_fake_fly_totem.scml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_fake_fly_totem/body/lighthouse_ground-1.png b/exported/aip_fake_fly_totem/body/lighthouse_ground-1.png
new file mode 100644
index 00000000..304e09ae
Binary files /dev/null and b/exported/aip_fake_fly_totem/body/lighthouse_ground-1.png differ
diff --git a/exported/aip_fake_fly_totem/body/totem.png b/exported/aip_fake_fly_totem/body/totem.png
new file mode 100644
index 00000000..7c9b82f4
Binary files /dev/null and b/exported/aip_fake_fly_totem/body/totem.png differ
diff --git a/exported/aip_fake_fly_totem/body/totem.sai2 b/exported/aip_fake_fly_totem/body/totem.sai2
new file mode 100644
index 00000000..d1f6c841
Binary files /dev/null and b/exported/aip_fake_fly_totem/body/totem.sai2 differ
diff --git a/exported/aip_legion/aip_legion.scml b/exported/aip_legion/aip_legion.scml
new file mode 100644
index 00000000..d2615c1e
--- /dev/null
+++ b/exported/aip_legion/aip_legion.scml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_legion/body/full.png b/exported/aip_legion/body/full.png
new file mode 100644
index 00000000..ffd295c0
Binary files /dev/null and b/exported/aip_legion/body/full.png differ
diff --git a/exported/aip_legion/body/full.sai2 b/exported/aip_legion/body/full.sai2
new file mode 100644
index 00000000..7d0b70a7
Binary files /dev/null and b/exported/aip_legion/body/full.sai2 differ
diff --git a/exported/aip_rubik/aip_rubik.scml b/exported/aip_rubik/aip_rubik.scml
new file mode 100644
index 00000000..7ecc5593
--- /dev/null
+++ b/exported/aip_rubik/aip_rubik.scml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_rubik/body/full.png b/exported/aip_rubik/body/full.png
new file mode 100644
index 00000000..d6bbf217
Binary files /dev/null and b/exported/aip_rubik/body/full.png differ
diff --git a/exported/aip_rubik/body/full.sai2 b/exported/aip_rubik/body/full.sai2
new file mode 100644
index 00000000..456b7629
Binary files /dev/null and b/exported/aip_rubik/body/full.sai2 differ
diff --git a/exported/aip_rubik/fire_marker/fire_marker.png b/exported/aip_rubik/fire_marker/fire_marker.png
new file mode 100644
index 00000000..293bb3ff
Binary files /dev/null and b/exported/aip_rubik/fire_marker/fire_marker.png differ
diff --git a/exported/aip_rubik_fire/aip_rubik_fire.scml b/exported/aip_rubik_fire/aip_rubik_fire.scml
new file mode 100644
index 00000000..fd99b005
--- /dev/null
+++ b/exported/aip_rubik_fire/aip_rubik_fire.scml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_rubik_fire/body/00000002 b/exported/aip_rubik_fire/body/00000002
new file mode 100644
index 00000000..7599f92d
Binary files /dev/null and b/exported/aip_rubik_fire/body/00000002 differ
diff --git a/exported/aip_rubik_fire/body/fire.png b/exported/aip_rubik_fire/body/fire.png
new file mode 100644
index 00000000..735c16a2
Binary files /dev/null and b/exported/aip_rubik_fire/body/fire.png differ
diff --git a/exported/aip_rubik_fire/body/light.png b/exported/aip_rubik_fire/body/light.png
new file mode 100644
index 00000000..64679311
Binary files /dev/null and b/exported/aip_rubik_fire/body/light.png differ
diff --git a/exported/aip_rubik_fire/body/next.png b/exported/aip_rubik_fire/body/next.png
new file mode 100644
index 00000000..f364b244
Binary files /dev/null and b/exported/aip_rubik_fire/body/next.png differ
diff --git a/exported/aip_rubik_ghost/aip_rubik_ghost.scml b/exported/aip_rubik_ghost/aip_rubik_ghost.scml
new file mode 100644
index 00000000..37c38ea6
--- /dev/null
+++ b/exported/aip_rubik_ghost/aip_rubik_ghost.scml
@@ -0,0 +1,572 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_rubik_ghost/body/body.png b/exported/aip_rubik_ghost/body/body.png
new file mode 100644
index 00000000..2eb35cb5
Binary files /dev/null and b/exported/aip_rubik_ghost/body/body.png differ
diff --git a/exported/aip_rubik_ghost/body/full.sai2 b/exported/aip_rubik_ghost/body/full.sai2
new file mode 100644
index 00000000..db579742
Binary files /dev/null and b/exported/aip_rubik_ghost/body/full.sai2 differ
diff --git a/exported/aip_rubik_ghost/body/hair1.png b/exported/aip_rubik_ghost/body/hair1.png
new file mode 100644
index 00000000..93c801a4
Binary files /dev/null and b/exported/aip_rubik_ghost/body/hair1.png differ
diff --git a/exported/aip_rubik_ghost/body/hair2.png b/exported/aip_rubik_ghost/body/hair2.png
new file mode 100644
index 00000000..ead8694e
Binary files /dev/null and b/exported/aip_rubik_ghost/body/hair2.png differ
diff --git a/exported/aip_rubik_ghost/body/hair3.png b/exported/aip_rubik_ghost/body/hair3.png
new file mode 100644
index 00000000..6e19f5c8
Binary files /dev/null and b/exported/aip_rubik_ghost/body/hair3.png differ
diff --git a/exported/aip_rubik_ghost/body/head.png b/exported/aip_rubik_ghost/body/head.png
new file mode 100644
index 00000000..7ef9b773
Binary files /dev/null and b/exported/aip_rubik_ghost/body/head.png differ
diff --git a/exported/aip_rubik_ghost/body/jaw.png b/exported/aip_rubik_ghost/body/jaw.png
new file mode 100644
index 00000000..78a02b38
Binary files /dev/null and b/exported/aip_rubik_ghost/body/jaw.png differ
diff --git a/exported/aip_rubik_ghost/body/tail.png b/exported/aip_rubik_ghost/body/tail.png
new file mode 100644
index 00000000..c4c7de9d
Binary files /dev/null and b/exported/aip_rubik_ghost/body/tail.png differ
diff --git a/exported/aip_rubik_heart/aip_rubik_heart.scml b/exported/aip_rubik_heart/aip_rubik_heart.scml
new file mode 100644
index 00000000..a95b0edc
--- /dev/null
+++ b/exported/aip_rubik_heart/aip_rubik_heart.scml
@@ -0,0 +1,455 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_rubik_heart/body/down.png b/exported/aip_rubik_heart/body/down.png
new file mode 100644
index 00000000..a6b7ec89
Binary files /dev/null and b/exported/aip_rubik_heart/body/down.png differ
diff --git a/exported/aip_rubik_heart/body/full.sai2 b/exported/aip_rubik_heart/body/full.sai2
new file mode 100644
index 00000000..e1d7bb6d
Binary files /dev/null and b/exported/aip_rubik_heart/body/full.sai2 differ
diff --git a/exported/aip_rubik_heart/body/left.png b/exported/aip_rubik_heart/body/left.png
new file mode 100644
index 00000000..9c1fbd25
Binary files /dev/null and b/exported/aip_rubik_heart/body/left.png differ
diff --git a/exported/aip_rubik_heart/body/right.png b/exported/aip_rubik_heart/body/right.png
new file mode 100644
index 00000000..6c3eb148
Binary files /dev/null and b/exported/aip_rubik_heart/body/right.png differ
diff --git a/exported/aip_rubik_heart/body/up.png b/exported/aip_rubik_heart/body/up.png
new file mode 100644
index 00000000..1098c8b4
Binary files /dev/null and b/exported/aip_rubik_heart/body/up.png differ
diff --git a/exported/aip_wizard_hat/aip_wizard_hat.scml b/exported/aip_wizard_hat/aip_wizard_hat.scml
new file mode 100644
index 00000000..614a65cf
--- /dev/null
+++ b/exported/aip_wizard_hat/aip_wizard_hat.scml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exported/aip_wizard_hat/swap_hat/aip_joker_face-0.sai2 b/exported/aip_wizard_hat/swap_hat/aip_joker_face-0.sai2
new file mode 100644
index 00000000..063f990b
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/aip_joker_face-0.sai2 differ
diff --git a/exported/aip_wizard_hat/swap_hat/aip_joker_face-3.sai2 b/exported/aip_wizard_hat/swap_hat/aip_joker_face-3.sai2
new file mode 100644
index 00000000..b111cb84
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/aip_joker_face-3.sai2 differ
diff --git a/exported/aip_wizard_hat/swap_hat/baseline-0.png b/exported/aip_wizard_hat/swap_hat/baseline-0.png
new file mode 100644
index 00000000..87cf80ad
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/baseline-0.png differ
diff --git a/exported/aip_wizard_hat/swap_hat/swap_hat-0.png b/exported/aip_wizard_hat/swap_hat/swap_hat-0.png
new file mode 100644
index 00000000..c22041a3
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/swap_hat-0.png differ
diff --git a/exported/aip_wizard_hat/swap_hat/swap_hat-1.png b/exported/aip_wizard_hat/swap_hat/swap_hat-1.png
new file mode 100644
index 00000000..83622457
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/swap_hat-1.png differ
diff --git a/exported/aip_wizard_hat/swap_hat/swap_hat-2.png b/exported/aip_wizard_hat/swap_hat/swap_hat-2.png
new file mode 100644
index 00000000..c22041a3
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/swap_hat-2.png differ
diff --git a/exported/aip_wizard_hat/swap_hat/swap_hat-3.png b/exported/aip_wizard_hat/swap_hat/swap_hat-3.png
new file mode 100644
index 00000000..0cc2358e
Binary files /dev/null and b/exported/aip_wizard_hat/swap_hat/swap_hat-3.png differ
diff --git a/images/aipWidgets/aipRubik.sai2 b/images/aipWidgets/aipRubik.sai2
new file mode 100644
index 00000000..03c0e901
Binary files /dev/null and b/images/aipWidgets/aipRubik.sai2 differ
diff --git a/images/inventoryimages/aip_fake_fly_totem.png b/images/inventoryimages/aip_fake_fly_totem.png
new file mode 100644
index 00000000..20a429e1
Binary files /dev/null and b/images/inventoryimages/aip_fake_fly_totem.png differ
diff --git a/images/inventoryimages/aip_fake_fly_totem.tex b/images/inventoryimages/aip_fake_fly_totem.tex
new file mode 100644
index 00000000..e6912428
Binary files /dev/null and b/images/inventoryimages/aip_fake_fly_totem.tex differ
diff --git a/images/inventoryimages/aip_fake_fly_totem.xml b/images/inventoryimages/aip_fake_fly_totem.xml
new file mode 100644
index 00000000..ff74faed
--- /dev/null
+++ b/images/inventoryimages/aip_fake_fly_totem.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/inventoryimages/aip_legion.png b/images/inventoryimages/aip_legion.png
new file mode 100644
index 00000000..35c9d28e
Binary files /dev/null and b/images/inventoryimages/aip_legion.png differ
diff --git a/images/inventoryimages/aip_legion.tex b/images/inventoryimages/aip_legion.tex
new file mode 100644
index 00000000..3b943457
Binary files /dev/null and b/images/inventoryimages/aip_legion.tex differ
diff --git a/images/inventoryimages/aip_legion.xml b/images/inventoryimages/aip_legion.xml
new file mode 100644
index 00000000..b54ca2a2
--- /dev/null
+++ b/images/inventoryimages/aip_legion.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/inventoryimages/aip_wizard_hat.png b/images/inventoryimages/aip_wizard_hat.png
new file mode 100644
index 00000000..c7bf1ac0
Binary files /dev/null and b/images/inventoryimages/aip_wizard_hat.png differ
diff --git a/images/inventoryimages/aip_wizard_hat.tex b/images/inventoryimages/aip_wizard_hat.tex
new file mode 100644
index 00000000..fe8cec57
Binary files /dev/null and b/images/inventoryimages/aip_wizard_hat.tex differ
diff --git a/images/inventoryimages/aip_wizard_hat.xml b/images/inventoryimages/aip_wizard_hat.xml
new file mode 100644
index 00000000..9ddf523a
--- /dev/null
+++ b/images/inventoryimages/aip_wizard_hat.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modmain.lua b/modmain.lua
index 90bfcb67..e84134c0 100644
--- a/modmain.lua
+++ b/modmain.lua
@@ -89,8 +89,12 @@ PrefabFiles = {
"aip_suwu",
"aip_suwu_mound",
"aip_breadfruit_tree",
- -- "aip_rubik_fire",
- -- "aip_rubik",
+ "aip_rubik_fire",
+ "aip_rubik",
+ "aip_legion",
+ "aip_rubik_ghost",
+ "aip_rubik_heart",
+ "aip_wizard_hat",
-- Orbit
"aip_orbit",
@@ -165,6 +169,8 @@ modimport("scripts/shadowPackageAction.lua")
modimport("scripts/widgetHooker.lua")
modimport("scripts/recpiesHooker.lua")
modimport("scripts/flyWrapper.lua")
+modimport("scripts/houseWrapper.lua")
+modimport("scripts/sgHooker.lua")
------------------------------------- 测试专用 -------------------------------------
if GetModConfigData("dev_mode") == "enabled" then
diff --git a/scripts/aipUtils.lua b/scripts/aipUtils.lua
index ac4e5c83..f0e4e53f 100644
--- a/scripts/aipUtils.lua
+++ b/scripts/aipUtils.lua
@@ -62,8 +62,10 @@ end
function _G.aipTableSlice(tbl, start, len)
local list = {}
+ local finalStart = start or 1
+ local finalLen = len or #tbl
- for i = start, math.min(len + start - 1, #tbl) do
+ for i = finalStart, math.min(finalLen + finalStart - 1, #tbl) do
table.insert(list, tbl[i])
end
return list
@@ -138,7 +140,11 @@ function _G.aipCommonStr(showType, split, ...)
parsed = parsed .. "}"
end
- str = str .. "[" .. vType .. ": " .. tostring(parsed) .. "]" .. split
+ if vType == "string" then
+ str = str.."'"..tostring(parsed).."'"..split
+ else
+ str = str .. "[" .. vType .. ": " .. tostring(parsed) .. "]" .. split
+ end
else
-- 显示文字
str = str .. tostring(parsed) .. split
@@ -236,11 +242,17 @@ function _G.aipDiffAngle(a1, a2)
return math.min(diff1, diff2)
end
--- 返回两点之间的距离(无视 Y 坐标)
-function _G.aipDist(p1, p2)
+-- 返回两点之间的距离(默认无视 Y 坐标)
+function _G.aipDist(p1, p2, includeY)
local dx = p1.x - p2.x
local dz = p1.z - p2.z
- return math.pow(dx*dx+dz*dz, 0.5)
+ local dy = p1.y - p2.y
+
+ if includeY then
+ return math.pow(dx*dx+dy*dy+dz*dz, 1/3)
+ else
+ return math.pow(dx*dx+dz*dz, 0.5)
+ end
end
function _G.aipRandomEnt(ents)
@@ -500,9 +512,13 @@ function _G.aipFindEnt(...)
end
-- 是暗影生物
-_G.aipShadowTags = { "shadow", "shadowminion", "shadowchesspiece", "stalker", "stalkerminion" }
+_G.aipShadowTags = { "shadow", "shadowminion", "shadowchesspiece", "stalker", "stalkerminion", "aip_shadowcreature" }
function _G.aipIsShadowCreature(inst)
+ if not inst then
+ return false
+ end
+
for i, tag in ipairs(_G.aipShadowTags) do
if inst:HasTag(tag) then
return true
@@ -518,18 +534,22 @@ function _G.aipRPC(funcName, ...)
end
-- 添加 aipc_buffer
-function _G.patchBuffer(inst, name, duration, fn)
+function _G.patchBuffer(inst, name, duration, fn, showFX)
if inst.components.aipc_buffer == nil then
inst:AddComponent("aipc_buffer")
end
- inst.components.aipc_buffer:Patch(name, duration, fn)
+ inst.components.aipc_buffer:Patch(name, duration, fn, showFX)
end
-- 存在 aipc_buffer
function _G.hasBuffer(inst, name)
- if inst.components.aipc_buffer ~= nil then
- return inst.components.aipc_buffer.buffers[name] ~= nil
+ -- if inst.components.aipc_buffer ~= nil then
+ -- return inst.components.aipc_buffer.buffers[name] ~= nil
+ -- end
+
+ if inst.replica.aipc_buffer ~= nil then
+ return inst.replica.aipc_buffer:HasBuffer(name)
end
return false
diff --git a/scripts/brains/aip_rubik_ghost_brain.lua b/scripts/brains/aip_rubik_ghost_brain.lua
new file mode 100644
index 00000000..c8985a4a
--- /dev/null
+++ b/scripts/brains/aip_rubik_ghost_brain.lua
@@ -0,0 +1,55 @@
+require "behaviours/wander"
+require "behaviours/chaseandattack"
+
+local NOTAGS = { "FX", "NOCLICK", "DECOR", "playerghost", "INLIMBO" }
+
+local MAX_CHASE_DIST = 15
+local MAX_CHASE_TIME = 8
+
+local MAX_WANDER_DIST = 20
+
+local RUN_AWAY_DIST = 3
+local STOP_RUN_AWAY_DIST = 5
+local STOP_RUN_AWAY_DIST_MAX = 8
+
+local RubikGhostBrain = Class(Brain, function(self, inst)
+ Brain._ctor(self, inst)
+ self.mytarget = nil
+end)
+
+function RubikGhostBrain:OnStop()
+end
+
+function RubikGhostBrain:OnStart()
+ local root = PriorityNode({
+ -- 寻找目标攻击
+ WhileNode(
+ function()
+ return self.inst.components.combat.target == nil or not self.inst.components.combat:InCooldown()
+ end,
+ "AttackMomentarily",
+ ChaseAndAttack(self.inst, MAX_CHASE_TIME, MAX_CHASE_DIST)
+ ),
+
+ -- 躲闪
+ WhileNode(
+ function()
+ return self.inst.components.combat.target and self.inst.components.combat:InCooldown()
+ end,
+ "Dodge",
+ RunAway(
+ self.inst,
+ function() return self.inst.components.combat.target end,
+ RUN_AWAY_DIST,
+ math.random(STOP_RUN_AWAY_DIST, STOP_RUN_AWAY_DIST_MAX)
+ )
+ ),
+
+ -- 漫步
+ Wander(self.inst, function() return self.inst.components.knownlocations:GetLocation("home") end, MAX_WANDER_DIST),
+ }, .25)
+
+ self.bt = BT(self.inst, root)
+end
+
+return RubikGhostBrain
\ No newline at end of file
diff --git a/scripts/components/aipc_aura.lua b/scripts/components/aipc_aura.lua
index b08f8652..ddb5d362 100644
--- a/scripts/components/aipc_aura.lua
+++ b/scripts/components/aipc_aura.lua
@@ -7,6 +7,7 @@ local Aura = Class(function(self, inst)
self.bufferFn = nil
self.mustTags = nil
self.noTags = nil
+ self.showFX = true
self:Start()
end)
@@ -16,7 +17,7 @@ local function SearchToAddBuffer(inst, self)
local ents = TheSim:FindEntities(x, y, z, self.range, self.mustTags, self.noTags)
for i, ent in ipairs(ents) do
- patchBuffer(ent, self.bufferName, self.bufferDuration, self.bufferFn)
+ patchBuffer(ent, self.bufferName, self.bufferDuration, self.bufferFn, self.showFX)
end
end
diff --git a/scripts/components/aipc_buffer.lua b/scripts/components/aipc_buffer.lua
index 78e5fcde..8929e802 100644
--- a/scripts/components/aipc_buffer.lua
+++ b/scripts/components/aipc_buffer.lua
@@ -19,6 +19,7 @@ local function DoEffect(inst, self)
end
self.buffers = aipFilterKeysTable(self.buffers, rmNames)
+ self:SyncBuffer()
if allRemove then
inst:RemoveComponent("aipc_buffer")
@@ -41,11 +42,31 @@ local Buffer = Class(function(self, inst)
self.inst:AddChild(self.fx)
end)
-function Buffer:Patch(name, duration, fn)
+function Buffer:Patch(name, duration, fn, showFX)
self.buffers[name] = {
duration = duration or 2,
- fn = fn
+ fn = fn,
+ showFX = showFX,
}
+
+ self:SyncBuffer()
+end
+
+function Buffer:SyncBuffer(ames)
+ local names = ""
+ local showFX = false
+ for name, ent in pairs(self.buffers) do
+ names = names.."|"..name
+ showFX = showFX or ent.showFX
+ end
+
+ if showFX then
+ self.fx:Show()
+ else
+ self.fx:Hide()
+ end
+
+ self.inst.replica.aipc_buffer:SyncBuffer(names)
end
return Buffer
\ No newline at end of file
diff --git a/scripts/components/aipc_buffer_replica.lua b/scripts/components/aipc_buffer_replica.lua
new file mode 100644
index 00000000..b8657248
--- /dev/null
+++ b/scripts/components/aipc_buffer_replica.lua
@@ -0,0 +1,23 @@
+local Buffer = Class(function(self, inst)
+ self.inst = inst
+ self.bufferNames = net_string(inst.GUID, "aipc_buffer", "aipc_buffer_dirty")
+end)
+
+function Buffer:SyncBuffer(names)
+ self.bufferNames:set(names)
+end
+
+function Buffer:HasBuffer(tgt)
+ local names = self.bufferNames:value()
+ local nameList = string.split(names, '|')
+
+ for i, name in ipairs(nameList) do
+ if name == tgt then
+ return true
+ end
+ end
+
+ return false
+end
+
+return Buffer
\ No newline at end of file
diff --git a/scripts/components/aipc_float.lua b/scripts/components/aipc_float.lua
new file mode 100644
index 00000000..81adfaee
--- /dev/null
+++ b/scripts/components/aipc_float.lua
@@ -0,0 +1,51 @@
+-- 一个只会飞行到目标地点的投掷物
+local Float = Class(function(self, inst)
+ self.inst = inst
+ self.targetPos = nil
+ self.speed = 6
+ self.ySpeed = 6
+end)
+
+-- 转向目标点
+function Float:RotateToTarget(dest)
+ local angle = aipGetAngle(self.inst:GetPosition(), dest)
+ self.inst.Transform:SetRotation(angle)
+ self.inst:FacePoint(dest)
+end
+
+-- 重置坐标
+function Float:GoToPoint(pt)
+ self.targetPos = pt
+ self.inst.Physics:Teleport(pt.x, pt.y, pt.z)
+
+ self.inst:StartUpdatingComponent(self)
+end
+
+-- 飞向目标
+function Float:MoveToPoint(pt)
+ self.targetPos = pt
+end
+
+function Float:OnUpdate(dt)
+ local pos = self.inst:GetPosition()
+ if not self.targetPos then
+ return
+ end
+
+ -- 水平方向的距离
+ local dist = aipDist(pos, self.targetPos)
+ local speed = self.speed
+ if dist < 0.3 then
+ speed = 0.5
+ end
+
+ -- 朝一个方向飞去
+ self:RotateToTarget(self.targetPos)
+ self.inst.Physics:SetMotorVel(
+ speed,
+ (self.targetPos.y - pos.y) * self.ySpeed,
+ 0
+ )
+end
+
+return Float
\ No newline at end of file
diff --git a/scripts/components/aipc_flyer_sc.lua b/scripts/components/aipc_flyer_sc.lua
index 58a3f4a4..c2a279f5 100644
--- a/scripts/components/aipc_flyer_sc.lua
+++ b/scripts/components/aipc_flyer_sc.lua
@@ -36,8 +36,13 @@ local function IsNearDanger(inst)
inst,
10,
function(target)
- return (target.components.combat ~= nil and target.components.combat.target == inst) or
- (target:HasTag("monster") and not target:HasTag("player"))
+ return (
+ target.components.combat ~= nil and
+ target.components.combat.target == inst and
+ target.prefab ~= "shadowtentacle"
+ ) or (
+ target:HasTag("monster") and not target:HasTag("player")
+ )
end,
nil,
nil,
diff --git a/scripts/components/aipc_projectile.lua b/scripts/components/aipc_projectile.lua
index 11195b64..c7be868e 100644
--- a/scripts/components/aipc_projectile.lua
+++ b/scripts/components/aipc_projectile.lua
@@ -60,7 +60,7 @@ function Projectile:OnUpdate(dt)
-- 目标已经失效
self:OnFinish()
return
- else
+ elseif self.target ~= nil then
-- 目标留存
self.targetPos = self.target:GetPosition()
end
diff --git a/scripts/components/aipc_rubik.lua b/scripts/components/aipc_rubik.lua
new file mode 100644
index 00000000..25d245f2
--- /dev/null
+++ b/scripts/components/aipc_rubik.lua
@@ -0,0 +1,549 @@
+local dev_mode = aipGetModConfig("dev_mode") == "enabled"
+
+local createProjectile = require("utils/aip_vest_util").createProjectile
+
+------------------------------- 方法 -------------------------------
+local FX_OFFSET = 2
+local FX_HEIGHT = 7
+
+local function getPos(idx)
+ local mathIdx = idx - 1
+ local y = math.floor(mathIdx / 9)
+ local x = math.floor(math.mod(mathIdx, 9) / 3)
+ local z = math.mod(mathIdx, 3)
+
+ return x - 1, y - 1, z - 1
+end
+
+local function getPosVec(idx)
+ local x, y, z = getPos(idx)
+ return Vector3(x, y, z)
+end
+
+local function getIndex(pt)
+ local x = pt.x + 1
+ local y = pt.y + 1
+ local z = pt.z + 1
+ return y * 9 + x * 3 + z + 1
+end
+
+local function getSameCount(x1, y1, z1, x2, y2, z2)
+ local sameX = x1 == x2
+ local sameY = y1 == y2
+ local sameZ = z1 == z2
+ local sameCnt = (sameX and 1 or 0) + (sameY and 1 or 0) + (sameZ and 1 or 0)
+ return sameCnt
+end
+
+local function isCorner(x, y, z)
+ return math.abs(x) == 1 and math.abs(y) == 1 and math.abs(z) == 1
+end
+
+-- 获取两点相同的轴
+local function getSameAxis(p1, p2)
+ local axises = {}
+ local xyz = {'x','y','z'}
+ for i, axis in ipairs(xyz) do
+ if p1[axis] == p2[axis] then
+ table.insert(axises, axis)
+ end
+ end
+ return axises
+end
+
+-- 是一条边的中间
+local function isEdgeCenter(x, y, z)
+ return math.abs(x) + math.abs(y) + math.abs(z) == 2
+end
+
+-- 是一个面的中间
+local function isFaceCenter(x, y, z)
+ return math.abs(x) + math.abs(y) + math.abs(z) == 1
+end
+
+-- 按照一个轴反转角度
+local function revertPT(axis, x, y, z)
+ if type(x) == "table" then
+ z = x.z
+ y = x.y
+ x = x.x
+ end
+
+ local pt = Vector3(x, y, z)
+ pt[axis] = -pt[axis]
+ return pt
+end
+
+-- 设置得到一个新的
+local function setPT(axis, pt, val)
+ local clone = Vector3(pt.x, pt.y, pt.z)
+ clone[axis] = val
+ return clone
+end
+
+-- 偏移得到一个新的
+local function offsetPT(axis, pt, offset)
+ local val = pt[axis] + offset
+ return setPT(axis, pt, val)
+end
+
+-- 根据一个轴的名字,获取剩余两个轴的名字
+local function getAxisRest(axis)
+ local tbl = {
+ x = { 'y', 'z' },
+ y = { 'z', 'x' },
+ z = { 'x', 'y' },
+ }
+ return tbl[axis]
+end
+
+-- 按照一个角度旋转
+local function rotateFace(originFxs, originMatrix, fixedAxis, fixedAxisPos, reverse)
+ local angle = PI / 2
+ local restAxises = getAxisRest(fixedAxis)
+
+ if reverse then
+ angle = -angle
+ end
+
+ local next_fxs = aipTableSlice(originFxs)
+ local next_matrix = aipTableSlice(originMatrix)
+
+ for x = -1, 1 do -- x y 只是代指两个轴,不是真的 x y
+ for y = -1, 1 do
+ -- 需要取整
+ local nextX = math.floor(x * math.cos(angle) - y * math.sin(angle) + 0.1)
+ local nextY = math.floor(x * math.sin(angle) + y * math.cos(angle) + 0.1)
+ local xyz = Vector3()
+ xyz[fixedAxis] = fixedAxisPos
+ xyz[restAxises[1]] = x
+ xyz[restAxises[2]] = y
+
+ -- 目标节点
+ local nextXYZ = Vector3(xyz.x, xyz.y, xyz.z)
+ nextXYZ[restAxises[1]] = nextX
+ nextXYZ[restAxises[2]] = nextY
+
+ -- aipPrint("ROTATE:", xyz.x, xyz.y, xyz.z, ">", nextXYZ.x, nextXYZ.y, nextXYZ.z)
+
+ -- 交换 颜色 和 实体
+ local oriIdx = getIndex(xyz)
+ local tgtIdx = getIndex(nextXYZ)
+
+ next_fxs[tgtIdx] = originFxs[oriIdx]
+ next_matrix[tgtIdx] = originMatrix[oriIdx]
+ end
+ end
+
+ return {
+ fxs = next_fxs,
+ matrix = next_matrix,
+ }
+end
+
+-- 获取旋转后的起点和终点距离
+local function rotateDist(fxs, next_fxs, startPT, endPT)
+ local startRotatedIdx = aipTableIndex(next_fxs, fxs[getIndex(startPT)])
+ local startRotatedPos = getPosVec(startRotatedIdx)
+ return aipDist(startRotatedPos, endPT, true)
+end
+
+-- 播放动画
+local function playAnimation(fx, motion)
+ if fx then
+ fx.AnimState:PlayAnimation(motion, true)
+
+ if motion == "optional" then
+ fx.rotate = true
+ end
+ end
+end
+
+-- 根据值获得轴的名字
+local function getAxisByVal(pt, val)
+ if pt.x == val then
+ return 'x'
+ elseif pt.y == val then
+ return 'y'
+ elseif pt.z == val then
+ return 'z'
+ end
+end
+
+-- 检查两个点是否在轴上(如果在轴上就不用旋转了)
+local function isOnAxis(axis, pt)
+ if axis == 'x' then
+ return pt.y == 0 and pt.z == 0
+ elseif axis == 'y' then
+ return pt.x == 0 and pt.z == 0
+ elseif axis == 'z' then
+ return pt.x == 0 and pt.y == 0
+ end
+end
+
+------------------------------- 组件 -------------------------------
+local Rubik = Class(function(self, inst)
+ self.inst = inst
+
+ self.start = false
+ self.selectIndex = nil
+ self.fxs = {}
+
+ self.matrix = {}
+ self.randomTimes = 10 -- 随机剩余次数
+ self.summonning = false
+
+ local colors = {"red","green","blue"}
+ for y = 0, 2 do
+ local color = colors[y + 1]
+
+ for x = 0, 2 do
+ for z = 0, 2 do
+ local idx = 1 + z + x * 3 + y * 9
+ local finalColor = color
+ if isFaceCenter(x-1,y-1,z-1) or (x==1 and y==1 and z==1) then
+ finalColor = "yellow"
+ end
+ self.matrix[idx] = finalColor
+ end
+ end
+ end
+end)
+
+----------------------------- 创造怪物 -----------------------------
+function Rubik:CreateMonster(count)
+ local pt = self.inst:GetPosition()
+ local dist = 7
+
+ -- 创建心脏
+ local heart = aipSpawnPrefab(self.inst, "aip_rubik_heart")
+ heart.aipGhosts = {}
+
+ -- 创建怪物
+ for i = 1, count do
+ local angle = 2 * PI / count * i
+ local tgtPT = Vector3(
+ pt.x + math.cos(angle) * dist, 0, pt.z + math.sin(angle) * dist
+ )
+
+ createProjectile(
+ self.inst, tgtPT, function(proj)
+ local effect = aipSpawnPrefab(proj, "aip_shadow_wrapper", nil, 0.1)
+ effect.DoShow()
+
+ local ghost = aipSpawnPrefab(proj, "aip_rubik_ghost")
+ if ghost.components.knownlocations then
+ ghost.components.knownlocations:RememberLocation("home", pt)
+ end
+
+ ghost.aipHeart = heart
+ table.insert(heart.aipGhosts, ghost)
+ end, { 0, 0, 0, 5 }, 10
+ )
+ end
+end
+
+------------------------------- 检查 -------------------------------
+-- 检查是否可以触发火坑
+function Rubik:SummonBoss()
+ local passedLevelCount = 0
+
+ for y = -1, 1 do
+ local commonColor = nil
+ local passed = true
+
+ for x = -1, 1 do
+ for z = -1, 1 do
+ local index = getIndex(Vector3(x, y ,z))
+ local color = self.matrix[index]
+
+ if color ~= "yellow" then
+ -- 如果没有颜色就初始化一个颜色
+ if commonColor == nil then
+ commonColor = color
+ end
+
+ -- 如果颜色不对,则不过了
+ if commonColor ~= color then
+ passed = false
+ end
+ end
+ end
+ end
+
+ if passed then
+ passedLevelCount = passedLevelCount + 1
+ end
+ end
+
+ -- 动画效果,每个火都要回到火坑里
+ local x, y, z = self.inst.Transform:GetWorldPosition()
+ local pt = Vector3(x, y + 1.5, z)
+
+ for i, fx in ipairs(self.fxs) do
+ fx.components.aipc_float:MoveToPoint(pt)
+ end
+
+ self.summonning = true
+ self.selectIndex = nil
+
+ self.inst:DoTaskInTime(0.5, function()
+ if self.inst.components.fueled ~= nil then
+ self.inst.components.fueled:MakeEmpty()
+ end
+
+ -- 爆炸效果
+ local effect = aipSpawnPrefab(self.inst, "aip_shadow_wrapper", nil, 0.1)
+ effect.DoShow(2)
+
+ -- 创建怪物
+ self:CreateMonster(1 + (3 - passedLevelCount) * 2)
+
+ -- 我们直接重新替换一个新的
+ local replacedRubik = aipReplacePrefab(self.inst, "aip_rubik")
+ if replacedRubik.components.fueled ~= nil then
+ replacedRubik.components.fueled:MakeEmpty()
+ end
+ end)
+end
+
+------------------------------- 位置 -------------------------------
+function Rubik:SyncPos(motion)
+ -- 我们总是延迟一丢丢
+ self.inst:DoTaskInTime(0.1, function()
+ local x, y, z = self.inst.Transform:GetWorldPosition()
+
+ for i, fx in ipairs(self.fxs) do
+ local ox, oy, oz = getPos(i)
+
+ local scaleOffset = 1 + (oy - 1) / 6
+
+ local tgtX = x + ox * FX_OFFSET * scaleOffset
+ local tgtY = y + oy * FX_OFFSET + FX_HEIGHT
+ local tgtZ = z + oz * FX_OFFSET * scaleOffset
+
+ local pt = Vector3(tgtX, tgtY, tgtZ)
+
+ if i == 2 then
+ fx.components.aipc_float.debug = true
+ end
+
+ if motion then
+ fx.components.aipc_float:MoveToPoint(pt)
+ else
+ fx.components.aipc_float:GoToPoint(pt)
+ end
+ end
+ end)
+end
+
+function Rubik:SyncHighlight()
+ for i, fx in ipairs(self.fxs) do
+ local ox, oy, oz = getPos(i)
+
+ playAnimation(fx, "idle")
+ fx.rotate = false
+
+ if ox == 0 and oy == 0 and oz == 0 then
+ fx:AddTag("FX")
+ else
+ fx:RemoveTag("FX")
+ end
+ end
+
+ if self.selectIndex then
+ -- 选中动画
+ local selected = self.fxs[self.selectIndex]
+ playAnimation(selected, "selected")
+
+ -- 可选动画
+ local sx, sy, sz = getPos(self.selectIndex)
+
+
+ ------------ 角落点击 ------------
+ if isCorner(sx, sy, sz) then
+ local ptXY = revertPT('x', revertPT('y', sx, sy, sz))
+ ptXY.lock = 'z'
+ local ptYZ = revertPT('y', revertPT('z', sx, sy, sz))
+ ptYZ.lock = 'x'
+ local ptXZ = revertPT('z', revertPT('x', sx, sy, sz))
+ ptXZ.lock = 'y'
+
+ local ptList = { ptXY, ptYZ, ptXZ }
+ for i, pt in ipairs(ptList) do
+ local restAxises = getAxisRest(pt.lock)
+
+ for i, restAxis in ipairs(restAxises) do
+ local finalPT = setPT(restAxis, pt, 0)
+ local idx = getIndex(finalPT)
+ playAnimation(self.fxs[idx], "optional")
+ end
+ end
+ end
+
+ ------------ 边缘中间 ------------
+ if isEdgeCenter(sx, sy, sz) then
+ -- 找到数值为 0 的轴
+ local pt = Vector3(sx, sy, sz)
+ local fixAxis = getAxisByVal(pt, 0)
+ local restAxises = getAxisRest(fixAxis)
+
+ -- 同轴对面
+ for i, restAxis in ipairs(restAxises) do
+ local finalPT = setPT(restAxis, pt, 0)
+ local idx = getIndex(finalPT)
+ playAnimation(self.fxs[idx], "optional")
+ end
+
+ -- 同面旋转
+ local pt1 = setPT(fixAxis, pt, -1) -- 先找到角落
+ local pt2 = setPT(fixAxis, pt, 1)
+ local ptList = { pt1, pt2 }
+ for i, pt in ipairs(ptList) do
+ for i, restAxis in ipairs(restAxises) do
+ local finalPT = setPT(restAxis, pt, 0)
+ local idx = getIndex(finalPT)
+ playAnimation(self.fxs[idx], "optional")
+ end
+ end
+ end
+
+ ------------ 中心点击 ------------
+ if isFaceCenter(sx, sy, sz) then
+ local pt = Vector3(sx, sy, sz)
+ local fixAxis = getAxisByVal(pt, 1) or getAxisByVal(pt, -1)
+ local restAxises = getAxisRest(fixAxis)
+
+ for i, restAxis in ipairs(restAxises) do
+ local pt1 = setPT(restAxis, pt, -1)
+ local pt2 = setPT(restAxis, pt, 1)
+ local ptList = { pt1, pt2 }
+ for i, finalPT in ipairs(ptList) do
+ local idx = getIndex(finalPT)
+ playAnimation(self.fxs[idx], "optional")
+ end
+ end
+ end
+ end
+end
+
+------------------------------- 开关 -------------------------------
+function Rubik:Start()
+ self.start = true
+
+ if #self.fxs == 0 then
+ for i, color in ipairs(self.matrix) do
+ local fx = SpawnPrefab("aip_rubik_fire_"..color)
+ fx.aipRubik = self.inst
+ self.fxs[i] = fx
+ end
+ end
+
+ self:SyncPos()
+ self:SyncHighlight()
+
+ -- 随机旋转
+ self:TryRandom()
+end
+
+function Rubik:TryRandom()
+ if self.randomTimes > 0 then
+ self.randomTimes = self.randomTimes - 1
+
+ local axises = { "x", "y", "z" }
+ local axis = dev_mode and 'x' or axises[math.random(#axises)]
+
+ local rndPos = 0
+ repeat
+ rndPos = math.random(-1, 1)
+ until(rndPos ~= self.randomPos)
+
+ local walkingInfo = rotateFace(self.fxs, self.matrix, axis, rndPos)
+ self.fxs = walkingInfo.fxs
+ self.matrix = walkingInfo.matrix
+
+ self.selectIndex = nil
+ self:SyncPos(true)
+ self:SyncHighlight()
+
+ -- 记录这次的旋转
+ self.randomPos = rndPos
+
+ -- 过一段时间再旋转一次
+ self.inst:DoTaskInTime(0.3, function()
+ self:TryRandom()
+ end)
+ end
+end
+
+function Rubik:Stop()
+ self.start = false
+
+ for i, fx in ipairs(self.fxs) do
+ fx:Remove()
+ end
+
+ self.fxs = {}
+end
+
+------------------------------- 选择 -------------------------------
+function Rubik:Select(fire)
+ -- 如果正在初始化旋转则不让选择
+ if self.randomTimes > 0 or self.summonning then
+ return
+ end
+
+ local prevIndex = self.selectIndex
+
+ self.selectIndex = aipTableIndex(self.fxs, fire)
+
+ -- 如果是中间那个 或者 相同元素,就不能选中
+ if self.selectIndex == 14 or prevIndex == self.selectIndex then
+ self.selectIndex = nil
+ end
+
+ ----------------------- 旋转判断 -----------------------
+ if prevIndex and self.selectIndex and fire.rotate then
+ local pStart = getPosVec(prevIndex)
+ local pEnd = getPosVec(self.selectIndex)
+
+ -- aipTypePrint("旋转吧:", pStart, '>>>', pEnd)
+
+ -- 遍历每个轴,找到可以旋转的那个
+ local axises = getSameAxis(pStart, pEnd)
+ for i, axis in ipairs(axises) do
+ -- aipPrint("检测轴:", axis, not isOnAxis(axis, pStart), not isOnAxis(axis, pEnd))
+ if not isOnAxis(axis, pStart) and not isOnAxis(axis, pEnd) then
+ local fixedAxisPos = pEnd[axis] -- 固定轴上的固定点
+
+ -- 顺、逆时针 都转一圈
+ local walkingInfo = rotateFace(self.fxs, self.matrix, axis, fixedAxisPos)
+ local reverseWalkingInfo = rotateFace(self.fxs, self.matrix, axis, fixedAxisPos, true)
+
+ -- 顺时针的距离计算
+ local rotatedDist = rotateDist(self.fxs, walkingInfo.fxs, pStart, pEnd)
+
+ -- 逆时针的距离计算
+ local reverseRotatedDist = rotateDist(self.fxs, reverseWalkingInfo.fxs, pStart, pEnd)
+
+ -- 同步回去
+ if rotatedDist < reverseRotatedDist then
+ self.fxs = walkingInfo.fxs
+ self.matrix = walkingInfo.matrix
+ else
+ self.fxs = reverseWalkingInfo.fxs
+ self.matrix = reverseWalkingInfo.matrix
+ end
+ self:SyncPos(true)
+
+ -- 转过了,不用选中了
+ self.selectIndex = nil
+ end
+ end
+ end
+
+ --------------------- 更新点击状态 ---------------------
+ self:SyncHighlight()
+end
+
+return Rubik
\ No newline at end of file
diff --git a/scripts/components/world_common_store.lua b/scripts/components/world_common_store.lua
index 0a5ff097..bb2047db 100644
--- a/scripts/components/world_common_store.lua
+++ b/scripts/components/world_common_store.lua
@@ -1,3 +1,5 @@
+local open_beta = aipGetModConfig("open_beta") == "open"
+
local dev_mode = aipGetModConfig("dev_mode") == "enabled"
local function findFarAwayOcean(pos)
@@ -57,6 +59,9 @@ local CommonStore = Class(function(self, inst)
-- 记录所有的箱子
self.chests = {}
+ -- 当前的豆酱图腾
+ self.douTotem = nil
+
-- 记录所有的飞行点
self.flyTotems = {}
@@ -90,6 +95,41 @@ function CommonStore:CreateCoookieKing(pos)
return nil
end
+-- 获取一下豆酱图腾
+function CommonStore:FindDouTotem()
+ if not self.douTotem then
+ self.douTotem = TheSim:FindFirstEntityWithTag("aip_dou_totem_final")
+ end
+ return self.douTotem
+end
+
+-- 创建 魔方,在墓地附近寻找
+function CommonStore:CreateRubik()
+ -- 存在且没有坐标就跳过
+ local ent = TheSim:FindFirstEntityWithTag("aip_rubik")
+ if ent ~= nil then
+ return ent
+ end
+
+ -- 寻找一个墓地
+ local grave = TheSim:FindFirstEntityWithTag("grave")
+ local pos = nil
+ if grave ~= nil then
+ pos = grave:GetPosition()
+ end
+
+ if not pos then
+ pos = aipGetSecretSpawnPoint(Vector3(0, 0, 0), 0, 1000)
+ end
+
+ pos = aipGetSecretSpawnPoint(pos, 0, 50, 5)
+
+ local rubik = aipSpawnPrefab(nil, "aip_rubik", pos.x, pos.y, pos.z)
+ rubik.components.fueled:MakeEmpty()
+
+ return rubik
+end
+
function CommonStore:CreateSuWuMound(pos)
-- 存在且没有坐标就跳过
local ent = TheSim:FindFirstEntityWithTag("aip_suwu_mound")
@@ -120,8 +160,6 @@ function CommonStore:PostWorld()
aipSpawnPrefab(nil, "aip_dou_totem_broken", tgt.x, tgt.y, tgt.z)
end
end
-
-
end)
--------------------------- 创建饼干 ---------------------------
@@ -129,9 +167,57 @@ function CommonStore:PostWorld()
self:CreateCoookieKing()
end)
- --------------------------- 开发模式 ---------------------------
- if dev_mode then
+ --------------------------- 创建魔方 ---------------------------
+ if open_beta then
+ self.inst:DoTaskInTime(5, function()
+ self:CreateRubik()
+ end)
end
+
+ --------------------------- 开发模式 ---------------------------
+ -- if dev_mode then
+ -- self.inst:DoTaskInTime(2, function()
+ -- if ThePlayer and false then
+ -- aipSpawnPrefab(ThePlayer, "aip_rubik")
+
+ -- -- 避免和神话书说&小房子冲突
+ -- local px = 1900
+ -- local py = 0
+ -- local pz = 1900
+
+ -- ThePlayer.Physics:Teleport(px, py, pz)
+
+ -- -- local tile = TheWorld.Map:GetTileAtPoint(px, py, pz)
+ -- -- aipPrint("Tile Type:", tile)
+
+ -- -- if tile == GROUND.INVALID then
+ -- -- local tileX, tileY = TheWorld.Map:GetTileCoordsAtPoint(px, py, pz)
+ -- -- TheWorld.Map:SetTile(tileX, tileY, GROUND.DIRT)
+ -- -- TheWorld.Map:RebuildLayer(GROUND.DIRT, tileX, tileY)
+
+ -- -- ThePlayer.Physics:Teleport(px, py, pz)
+ -- -- end
+
+
+ -- -- aipPrint("Next Tile Type:", TheWorld.Map:GetTileAtPoint(px, py, pz))
+
+ -- -- for px = 0, 1000 do
+ -- -- local py = 0
+ -- -- local pz = 0
+ -- -- local tile = TheWorld.Map:GetTileAtPoint(px, py, pz)
+ -- -- aipPrint("Tile Type:", px, tile)
+ -- -- end
+ -- end
+ -- end)
+
+ -- -- self.inst:DoPeriodicTask(1, function()
+ -- -- if ThePlayer then
+ -- -- local x, y, z = ThePlayer.Transform:GetWorldPosition()
+ -- -- local tile = TheWorld.Map:GetTileAtPoint(x,y,z)
+ -- -- aipPrint("Player Tile Type:", tile)
+ -- -- end
+ -- -- end)
+ -- end
end
return CommonStore
diff --git a/scripts/componentsHooker.lua b/scripts/componentsHooker.lua
index 1118ffb9..edde487d 100644
--- a/scripts/componentsHooker.lua
+++ b/scripts/componentsHooker.lua
@@ -1,6 +1,8 @@
local _G = GLOBAL
local language = _G.aipGetModConfig("language")
+env.AddReplicableComponent("aipc_buffer")
+
----------------------------------- 通用组件行为 -----------------------------------
-- 服务端组件
local function triggerComponentAction(player, item, target, targetPoint)
diff --git a/scripts/dev.lua b/scripts/dev.lua
index 35d39092..0037acfb 100644
--- a/scripts/dev.lua
+++ b/scripts/dev.lua
@@ -17,8 +17,8 @@ function PlayerPrefabPostInit(inst)
-- 开发模式下移除失眠效果
inst:RemoveTag("insomniac")
- inst.components.aipc_timer:Interval(1, function()
- if inst.components.health.currenthealth < 50 then
+ inst.components.aipc_timer:Interval(0.3, function()
+ if not inst.components.health:IsDead() and inst.components.health.currenthealth < 50 then
inst.components.health:DoDelta(50)
end
if inst.components.sanity.current < 30 then
diff --git a/scripts/houseWrapper.lua b/scripts/houseWrapper.lua
new file mode 100644
index 00000000..4f0dc4c0
--- /dev/null
+++ b/scripts/houseWrapper.lua
@@ -0,0 +1,94 @@
+local _G = GLOBAL
+
+local dev_mode = _G.aipGetModConfig("dev_mode") == "enabled"
+
+if not dev_mode then
+ return
+end
+
+-- IMPASSABLE = 1 不可过边界(深水)
+-- GRASS = 6, 草地
+-- FOREST = 7, 森林
+-- OCEAN_COASTAL = 201 海洋
+-- OCEAN_COASTAL_SHORE = 202 海岸线
+-- INVALID = 255 无效(黑色的空间)
+
+
+-- local aipHouses = {}
+
+local RANGE = 1900
+
+-- 是否在特殊空间里
+local function inPlace(x, z, offset)
+ offset = offset or 50
+ return x >= RANGE - offset and x <= RANGE + offset and
+ z >= RANGE - offset and z <= RANGE + offset
+end
+
+AddPrefabPostInit("world", function(inst)
+ local map = _G.getmetatable(inst.Map).__index
+ if map then
+
+ local old_IsAboveGroundAtPoint = map.IsAboveGroundAtPoint
+ map.IsAboveGroundAtPoint = function(self, x, y, z, ...)
+ if inPlace(x, z) then
+ -- for k, v in pairs(aipHouses) do
+ -- if v[3] ~= nil then
+ -- if z >= v[2] - 6.5 and z <= v[2] + 6.5 and x >= v[1] -
+ -- 5.5 and x <= v[1] + 5 then
+ -- return true
+ -- end
+ -- else
+ -- if v and z >= v[2] - 12 and z <= v[2] + 12 and x >= v[1] -
+ -- 8 and x <= v[1] + 8 then
+ -- if TheSim:WorldPointInPoly(x, z, {
+ -- {v[1] - 6.2, v[2] + 10},
+ -- {v[1] - 6.2, v[2] - 10.6},
+ -- {v[1] + 7.8, v[2] - 12}, {v[1] + 7.8, v[2] + 12}
+ -- }) then return true end
+ -- end
+ -- end
+ -- end
+ return true
+ end
+ return old_IsAboveGroundAtPoint(self, x, y, z, ...)
+ end
+
+ local old_IsVisualGroundAtPoint = map.IsVisualGroundAtPoint
+ map.IsVisualGroundAtPoint = function(self, x, y, z, ...)
+ if inPlace(x, z) then
+ -- for k, v in pairs(aipHouses) do
+ -- if v[3] ~= nil then
+ -- if z >= v[2] - 6.5 and z <= v[2] + 6.5 and x >= v[1] -
+ -- 5.5 and x <= v[1] + 5 then
+ -- return true
+ -- end
+ -- else
+ -- if v and z >= v[2] - 12 and z <= v[2] + 12 and x >= v[1] -
+ -- 8 and x <= v[1] + 8 then
+ -- if TheSim:WorldPointInPoly(x, z, {
+ -- {v[1] - 6.2, v[2] + 10},
+ -- {v[1] - 6.2, v[2] - 10.6},
+ -- {v[1] + 7.8, v[2] - 12}, {v[1] + 7.8, v[2] + 12}
+ -- }) then return true end
+ -- end
+ -- end
+ -- end
+ return true
+ end
+ return old_IsVisualGroundAtPoint(self, x, y, z, ...)
+ end
+
+ local old_GetTileCenterPoint = map.GetTileCenterPoint
+ map.GetTileCenterPoint = function(self, x, y, z)
+ if inPlace(x, z, 0) then
+ return math.floor(x / 4) * 4 + 2, 0, math.floor(z / 4) * 4 + 2
+ end
+ if z then
+ return old_GetTileCenterPoint(self, x, y, z)
+ else
+ return old_GetTileCenterPoint(self, x, y)
+ end
+ end
+ end
+end)
diff --git a/scripts/prefabs/aip_aura.lua b/scripts/prefabs/aip_aura.lua
index 89e56223..0340e767 100644
--- a/scripts/prefabs/aip_aura.lua
+++ b/scripts/prefabs/aip_aura.lua
@@ -20,14 +20,16 @@ local function getFn(data)
inst.entity:AddAnimState()
inst.entity:AddNetwork()
- inst.AnimState:SetBank(data.name)
- inst.AnimState:SetBuild(data.name)
+ if data.assets ~= nil then
+ inst.AnimState:SetBank(data.name)
+ inst.AnimState:SetBuild(data.name)
- inst.AnimState:PlayAnimation("idle", data.onAnimOver == nil)
+ inst.AnimState:PlayAnimation("idle", data.onAnimOver == nil)
- inst.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround)
- inst.AnimState:SetLayer(LAYER_WORLD_BACKGROUND)
- inst.AnimState:SetSortOrder(2)
+ inst.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround)
+ inst.AnimState:SetLayer(LAYER_WORLD_BACKGROUND)
+ inst.AnimState:SetSortOrder(2)
+ end
if data.scale ~= nil then
inst.Transform:SetScale(data.scale, data.scale, data.scale)
@@ -54,6 +56,9 @@ local function getFn(data)
inst.components.aipc_aura.bufferFn = data.bufferFn
inst.components.aipc_aura.mustTags = data.mustTags
inst.components.aipc_aura.noTags = data.noTags
+ if data.showFX ~= nil then
+ inst.components.aipc_aura.showFX = data.showFX
+ end
end
if data.onAnimOver ~= nil then
@@ -89,6 +94,14 @@ local list = {
inst:Remove()
end,
},
+ { -- 预见光环:可以直接看到诡影脚步
+ name = "aip_aura_see",
+ range = 1,
+ bufferName = "seeFootPrint",
+ showFX = false,
+ mustTags = { "_health" },
+ noTags = { "INLIMBO", "NOCLICK", "ghost" },
+ },
}
diff --git a/scripts/prefabs/aip_cookiecutter_king.lua b/scripts/prefabs/aip_cookiecutter_king.lua
index 6fe90983..313b34cf 100644
--- a/scripts/prefabs/aip_cookiecutter_king.lua
+++ b/scripts/prefabs/aip_cookiecutter_king.lua
@@ -18,7 +18,7 @@ local LANG_MAP = {
TALK_KING_HUNGER_AIP_MUD_CRAB = "(Live Mud crab!)",
TALK_KING_FIND_ME = "(Keep in touch!)",
- TALK_KING_FINISH = "(Nice food. Next challenge is in dev)",
+ TALK_KING_FINISH = "(Nice food. Wish you like rubik)",
TALK_KING_88 = "(Bye!)",
},
chinese = {
@@ -35,7 +35,7 @@ local LANG_MAP = {
TALK_KING_HUNGER_AIP_MUD_CRAB = "(活泥蟹!)",
TALK_KING_FIND_ME = "(保持联系,投石问路)",
- TALK_KING_FINISH = "(多谢招待,下一个挑战还在开发中)",
+ TALK_KING_FINISH = "(多谢招待,希望你喜欢拼图)",
TALK_KING_88 = "(再见)",
},
}
@@ -270,6 +270,9 @@ local function startEater(inst)
-- 给予子卿
inst.aipVest.components.lootdropper:SpawnLootPrefab("aip_suwu")
end
+
+ -- 给予棱镜石
+ inst.aipVest.components.lootdropper:SpawnLootPrefab("aip_legion")
end
)
end
diff --git a/scripts/prefabs/aip_dou_totem.lua b/scripts/prefabs/aip_dou_totem.lua
index da25df64..a89ae26e 100644
--- a/scripts/prefabs/aip_dou_totem.lua
+++ b/scripts/prefabs/aip_dou_totem.lua
@@ -1,8 +1,14 @@
+local dev_mode = aipGetModConfig("dev_mode") == "enabled"
-- 配置
local language = aipGetModConfig("language")
local additional_survival = aipGetModConfig("additional_survival") == "open"
+local WarnRange = 8 -- 创造攻击玩家的范围
+
+
+local FueledTime = dev_mode and 8 or 40
+
local LANG_MAP = {
english = {
BROEKN_NAME = "Wreckage",
@@ -12,10 +18,11 @@ local LANG_MAP = {
NAME = "IOT Totem",
DESC = "Seems magic somewhere",
TALK_WELCOME = "Are you ready?",
+ TALK_ANGER = "'Thank you' for fuel me!",
TALK_FIRST = "Start your challenge",
TOTEM_POS = "First Place",
TOTEM_BALLOON = "Ruo Guang",
- TOTEM_SNAKE = "", -- 抓捕玩具蛇
+ TOTEM_RUBIK = "The Matrix",
},
chinese = {
BROEKN_NAME = "一片残骸",
@@ -25,9 +32,11 @@ local LANG_MAP = {
NAME = "联结图腾",
DESC = "有一丝魔法气息",
TALK_WELCOME = "想得到我的秘密,你做好准备了吗?",
+ TALK_ANGER = "“感谢”你的馈赠",
TALK_FIRST = "开始你的挑战!",
TOTEM_POS = "伊始之地",
TOTEM_BALLOON = "若光小驻",
+ TOTEM_RUBIK = "薄暮矩阵",
},
}
@@ -42,6 +51,7 @@ STRINGS.NAMES.AIP_DOU_TOTEM = LANG.NAME
STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_DOU_TOTEM = LANG.DESC
STRINGS.AIP_DOU_TOTEM_TALK_WELCOME = LANG.TALK_WELCOME
STRINGS.AIP_DOU_TOTEM_TALK_FIRST = LANG.TALK_FIRST
+STRINGS.AIP_DOU_TOTEM_TALK_ANGER = LANG.TALK_ANGER
---------------------------------- 资源 ----------------------------------
local assets = {
@@ -87,12 +97,15 @@ end
local function createFlyTotems(inst)
local startTotem = false
local balloonTotem = false
+ local rubikTotem = false
for i, totem in ipairs(TheWorld.components.world_common_store.flyTotems) do
if totem.markType == "START" then
startTotem = true
elseif totem.markType == "BALLOON" then
balloonTotem = true
+ elseif totem.markType == "RUBIK" then
+ rubikTotem = true
end
end
@@ -116,6 +129,17 @@ local function createFlyTotems(inst)
)
end
end
+
+ if rubikTotem == false then
+ local rubik = TheSim:FindFirstEntityWithTag("aip_rubik")
+ if rubik then
+ createFlyTotem(
+ aipGetSecretSpawnPoint(rubik:GetPosition(), 4, 8, 5),
+ LANG.TOTEM_RUBIK,
+ "RUBIK"
+ )
+ end
+ end
end
-- 创建挑战点
@@ -123,6 +147,74 @@ local function createChallenge()
aipGetTopologyPoint("lunacyarea", "moon_fissure")
end
+---------------------------------- 燃料 ----------------------------------
+local function ontakefuel(inst)
+ inst.SoundEmitter:PlaySound("dontstarve/common/nightmareAddFuel")
+end
+
+local function onupdatefueled(inst)
+ if inst.components.burnable ~= nil then
+ inst.components.burnable:SetFXLevel(inst.components.fueled:GetCurrentSection(), inst.components.fueled:GetSectionPercent())
+ end
+end
+
+local function onfuelchange(newsection, oldsection, inst)
+ if newsection <= 0 then
+ inst.components.burnable:Extinguish()
+ else
+ if not inst.components.burnable:IsBurning() then
+ inst.components.burnable:Ignite()
+ end
+
+ inst.components.burnable:SetFXLevel(newsection, inst.components.fueled:GetSectionPercent())
+ end
+end
+
+---------------------------------- 玩家 ----------------------------------
+local function killTimer(inst)
+ if inst.aipGhostTimer ~= nil then
+ inst.aipGhostTimer:Cancel()
+ end
+
+ inst.aipGhostTimer = nil
+end
+
+-- 玩家靠近
+local function onNear(inst, player)
+ killTimer(inst)
+
+ inst.aipGhostTimer = inst:DoPeriodicTask(8, function()
+ -- 点燃时,源源不断创造触手攻击玩家
+ if inst.components.fueled ~= nil and not inst.components.fueled:IsEmpty() then
+ local players = aipFindNearPlayers(inst, WarnRange)
+ local player = aipRandomEnt(players)
+
+ if player ~= nil then
+ local tentacle = aipSpawnPrefab(player, "shadowtentacle")
+ tentacle.components.combat:SetTarget(player)
+
+ -- 触手不会降低玩家理智
+ if tentacle.components.sanityaura ~= nil then
+ tentacle.components.sanityaura.aura = 0
+ end
+
+ tentacle.SoundEmitter:PlaySound("dontstarve/characters/walter/slingshot/shadowTentacleAttack_1")
+ tentacle.SoundEmitter:PlaySound("dontstarve/characters/walter/slingshot/shadowTentacleAttack_2")
+
+ -- 警告玩家
+ if inst.components.talker then
+ inst.components.talker:Say(STRINGS.AIP_DOU_TOTEM_TALK_ANGER)
+ end
+ end
+ end
+ end)
+end
+
+-- 玩家远离
+local function onFar(inst)
+ killTimer(inst)
+end
+
---------------------------------- 实体 ----------------------------------
local function makeTotemFn(name, animation, nextPrefab, nextPrefabAnimation)
-- 建筑到下一个级别
@@ -183,6 +275,8 @@ local function makeTotemFn(name, animation, nextPrefab, nextPrefabAnimation)
-- 最后一个级别做额外事情
if nextPrefab == nil then
+ inst:AddTag("aip_dou_totem_final")
+
-- 会添加对话能力
inst:AddComponent("talker")
inst.components.talker.fontsize = 30
@@ -197,6 +291,35 @@ local function makeTotemFn(name, animation, nextPrefab, nextPrefabAnimation)
return inst
end
+ -- 最后一个级别做额外事情
+ if nextPrefab == nil then
+ -- 可以点燃
+ inst:AddComponent("burnable")
+ inst.components.burnable:AddBurnFX("nightlight_flame", Vector3(0, 0, 0), "fire_marker_left")
+ inst.components.burnable:AddBurnFX("nightlight_flame", Vector3(0, 0, 0), "fire_marker_right")
+ inst.components.burnable.canlight = false
+
+ -- 使用燃料
+ inst:AddComponent("fueled")
+ inst.components.fueled.maxfuel = FueledTime
+ inst.components.fueled.accepting = true
+ inst.components.fueled.fueltype = FUELTYPE.NIGHTMARE
+ inst.components.fueled:SetSections(3)
+ inst.components.fueled:SetTakeFuelFn(ontakefuel)
+ inst.components.fueled:SetUpdateFn(onupdatefueled)
+ inst.components.fueled:SetSectionCallback(onfuelchange)
+ inst.components.fueled:InitializeFuelLevel(0)
+
+ inst.components.fueled.rate = 0 -- 永不熄灭
+ inst.components.fueled.bonusmult = (1 / TUNING.LARGE_FUEL) * (FueledTime / 4)
+
+ -- 玩家召唤触手打玩家
+ inst:AddComponent("playerprox")
+ inst.components.playerprox:SetDist(WarnRange, WarnRange)
+ inst.components.playerprox:SetOnPlayerNear(onNear)
+ inst.components.playerprox:SetOnPlayerFar(onFar)
+ end
+
inst:AddComponent("inspectable")
if nextPrefab ~= nil then
diff --git a/scripts/prefabs/aip_dragon_footprint.lua b/scripts/prefabs/aip_dragon_footprint.lua
index a063c792..f32c03f6 100644
--- a/scripts/prefabs/aip_dragon_footprint.lua
+++ b/scripts/prefabs/aip_dragon_footprint.lua
@@ -49,7 +49,15 @@ local function PrintFootPrint(inst)
local player = ThePlayer
if player ~= nil and player.replica.sanity ~= nil then
local ptg = player.replica.sanity:GetPercent()
- vest.AnimState:OverrideMultColour(1, 1, 1, 1 - ptg * 0.8)
+
+ -- 如果有帽子光环就可以直接看到
+ if hasBuffer(player, "seeFootPrint") then
+ ptg = 0
+ end
+
+ local mulPtg = 1 - ptg * 0.8
+
+ vest.AnimState:OverrideMultColour(mulPtg, mulPtg, mulPtg, mulPtg)
end
-- 每次打印脚印后就更新一下记录
diff --git a/scripts/prefabs/aip_dress_template.lua b/scripts/prefabs/aip_dress_template.lua
index 7d4580d7..2b207888 100644
--- a/scripts/prefabs/aip_dress_template.lua
+++ b/scripts/prefabs/aip_dress_template.lua
@@ -101,6 +101,11 @@ local function template(name, config)
inst:AddTag("hat")
inst:AddTag("waterproofer")
+ -- 额外功能
+ if config.preInst then
+ config.preInst(inst)
+ end
+
inst.entity:SetPristine()
if not TheWorld.ismastersim then
@@ -151,7 +156,7 @@ local function template(name, config)
end
end
- -- 护甲
+ -- 额外功能
if config.postInst then
config.postInst(inst)
end
diff --git a/scripts/prefabs/aip_fly_totem.lua b/scripts/prefabs/aip_fly_totem.lua
index 12753b83..99629f34 100644
--- a/scripts/prefabs/aip_fly_totem.lua
+++ b/scripts/prefabs/aip_fly_totem.lua
@@ -6,6 +6,12 @@ local LANG_MAP = {
NAME = "Fly Totem",
RECDESC = "Unicom's flight site",
DESC = "To Infinity... and Beyond",
+
+ FAKE_NAME = "Inferior Fly Totem",
+ FAKE_RECDESC = "Not an outstanding counterfeit ",
+ FAKE_DESC = "Things that need to be recharged to activate",
+ FAKE_NO_POWER = "Need recharge on the source",
+
UNNAMED = "[UNNAMED]",
CURRENT = "I'm already here!",
INVLIDATE = "Target seems disappeared",
@@ -16,6 +22,12 @@ local LANG_MAP = {
NAME = "飞行图腾",
RECDESC = "联通的飞行站点",
DESC = "飞向宇宙,浩瀚无垠!",
+
+ FAKE_NAME = "劣质的飞行图腾",
+ FAKE_RECDESC = "并不杰出的仿冒品",
+ FAKE_DESC = "需要充能才能启动的玩意儿",
+ FAKE_NO_POWER = "它的魔力来源已经消耗殆尽了",
+
UNNAMED = "[未命名]",
CURRENT = "我就在这里!",
INVLIDATE = "目的地不见了",
@@ -30,6 +42,12 @@ local LANG = LANG_MAP[language] or LANG_MAP.english
STRINGS.NAMES.AIP_FLY_TOTEM = LANG.NAME
STRINGS.RECIPE_DESC.AIP_FLY_TOTEM = LANG.RECDESC
STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FLY_TOTEM = LANG.DESC
+
+STRINGS.NAMES.AIP_FAKE_FLY_TOTEM = LANG.FAKE_NAME
+STRINGS.RECIPE_DESC.AIP_FAKE_FLY_TOTEM = LANG.FAKE_RECDESC
+STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FAKE_FLY_TOTEM = LANG.FAKE_DESC
+STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FAKE_FLY_TOTEM_NO_POWER = LANG.FAKE_NO_POWER
+
STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FLY_TOTEM_UNNAMED = LANG.UNNAMED
STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FLY_TOTEM_CURRENT = LANG.CURRENT
STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FLY_TOTEM_INVLIDATE = LANG.INVLIDATE
@@ -41,7 +59,9 @@ require "prefabutil"
local assets = {
Asset("ANIM", "anim/aip_fly_totem.zip"),
+ Asset("ANIM", "anim/aip_fake_fly_totem.zip"),
Asset("ATLAS", "images/inventoryimages/aip_fly_totem.xml"),
+ Asset("ATLAS", "images/inventoryimages/aip_fake_fly_totem.xml"),
}
local prefabs = {
@@ -81,6 +101,21 @@ local function canBeActOn(inst, doer)
end
local function onOpenPicker(inst, doer)
+ -- 如果是假图腾就检查是否豆酱图腾由能量
+ if inst.aipFake == true and TheWorld.components.world_common_store then
+ local douTotem = TheWorld.components.world_common_store:FindDouTotem()
+
+ -- 没有的话就不让飞行了
+ if not douTotem or not douTotem.components.fueled or douTotem.components.fueled:IsEmpty() then
+ if doer.components.talker then
+ doer.components.talker:Say(
+ STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_FAKE_FLY_TOTEM_NO_POWER
+ )
+ end
+ return
+ end
+ end
+
-- 加一个切割前缀强制服务器触发
doer.player_classified.aip_fly_picker:set(tostring(os.time()).."|"..inst.aipId)
end
@@ -101,6 +136,18 @@ local function onLoad(inst, data)
end
end
+---------------------------------- 燃料 ----------------------------------
+local function ontakefuel(inst)
+ if TheWorld.components.world_common_store then
+ local douTotem = TheWorld.components.world_common_store:FindDouTotem()
+
+ -- 给链接图腾补充燃料
+ if douTotem and douTotem.components.fueled then
+ douTotem.components.fueled:DoDelta(5)
+ end
+ end
+end
+
---------------------------------- 方法 ----------------------------------
-- 玩家靠近时创建一个小豆酱
local function onnear(inst, player)
@@ -153,86 +200,105 @@ local function startSpell(inst, targetTotem)
local aura = aipSpawnPrefab(inst, "aip_aura_send")
aura.OnRemoveEntity = function()
startTranslate(inst, targetTotem)
+
+ -- 如果是劣质图腾则消耗一下使用寿命
+ if inst.aipFake == true and TheWorld.components.world_common_store then
+ local douTotem = TheWorld.components.world_common_store:FindDouTotem()
+ douTotem.components.fueled:DoDelta(-1, inst)
+ end
end
end
---------------------------------- 实体 ----------------------------------
-local function fn()
- local inst = CreateEntity()
-
- inst.entity:AddTransform()
- inst.entity:AddAnimState()
- inst.entity:AddSoundEmitter()
- -- inst.entity:AddMiniMapEntity()
- inst.entity:AddNetwork()
-
- MakeObstaclePhysics(inst, .2)
-
- -- inst.MiniMapEntity:SetIcon("sign.png")
+local function genTotem(buildName, fake)
+ local function fn()
+ local inst = CreateEntity()
- inst.AnimState:SetBank("aip_fly_totem")
- inst.AnimState:SetBuild("aip_fly_totem")
- inst.AnimState:PlayAnimation("idle")
+ inst.entity:AddTransform()
+ inst.entity:AddAnimState()
+ inst.entity:AddSoundEmitter()
+ -- inst.entity:AddMiniMapEntity()
+ inst.entity:AddNetwork()
- MakeSnowCoveredPristine(inst)
+ MakeObstaclePhysics(inst, .2)
- inst:AddTag("structure")
- inst:AddTag("aip_fly_totem")
+ -- inst.MiniMapEntity:SetIcon("sign.png")
- --Sneak these into pristine state for optimization
- inst:AddTag("_writeable")
+ inst.AnimState:SetBank(buildName)
+ inst.AnimState:SetBuild(buildName)
+ inst.AnimState:PlayAnimation("idle")
- -- 添加飞行图腾
- inst:AddComponent("aipc_action_client")
- inst.components.aipc_action_client.canBeActOn = canBeActOn
+ MakeSnowCoveredPristine(inst)
- inst.entity:SetPristine()
+ inst:AddTag("structure")
+ inst:AddTag("aip_fly_totem")
- if not TheWorld.ismastersim then
- return inst
- end
+ --Sneak these into pristine state for optimization
+ inst:AddTag("_writeable")
- --Remove these tags so that they can be added properly when replicating components below
- inst:RemoveTag("_writeable")
+ -- 添加飞行图腾
+ inst:AddComponent("aipc_action_client")
+ inst.components.aipc_action_client.canBeActOn = canBeActOn
- inst:AddComponent("aipc_action")
- inst.components.aipc_action.onDoAction = onOpenPicker
+ inst.entity:SetPristine()
- inst:AddComponent("inspectable")
- inst:AddComponent("writeable")
- inst:AddComponent("lootdropper")
+ if not TheWorld.ismastersim then
+ return inst
+ end
- inst:AddComponent("workable")
- inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
- inst.components.workable:SetWorkLeft(4)
- inst.components.workable:SetOnFinishCallback(onhammered)
- inst.components.workable:SetOnWorkCallback(onhit)
+ --Remove these tags so that they can be added properly when replicating components below
+ inst:RemoveTag("_writeable")
+
+ inst:AddComponent("aipc_action")
+ inst.components.aipc_action.onDoAction = onOpenPicker
+
+ inst:AddComponent("inspectable")
+ inst:AddComponent("writeable")
+ inst:AddComponent("lootdropper")
+
+ inst:AddComponent("workable")
+ inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
+ inst.components.workable:SetWorkLeft(4)
+ inst.components.workable:SetOnFinishCallback(onhammered)
+ inst.components.workable:SetOnWorkCallback(onhit)
+
+ -- 玩家靠近
+ inst:AddComponent("playerprox")
+ inst.components.playerprox:SetDist(10, 13)
+ inst.components.playerprox:SetOnPlayerNear(onnear)
+
+ -- 如果伪劣产品,我们可以充能
+ if fake then
+ inst:AddComponent("fueled")
+ inst.components.fueled.accepting = true
+ inst.components.fueled.fueltype = FUELTYPE.NIGHTMARE
+ inst.components.fueled:SetTakeFuelFn(ontakefuel)
+ end
- -- 玩家靠近
- inst:AddComponent("playerprox")
- inst.components.playerprox:SetDist(10, 13)
- inst.components.playerprox:SetOnPlayerNear(onnear)
+ MakeSnowCovered(inst)
- MakeSnowCovered(inst)
+ MakeSmallBurnable(inst, TUNING.LARGE_BURNTIME)
+ MakeSmallPropagator(inst)
- MakeSmallBurnable(inst, TUNING.LARGE_BURNTIME)
- MakeSmallPropagator(inst)
+ inst.aipId = tostring(os.time())..tostring(math.random())
- inst.aipId = tostring(os.time())..tostring(math.random())
+ inst.aipStartSpell = startSpell
+ inst.aipFake = fake
- inst.aipStartSpell = startSpell
+ inst.OnSave = onSave
+ inst.OnLoad = onLoad
- inst.OnSave = onSave
- inst.OnLoad = onLoad
+ MakeHauntableWork(inst)
+ inst:ListenForEvent("onbuilt", onbuilt)
- MakeHauntableWork(inst)
- inst:ListenForEvent("onbuilt", onbuilt)
+ -- 全局注册飞行图腾
+ table.insert(TheWorld.components.world_common_store.flyTotems, inst)
+ inst:ListenForEvent("onremove", onRemove)
- -- 全局注册飞行图腾
- table.insert(TheWorld.components.world_common_store.flyTotems, inst)
- inst:ListenForEvent("onremove", onRemove)
+ return inst
+ end
- return inst
+ return fn
end
-------------------------------- 起飞特效 --------------------------------
@@ -307,7 +373,9 @@ local function flyEffectFn()
end
-return Prefab("aip_fly_totem", fn, assets, prefabs),
+return Prefab("aip_fly_totem", genTotem("aip_fly_totem"), assets, prefabs),
MakePlacer("aip_fly_totem_placer", "aip_fly_totem", "aip_fly_totem", "idle"),
+ Prefab("aip_fake_fly_totem", genTotem("aip_fake_fly_totem", true), assets, prefabs),
+ MakePlacer("aip_fake_fly_totem_placer", "aip_fake_fly_totem", "aip_fake_fly_totem", "idle"),
Prefab("aip_eagle_effect", effectFn, { Asset("ANIM", "anim/lavaarena_attack_buff_effect.zip") }),
Prefab("aip_fly_totem_effect", flyEffectFn, { Asset("ANIM", "anim/aip_fly_totem_effect.zip") })
diff --git a/scripts/prefabs/aip_legion.lua b/scripts/prefabs/aip_legion.lua
new file mode 100644
index 00000000..f8375a3f
--- /dev/null
+++ b/scripts/prefabs/aip_legion.lua
@@ -0,0 +1,111 @@
+local open_beta = aipGetModConfig("open_beta") == "open"
+
+local language = aipGetModConfig("language")
+
+-- 文字描述
+local LANG_MAP = {
+ english = {
+ NAME = "Legion Stone",
+ DESC = "Last piece of Magic Rubik",
+ EMPTY = "This Rubik is powerless",
+ },
+ chinese = {
+ NAME = "棱镜石",
+ DESC = "魔力方阵的最后一片",
+ EMPTY = "这个方阵没有魔力了",
+ },
+}
+
+if not open_beta then
+ LANG_MAP.english.EMPTY = "Special but useless"
+ LANG_MAP.chinese.EMPTY = "奇特的石头,似乎没有什么用处"
+end
+
+local LANG = LANG_MAP[language] or LANG_MAP.english
+
+STRINGS.NAMES.AIP_LEGION = LANG.NAME
+STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_LEGION = LANG.DESC
+STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_LEGION_EMPTY = LANG.EMPTY
+
+-- 资源
+local assets = {
+ Asset("ANIM", "anim/aip_legion.zip"),
+ Asset("ATLAS", "images/inventoryimages/aip_legion.xml"),
+}
+
+-----------------------------------------------------------
+local function canActOn(inst, doer, target)
+ return target.prefab == "aip_rubik"
+end
+
+local function onDoTargetAction(inst, doer, target)
+ -- server only
+ if
+ not TheWorld.ismastersim or -- 不是主机
+ target.prefab ~= "aip_rubik" -- 不是魔方
+ then
+ return
+ end
+
+ if
+ not target.components.fueled or -- 没有燃料
+ target.components.fueled:IsEmpty() -- 燃料耗尽
+ then
+ if doer.components.talker ~= nil then
+ doer.components.talker:Say(
+ STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_LEGION_EMPTY
+ )
+ end
+ return
+ end
+
+ -- 召唤 BOSS
+ if target.components.aipc_rubik then
+ target.components.aipc_rubik:SummonBoss()
+ end
+
+ inst:Remove()
+end
+
+-----------------------------------------------------------
+local function fn()
+ local inst = CreateEntity()
+
+ inst.entity:AddTransform()
+ inst.entity:AddAnimState()
+ inst.entity:AddNetwork()
+
+ MakeInventoryPhysics(inst)
+
+ inst.AnimState:SetBank("aip_legion")
+ inst.AnimState:SetBuild("aip_legion")
+ inst.AnimState:PlayAnimation("idle")
+
+ MakeInventoryFloatable(inst, "med", nil, 0.68)
+
+ inst.entity:SetPristine()
+
+ inst:AddComponent("aipc_action_client")
+ inst.components.aipc_action_client.canActOn = canActOn
+
+ if not TheWorld.ismastersim then
+ return inst
+ end
+
+ inst:AddComponent("aipc_action")
+ inst.components.aipc_action.onDoTargetAction = onDoTargetAction
+
+ inst:AddComponent("inspectable")
+
+ inst:AddComponent("inventoryitem")
+ inst.components.inventoryitem.atlasname = "images/inventoryimages/aip_legion.xml"
+
+ inst:AddComponent("tradable")
+ inst.components.tradable.goldvalue = 3
+
+ MakeHauntableLaunch(inst)
+
+ return inst
+end
+
+return Prefab("aip_legion", fn, assets)
diff --git a/scripts/prefabs/aip_mini_doujiang.lua b/scripts/prefabs/aip_mini_doujiang.lua
index 850ef837..eb2216a6 100644
--- a/scripts/prefabs/aip_mini_doujiang.lua
+++ b/scripts/prefabs/aip_mini_doujiang.lua
@@ -28,6 +28,7 @@ local LANG_MAP = {
WAIT_NEXT = "I've seen an Gaint. Maybe you have interested",
CARDS = "Reciprocity~",
WRONG_GIFT = "It's meaningless",
+ TOTEM_RUBIK = "Shadow Rubik",
},
chinese = {
NAME = "若光",
@@ -41,6 +42,7 @@ local LANG_MAP = {
WAIT_NEXT = "我看到过一个大家伙,你也去看看吧",
CARDS = "礼尚往来~",
WRONG_GIFT = "我不需要它!",
+ TOTEM_RUBIK = "暗影魔方",
},
}
@@ -156,6 +158,23 @@ local function aipThrowBallBack(inst, ball)
inst.components.timer:StartTimer("aip_mini_dou_no_talk", 5)
end
+-- 创建魔方挑战
+local function createChallenge()
+ local rubikTotem = false
+
+ for i, totem in ipairs(TheWorld.components.world_common_store.flyTotems) do
+ if totem.markType == "RUBIK" then
+ rubikTotem = true
+ end
+ end
+
+ if rubikTotem == false then
+ local flyTotem = aipSpawnPrefab(nil, "aip_fly_totem", 3000, 0, 3000)
+ flyTotem.components.writeable:SetText(LANG.TOTEM_RUBIK)
+ flyTotem.markType = "RUBIK"
+ end
+end
+
-- 判断是否要给奖励
local function aipPlayEnd(inst, throwTimes)
local rewardTimes = dev_mode and 2 or 4
diff --git a/scripts/prefabs/aip_olden_tea.lua b/scripts/prefabs/aip_olden_tea.lua
index 83adeab4..06ca5932 100644
--- a/scripts/prefabs/aip_olden_tea.lua
+++ b/scripts/prefabs/aip_olden_tea.lua
@@ -118,7 +118,7 @@ local function onDoEat(inst, doer)
if doer.components.timer ~= nil then
doer.components.timer:StopTimer("aip_olden_tea")
- doer.components.timer:StartTimer("aip_olden_tea", dev_mode and 10 or 60)
+ doer.components.timer:StartTimer("aip_olden_tea", dev_mode and 300 or 60)
-- 喝茶时说一句话
if doer.components.talker ~= nil then
diff --git a/scripts/prefabs/aip_rubik.lua b/scripts/prefabs/aip_rubik.lua
index 83620e60..c6a4a6cb 100644
--- a/scripts/prefabs/aip_rubik.lua
+++ b/scripts/prefabs/aip_rubik.lua
@@ -1,3 +1,5 @@
+local dev_mode = aipGetModConfig("dev_mode") == "enabled"
+
local language = aipGetModConfig("language")
-- 文字描述
@@ -19,8 +21,7 @@ STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_RUBIK = LANG.DESC
-- 资源
local assets = {
- Asset("ANIM", "anim/aip_22_fish.zip"),
- Asset("ATLAS", "images/inventoryimages/aip_22_fish.xml"),
+ Asset("ANIM", "anim/aip_rubik.zip"),
}
local prefabs = {
@@ -29,35 +30,41 @@ local prefabs = {
"aip_rubik_fire_red",
}
-------------------------------- 事件 -------------------------------
+------------------------------- 燃烧 -------------------------------
+local function onextinguish(inst)
+ if inst.components.fueled ~= nil then
+ inst.components.fueled:InitializeFuelLevel(0)
+ end
+ inst:RemoveTag("shadow_fire")
+ inst.components.aipc_rubik:Stop()
+end
+
+local function onignite(inst)
+ inst:AddTag("shadow_fire")
+ inst.components.aipc_rubik:Start()
+end
+
+------------------------------- 燃料 -------------------------------
+local function ontakefuel(inst)
+ inst.SoundEmitter:PlaySound("dontstarve/common/nightmareAddFuel")
+end
-------------------------------- 魔方 -------------------------------
-local function CreateFire(inst, colorName, x, y, z)
- local tgt = SpawnPrefab("aip_rubik_fire_"..colorName)
- inst:AddChild(tgt)
- tgt.Transform:SetPosition(x, y, z)
+local function onupdatefueled(inst)
+ if inst.components.burnable ~= nil then
+ inst.components.burnable:SetFXLevel(inst.components.fueled:GetCurrentSection(), inst.components.fueled:GetSectionPercent())
+ end
end
-local function initRubik(inst)
- local offset = 2
- local height = 4
- local colors = {"green", "red","blue"}
-
- for oy = 1, 3 do
- local scaleOffset = 1 -- + (3 - oy) / 6
-
- for ox = -1, 1 do
- for oz = -1, 1 do
- CreateFire(
- inst,
- colors[oy],
- ox * offset * scaleOffset,
- oy * offset + height,
- oz * offset * scaleOffset
- )
- end
- end
- end
+local function onfuelchange(newsection, oldsection, inst)
+ if newsection <= 0 then
+ inst.components.burnable:Extinguish()
+ else
+ if not inst.components.burnable:IsBurning() then
+ inst.components.burnable:Ignite()
+ end
+
+ inst.components.burnable:SetFXLevel(newsection, inst.components.fueled:GetSectionPercent())
+ end
end
------------------------------- 实体 -------------------------------
@@ -66,14 +73,18 @@ local function fn()
inst.entity:AddTransform()
inst.entity:AddAnimState()
+ inst.entity:AddSoundEmitter()
inst.entity:AddNetwork()
- MakeInventoryPhysics(inst)
+ MakeObstaclePhysics(inst, .2)
- inst.AnimState:SetBank("aip_22_fish")
- inst.AnimState:SetBuild("aip_22_fish")
+ inst.AnimState:SetBank("aip_rubik")
+ inst.AnimState:SetBuild("aip_rubik")
inst.AnimState:PlayAnimation("idle")
+ inst:AddTag("wildfireprotected")
+ inst:AddTag("aip_rubik")
+
inst.entity:SetPristine()
if not TheWorld.ismastersim then
@@ -81,12 +92,25 @@ local function fn()
end
inst:AddComponent("inspectable")
-
- inst:AddComponent("inventoryitem")
- inst.components.inventoryitem.atlasname = "images/inventoryimages/aip_22_fish.xml"
- inst.components.inventoryitem.imagename = "aip_22_fish"
-
- initRubik(inst)
+ inst:AddComponent("aipc_rubik")
+
+ -- 可以点燃
+ inst:AddComponent("burnable")
+ inst.components.burnable:AddBurnFX("nightlight_flame", Vector3(0, 0, 0), "fire_marker")
+ inst.components.burnable.canlight = false
+ inst:ListenForEvent("onextinguish", onextinguish)
+ inst:ListenForEvent("onignite", onignite)
+
+ -- 使用燃料
+ inst:AddComponent("fueled")
+ inst.components.fueled.maxfuel = TUNING.NIGHTLIGHT_FUEL_MAX
+ inst.components.fueled.accepting = true
+ inst.components.fueled.fueltype = FUELTYPE.NIGHTMARE
+ inst.components.fueled:SetSections(4)
+ inst.components.fueled:SetTakeFuelFn(ontakefuel)
+ inst.components.fueled:SetUpdateFn(onupdatefueled)
+ inst.components.fueled:SetSectionCallback(onfuelchange)
+ inst.components.fueled:InitializeFuelLevel(dev_mode and TUNING.NIGHTLIGHT_FUEL_START or 0)
return inst
end
diff --git a/scripts/prefabs/aip_rubik_fire.lua b/scripts/prefabs/aip_rubik_fire.lua
index 6c789371..6f79316f 100644
--- a/scripts/prefabs/aip_rubik_fire.lua
+++ b/scripts/prefabs/aip_rubik_fire.lua
@@ -1,3 +1,42 @@
+local colors = {
+ red = { 1, 0, 0 },
+ green = { 0, 1, 0 },
+ blue = { 0, 0, 1 },
+ yellow = { 1, 1, 0 },
+}
+
+-- string.upper(name)
+
+------------------------------- 描述 -------------------------------
+local language = aipGetModConfig("language")
+
+local LANG_MAP = {
+ english = {
+ NAME = "Fire",
+ DESC = "Examine close fire to move",
+ },
+ chinese = {
+ NAME = "火焰",
+ DESC = "检查相邻火焰移动",
+ },
+}
+
+local LANG = LANG_MAP[language] or LANG_MAP.english
+
+for colorName, rgb in pairs(colors) do
+ local name = "AIP_RUBIK_FIRE_"..string.upper(colorName)
+ STRINGS.NAMES[name] = LANG.NAME
+ STRINGS.CHARACTERS.GENERIC.DESCRIBE[name] = LANG.DESC
+end
+
+------------------------------- 事件 -------------------------------
+local function onSelect(inst, viewer)
+ if inst.aipRubik ~= nil and inst.aipRubik.components.aipc_rubik ~= nil then
+ inst.aipRubik.components.aipc_rubik:Select(inst)
+ end
+end
+
+------------------------------- 实体 -------------------------------
local MakeTorchFire = require("prefabs/torchfire_common")
local ANIM_HAND_TEXTURE = "fx/animhand.tex"
@@ -7,6 +46,7 @@ local SHADER = "shaders/vfx_particle.ksh"
local REVEAL_SHADER = "shaders/vfx_particle_reveal.ksh"
local assets = {
+ Asset("ANIM", "anim/aip_rubik_fire.zip"),
Asset("IMAGE", ANIM_HAND_TEXTURE),
Asset("IMAGE", ANIM_SMOKE_TEXTURE),
Asset("SHADER", SHADER),
@@ -176,6 +216,18 @@ local function genRubik(colorName, rgb)
--------------------------------------------------------------------------
local function common_postinit(inst)
+ inst.entity:AddAnimState()
+ inst.AnimState:SetBank("aip_rubik_fire")
+ inst.AnimState:SetBuild("aip_rubik_fire")
+ inst.AnimState:PlayAnimation("idle")
+
+ MakeTinyFlyingCharacterPhysics(inst, 1, 0)
+ -- MakeTinyFlyingCharacterPhysics(inst, 0, 0)
+ -- MakeInventoryPhysics(inst, 1, .5)
+ RemovePhysicsColliders(inst)
+
+ inst:RemoveTag("FX")
+
--Dedicated server does not need to spawn local particle fx
if TheNet:IsDedicated() then
return
@@ -186,7 +238,8 @@ local function genRubik(colorName, rgb)
-----------------------------------------------------
local effect = inst.entity:AddVFXEffect()
- effect:InitEmitters(3)
+ -- effect:InitEmitters(3)
+ effect:InitEmitters(1)
--FIRE
effect:SetRenderResources(0, ANIM_SMOKE_TEXTURE, REVEAL_SHADER)
@@ -203,32 +256,31 @@ local function genRubik(colorName, rgb)
effect:SetKillOnEntityDeath(0, true)
effect:SetFollowEmitter(0, true)
- --SMOKE
- effect:SetRenderResources(1, ANIM_SMOKE_TEXTURE, REVEAL_SHADER) --REVEAL_SHADER --particle_add
- effect:SetMaxNumParticles(1, 32)
- effect:SetRotationStatus(1, true)
- effect:SetMaxLifetime(1, SMOKE_MAX_LIFETIME)
- effect:SetColourEnvelope(1, COLOUR_ENVELOPE_NAME_SMOKE)
- effect:SetScaleEnvelope(1, SCALE_ENVELOPE_NAME_SMOKE)
- effect:SetBlendMode(1, BLENDMODE.AlphaBlended) --AlphaBlended Premultiplied
- effect:EnableBloomPass(1, true)
- effect:SetUVFrameSize(1, 1, 1)
- effect:SetSortOrder(1, 0)
- effect:SetSortOffset(1, 1)
-
- --HAND
- effect:SetRenderResources(2, ANIM_HAND_TEXTURE, REVEAL_SHADER) --REVEAL_SHADER --particle_add
- effect:SetMaxNumParticles(2, 32)
- effect:SetRotationStatus(2, true)
- effect:SetMaxLifetime(2, HAND_MAX_LIFETIME)
- effect:SetColourEnvelope(2, COLOUR_ENVELOPE_NAME_HAND)
- effect:SetScaleEnvelope(2, SCALE_ENVELOPE_NAME_HAND)
- effect:SetBlendMode(2, BLENDMODE.AlphaBlended) --AlphaBlended Premultiplied
- effect:EnableBloomPass(2, true)
- effect:SetUVFrameSize(2, .25, 1)
- effect:SetSortOrder(2, 0)
- effect:SetSortOffset(2, 1)
- --effect:SetDragCoefficient(2, 50)
+ -- --SMOKE
+ -- effect:SetRenderResources(1, ANIM_SMOKE_TEXTURE, REVEAL_SHADER) --REVEAL_SHADER --particle_add
+ -- effect:SetMaxNumParticles(1, 32)
+ -- effect:SetRotationStatus(1, true)
+ -- effect:SetMaxLifetime(1, SMOKE_MAX_LIFETIME)
+ -- effect:SetColourEnvelope(1, COLOUR_ENVELOPE_NAME_SMOKE)
+ -- effect:SetScaleEnvelope(1, SCALE_ENVELOPE_NAME_SMOKE)
+ -- effect:SetBlendMode(1, BLENDMODE.AlphaBlended) --AlphaBlended Premultiplied
+ -- effect:EnableBloomPass(1, true)
+ -- effect:SetUVFrameSize(1, 1, 1)
+ -- effect:SetSortOrder(1, 0)
+ -- effect:SetSortOffset(1, 1)
+
+ -- --HAND
+ -- effect:SetRenderResources(2, ANIM_HAND_TEXTURE, REVEAL_SHADER) --REVEAL_SHADER --particle_add
+ -- effect:SetMaxNumParticles(2, 32)
+ -- effect:SetRotationStatus(2, true)
+ -- effect:SetMaxLifetime(2, HAND_MAX_LIFETIME)
+ -- effect:SetColourEnvelope(2, COLOUR_ENVELOPE_NAME_HAND)
+ -- effect:SetScaleEnvelope(2, SCALE_ENVELOPE_NAME_HAND)
+ -- effect:SetBlendMode(2, BLENDMODE.AlphaBlended) --AlphaBlended Premultiplied
+ -- effect:EnableBloomPass(2, true)
+ -- effect:SetUVFrameSize(2, .25, 1)
+ -- effect:SetSortOrder(2, 0)
+ -- effect:SetSortOffset(2, 1)
-----------------------------------------------------
@@ -256,35 +308,33 @@ local function genRubik(colorName, rgb)
end
fire_num_particles_to_emit = fire_num_particles_to_emit + fire_particles_per_tick * math.random() * 3
- --SMOKE
- while smoke_num_particles_to_emit > 1 do
- emit_smoke_fn(effect, sphere_emitter)
- smoke_num_particles_to_emit = smoke_num_particles_to_emit - 1
- end
- smoke_num_particles_to_emit = smoke_num_particles_to_emit + smoke_particles_per_tick
-
- --HAND
- while hand_num_particles_to_emit > 1 do
- emit_hand_fn(effect, sphere_emitter)
- hand_num_particles_to_emit = hand_num_particles_to_emit - 1
- end
- hand_num_particles_to_emit = hand_num_particles_to_emit + hand_particles_per_tick
+ -- --SMOKE
+ -- while smoke_num_particles_to_emit > 1 do
+ -- emit_smoke_fn(effect, sphere_emitter)
+ -- smoke_num_particles_to_emit = smoke_num_particles_to_emit - 1
+ -- end
+ -- smoke_num_particles_to_emit = smoke_num_particles_to_emit + smoke_particles_per_tick
+
+ -- --HAND
+ -- while hand_num_particles_to_emit > 1 do
+ -- emit_hand_fn(effect, sphere_emitter)
+ -- hand_num_particles_to_emit = hand_num_particles_to_emit - 1
+ -- end
+ -- hand_num_particles_to_emit = hand_num_particles_to_emit + hand_particles_per_tick
end)
end
local function master_postinit(inst)
- inst.fx_offset = -100
+ inst:AddComponent("inspectable")
+ inst.components.inspectable.descriptionfn = onSelect
+
+ inst:AddComponent("aipc_float")
end
return MakeTorchFire("aip_rubik_fire_"..colorName, assets, nil, common_postinit, master_postinit)
end
local prefabList = {}
-local colors = {
- red = { 1, 0, 0 },
- green = { 0, 1, 0 },
- blue = { 0, 0, 1 },
-}
for colorName, rgb in pairs(colors) do
table.insert(prefabList, genRubik(colorName, rgb))
diff --git a/scripts/prefabs/aip_rubik_ghost.lua b/scripts/prefabs/aip_rubik_ghost.lua
new file mode 100644
index 00000000..60a49e55
--- /dev/null
+++ b/scripts/prefabs/aip_rubik_ghost.lua
@@ -0,0 +1,176 @@
+-- 开发模式
+local dev_mode = aipGetModConfig("dev_mode") == "enabled"
+
+local brain = require("brains/aip_rubik_ghost_brain")
+
+local assets = {
+ Asset("ANIM", "anim/aip_rubik_ghost.zip"),
+}
+
+local language = aipGetModConfig("language")
+
+local LANG_MAP = {
+ english = {
+ NAME = "Mr. Skits",
+ },
+ chinese = {
+ NAME = "诙谐梦魇",
+ },
+}
+
+local LANG = LANG_MAP[language] or LANG_MAP.english
+
+-- 文字描述
+STRINGS.NAMES.AIP_RUBIK_GHOST = LANG.NAME
+
+local sounds = {
+ attack = "dontstarve/sanity/creature2/attack",
+ attack_grunt = "dontstarve/sanity/creature2/attack_grunt",
+ death = "dontstarve/sanity/creature2/die",
+ idle = "dontstarve/sanity/creature2/idle",
+ taunt = "dontstarve/sanity/creature2/taunt",
+ appear = "dontstarve/sanity/creature2/appear",
+ disappear = "dontstarve/sanity/creature2/dissappear",
+}
+
+------------------------------- 厉火 -------------------------------
+local BaseHealth = dev_mode and 100 or TUNING.WORM_HEALTH
+local MultipleHealth = BaseHealth * 0.5
+
+local BaseDamage = dev_mode and 4 or 40
+local MultipleDamage = BaseDamage * 0.5
+
+local BASE_SCALE = 1
+local MULTIPLE_SCALE = 0.2
+
+local function refreshGrow(inst, nextLevel)
+ local currentLevel = inst.aipGrow or 0
+
+ -- 每次死亡一个单位就飞过来充能一个单位
+ inst.aipGrow = nextLevel
+ local scale = BASE_SCALE + nextLevel * MULTIPLE_SCALE
+ inst.Transform:SetScale(scale, scale, scale)
+
+ -- 更新生命值
+ local healthPTG = inst.components.health:GetPercent()
+ inst.components.health:SetMaxHealth(BaseHealth + nextLevel * MultipleHealth)
+ inst.components.health:SetPercent(healthPTG)
+
+ -- 更新攻击力
+ inst.components.combat:SetDefaultDamage(BaseDamage + nextLevel * MultipleDamage)
+end
+
+------------------------------- 事件 -------------------------------
+local RETARGET_CANT_TAGS = {}
+local RETARGET_ONEOF_TAGS = {"character"}
+local function Retarget(inst)
+ local newtarget = FindEntity(
+ inst,
+ dev_mode and TUNING.BAT_TARGET_DIST or TUNING.PIG_TARGET_DIST,
+ function(guy)
+ return inst.components.combat:CanTarget(guy)
+ end,
+ nil,
+ RETARGET_CANT_TAGS,
+ RETARGET_ONEOF_TAGS
+ )
+
+ return newtarget
+end
+
+local function OnAttacked(inst, data)
+ inst.components.combat:SetTarget(data.attacker)
+ inst.components.combat:ShareTarget(data.attacker, 30, function(dude)
+ return dude:HasTag("aip_rubik_ghost") and not dude.components.health:IsDead()
+ end, 99)
+end
+
+local function OnDead(inst)
+ if inst.aipHeart and inst.aipHeart.aipGhosts then
+ for i, ghost in ipairs(inst.aipHeart.aipGhosts) do
+ if ghost ~= inst and ghost.components.health and not ghost.components.health:IsDead() then
+ -- 飞行灵魂给其他鬼魂
+ local proj = aipSpawnPrefab(inst, "aip_projectile")
+ proj.components.aipc_info_client:SetByteArray( -- 调整颜色
+ "aip_projectile_color", { 0, 0, 0, 5 }
+ )
+ proj.components.aipc_projectile.speed = 10
+ proj.components.aipc_projectile:GoToTarget(ghost, function()
+ if ghost.components and ghost.components.health and not ghost.components.health:IsDead() then
+ refreshGrow(ghost, (ghost.aipGrow or 1) + 1)
+ end
+ end)
+ end
+ end
+ end
+end
+
+------------------------------- 实体 -------------------------------
+local function fn()
+ local inst = CreateEntity()
+
+ inst.entity:AddTransform()
+ inst.entity:AddAnimState()
+ inst.entity:AddSoundEmitter()
+ inst.entity:AddNetwork()
+
+ MakeFlyingCharacterPhysics(inst, 1, .5)
+
+ inst.Transform:SetTwoFaced()
+ -- inst.Transform:SetScale(2, 2, 2)
+
+ inst:AddTag("shadowcreature")
+ inst:AddTag("gestaltnoloot")
+ inst:AddTag("monster")
+ inst:AddTag("hostile")
+ inst:AddTag("shadow")
+ inst:AddTag("notraptrigger")
+
+ inst:AddTag("aip_rubik_ghost")
+
+ inst.AnimState:SetBank("aip_rubik_ghost")
+ inst.AnimState:SetBuild("aip_rubik_ghost")
+ inst.AnimState:PlayAnimation("idle_loop", true)
+ inst.AnimState:SetMultColour(1, 1, 1, .7)
+
+ inst.entity:SetPristine()
+
+ if not TheWorld.ismastersim then
+ return inst
+ end
+
+ inst:AddComponent("locomotor") -- locomotor must be constructed before the stategraph
+ inst.components.locomotor.walkspeed = TUNING.BEEQUEEN_SPEED
+ inst.components.locomotor:SetTriggersCreep(false)
+ inst.components.locomotor.pathcaps = { ignorecreep = true }
+
+ inst.sounds = sounds
+
+ inst:SetStateGraph("SGaip_rubik_ghost")
+ inst:SetBrain(brain)
+
+ inst:AddComponent("health")
+ inst.components.health:SetMaxHealth(BaseHealth)
+
+ inst:AddComponent("knownlocations")
+
+ inst:AddComponent("combat")
+ inst.components.combat.hiteffectsymbol = "body"
+ inst.components.combat:SetDefaultDamage(BaseDamage)
+ inst.components.combat:SetAttackPeriod(TUNING.BEEQUEEN_ATTACK_PERIOD)
+ -- inst.components.combat:SetRange(TUNING.BEE_ATTACK_RANGE)
+ inst.components.combat:SetRetargetFunction(1, Retarget)
+
+ inst:ListenForEvent("attacked", OnAttacked)
+ inst:ListenForEvent("death", OnDead)
+
+ inst.persists = false
+
+ inst:DoTaskInTime(0.1, function()
+ refreshGrow(inst, 0)
+ end)
+
+ return inst
+end
+
+return Prefab("aip_rubik_ghost", fn, assets)
diff --git a/scripts/prefabs/aip_rubik_heart.lua b/scripts/prefabs/aip_rubik_heart.lua
new file mode 100644
index 00000000..4adddb65
--- /dev/null
+++ b/scripts/prefabs/aip_rubik_heart.lua
@@ -0,0 +1,188 @@
+-- 开发模式
+local dev_mode = aipGetModConfig("dev_mode") == "enabled"
+
+local BaseHealth = dev_mode and 100 or TUNING.LEIF_HEALTH
+
+local assets = {
+ Asset("ANIM", "anim/aip_rubik_heart.zip"),
+}
+
+local language = aipGetModConfig("language")
+
+local LANG_MAP = {
+ english = {
+ NAME = "Skits Heart",
+ DESC = "A beating heart",
+ },
+ chinese = {
+ NAME = "诙谐之心",
+ DESC = "一颗跳动的心脏",
+ },
+}
+
+local LANG = LANG_MAP[language] or LANG_MAP.english
+
+-- 文字描述
+STRINGS.NAMES.AIP_RUBIK_HEART = LANG.NAME
+STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_RUBIK_HEART = LANG.DESC
+
+------------------------------- 掉落 -------------------------------
+local loot = {
+ "aip_fake_fly_totem_blueprint",
+ "aip_wizard_hat",
+}
+
+local createProjectile = require("utils/aip_vest_util").createProjectile
+
+------------------------------- 事件 -------------------------------
+-- 移除无效死鬼
+local function refreshGhosts(inst)
+ inst.aipGhosts = aipFilterTable(inst.aipGhosts or {}, function(ghost)
+ return ghost and ghost:IsValid() and ghost.components.health and not ghost.components.health:IsDead()
+ end)
+end
+
+local function onHit(inst)
+ inst.SoundEmitter:PlaySound("dontstarve/creatures/leif/livingtree_hit")
+
+ -- 只有没有其他动作的时候被打才会表现动画
+ if inst.AnimState:IsCurrentAnimation("idle") then
+ inst.AnimState:PlayAnimation("hit")
+ inst.AnimState:PushAnimation("idle", true)
+ end
+
+ if
+ inst.components.health ~= nil and
+ inst.components.health:GetPercent() < 0.5
+ then
+ refreshGhosts(inst)
+
+ -- 血少了,开始吸血玩家
+ if inst.components.timer ~= nil and not inst.components.timer:TimerExists("aip_eat_snity") then
+ inst.components.timer:StartTimer("aip_eat_snity", 5)
+ inst.AnimState:PlayAnimation("spell")
+
+ inst:DoTaskInTime(.5, function() -- 延迟施法
+ if inst.components.health:IsDead() then
+ return
+ end
+
+ local players = aipFindNearPlayers(inst, 10)
+
+ for i, player in ipairs(players) do
+ if player.components.sanity ~= nil and player.components.sanity:GetPercent() > 0 then
+ player.components.sanity:DoDelta(-30)
+
+ createProjectile(player, inst, function()
+ if not inst.components.health:IsDead() then
+ inst.components.health:DoDelta(50)
+ end
+ end, { 0, 0, 0, 5 })
+ end
+ end
+ end)
+ elseif #inst.aipGhosts <= 0 and not inst.components.timer:TimerExists("aip_spawn_ghost") then
+ inst.components.timer:StartTimer("aip_spawn_ghost", 10)
+
+ -- 如果发现没有生物了,我们至少召唤一个
+ local homePos = inst:GetPosition()
+ createProjectile(
+ inst, aipGetSpawnPoint(homePos, 5), function(proj)
+ local effect = aipSpawnPrefab(proj, "aip_shadow_wrapper", nil, 0.1)
+ effect.DoShow()
+
+ -- 有血我们才创建
+ if not inst.components.health:IsDead() then
+ local ghost = aipSpawnPrefab(proj, "aip_rubik_ghost")
+ if ghost.components.knownlocations then
+ ghost.components.knownlocations:RememberLocation("home", homePos)
+ end
+
+ ghost.aipHeart = inst
+ table.insert(inst.aipGhosts, ghost)
+ end
+ end, { 0, 0, 0, 5 }
+ )
+ end
+ end
+end
+
+local function OnDead(inst)
+ if inst.aipGhosts then
+ -- 不用再通知其他鬼魂了
+ local tmpGhosts = inst.aipGhosts
+ inst.aipGhosts = {}
+
+ for i, ghost in ipairs(tmpGhosts) do
+ ghost.aipHeartDead = true
+ if ghost.components.health and not ghost.components.health:IsDead() then
+ ghost.components.health:Kill()
+ end
+ end
+ end
+
+ inst:DoTaskInTime(0.1, function()
+ inst.AnimState:PlayAnimation("dead")
+ inst:ListenForEvent("animover", function()
+ aipSpawnPrefab(inst, "aip_shadow_wrapper", nil, 4).DoShow()
+ local pt = inst:GetPosition()
+ pt.y = 4
+ inst.components.lootdropper:DropLoot(pt)
+ inst:Remove()
+ end)
+ end)
+end
+
+------------------------------- 实体 -------------------------------
+local function fn()
+ local inst = CreateEntity()
+
+ inst.entity:AddTransform()
+ inst.entity:AddAnimState()
+ inst.entity:AddSoundEmitter()
+ inst.entity:AddDynamicShadow()
+ inst.entity:AddNetwork()
+
+ inst.DynamicShadow:SetSize(.8, .5)
+
+ MakeFlyingCharacterPhysics(inst, 0, 0)
+
+ inst:AddTag("aip_shadowcreature") -- 标记的暗影生物,因为默认的不允许攻击
+ inst:AddTag("gestaltnoloot")
+ inst:AddTag("monster")
+ inst:AddTag("hostile")
+ inst:AddTag("shadow")
+ inst:AddTag("notraptrigger")
+
+ inst.AnimState:SetBank("aip_rubik_heart")
+ inst.AnimState:SetBuild("aip_rubik_heart")
+ inst.AnimState:PlayAnimation("idle", true)
+
+ inst.entity:SetPristine()
+
+ if not TheWorld.ismastersim then
+ return inst
+ end
+
+ inst:AddComponent("inspectable")
+
+ inst:AddComponent("timer")
+
+ inst:AddComponent("health")
+ inst.components.health:SetMaxHealth(BaseHealth)
+ inst.components.health.nofadeout = true
+
+ inst:AddComponent("combat")
+ inst.components.combat:SetOnHit(onHit)
+
+ inst:AddComponent("lootdropper")
+ inst.components.lootdropper:SetLoot(loot)
+
+ inst:ListenForEvent("death", OnDead)
+
+ inst.persists = false
+
+ return inst
+end
+
+return Prefab("aip_rubik_heart", fn, assets)
diff --git a/scripts/prefabs/aip_shadow_wrapper.lua b/scripts/prefabs/aip_shadow_wrapper.lua
index 5143ae29..21118384 100755
--- a/scripts/prefabs/aip_shadow_wrapper.lua
+++ b/scripts/prefabs/aip_shadow_wrapper.lua
@@ -62,7 +62,10 @@ function fn()
end
-- Play show
- inst.DoShow = function()
+ inst.DoShow = function(scale)
+ scale = scale or 1
+ inst.Transform:SetScale(scale, scale, scale)
+
inst.SoundEmitter:PlaySound("dontstarve/maxwell/shadowmax_despawn")
inst.AnimState:PlayAnimation("end")
diff --git a/scripts/prefabs/aip_wizard_hat.lua b/scripts/prefabs/aip_wizard_hat.lua
new file mode 100644
index 00000000..9c122063
--- /dev/null
+++ b/scripts/prefabs/aip_wizard_hat.lua
@@ -0,0 +1,72 @@
+-- 配置
+local dress_uses = aipGetModConfig("dress_uses")
+local language = aipGetModConfig("language")
+
+-- 默认参数
+local PERISH_MAP = {
+ ["less"] = 0.5,
+ ["normal"] = 1,
+ ["much"] = 2,
+}
+
+local LANG_MAP = {
+ english = {
+ NAME = "Haunted Wizard Hat",
+ DESC = "Ghosts can't scare me anymore",
+ },
+ chinese = {
+ NAME = "闹鬼巫师帽",
+ DESC = "鬼已经吓不到我了",
+ },
+}
+
+TUNING.AIP_WIZARD_FUEL = TUNING.YELLOWAMULET_FUEL * PERISH_MAP[dress_uses]
+
+local LANG = LANG_MAP[language] or LANG_MAP.english
+
+-- 文字描述
+STRINGS.NAMES.AIP_WIZARD_HAT = LANG.NAME
+STRINGS.CHARACTERS.GENERIC.DESCRIBE.AIP_WIZARD_HAT = LANG.DESC
+
+local function onremovebody(body)
+ body._aipHat._aipEye = nil
+end
+
+-- 配方
+local tempalte = require("prefabs/aip_dress_template")
+
+-- 可以获得直接看到诡影的能力,同时被攻击没有硬直
+return tempalte("aip_wizard_hat", {
+ dapperness = TUNING.DAPPERNESS_LARGE,
+ fueled = {
+ level = TUNING.AIP_WIZARD_FUEL,
+ },
+ waterproofer = true,
+ onEquip = function(inst, owner)
+ if inst._aipAura ~= nil then
+ inst._aipAura:Remove()
+ end
+
+ inst._aipAura = SpawnPrefab("aip_aura_see")
+ inst:AddChild(inst._aipAura)
+ -- if inst._aipEye ~= nil then
+ -- inst._aipEye:Remove()
+ -- end
+ -- inst._aipEye = SpawnPrefab("redlanternbody")
+ -- inst._aipEye._aipHat = inst
+ -- inst:ListenForEvent("onremove", onremovebody, inst._aipEye)
+
+ -- inst._aipEye.entity:SetParent(owner.entity)
+ -- inst._aipEye.entity:AddFollower()
+ -- inst._aipEye.Follower:FollowSymbol(owner.GUID, "hair", 0, 0, 1)
+ end,
+ onUnequip = function(inst, owner)
+ if inst._aipAura ~= nil then
+ inst._aipAura:Remove()
+ end
+ inst._aipAura = nil
+ end,
+ preInst = function(inst)
+ inst:AddTag("aip_no_shadow_stun")
+ end,
+})
diff --git a/scripts/recpiesHooker.lua b/scripts/recpiesHooker.lua
index c32381ba..047ec910 100644
--- a/scripts/recpiesHooker.lua
+++ b/scripts/recpiesHooker.lua
@@ -20,6 +20,16 @@ AddRecipe(
"aip_score_ball.tex"
)
+-- 劣质的飞行图腾
+AddRecipe(
+ "aip_fake_fly_totem",
+ { Ingredient("boards", 1), Ingredient("rope", 1), Ingredient("nightmarefuel", 1) },
+ _G.RECIPETABS.TOWN, _G.TECH.LOST,
+ "aip_fake_fly_totem_placer", nil, nil, nil, nil,
+ "images/inventoryimages/aip_fake_fly_totem.xml",
+ "aip_fake_fly_totem.tex"
+)
+
-- 飞行图腾。只有蓝图可以做出来,现在不提供
AddRecipe(
"aip_fly_totem",
diff --git a/scripts/sgHooker.lua b/scripts/sgHooker.lua
new file mode 100644
index 00000000..3536ecbe
--- /dev/null
+++ b/scripts/sgHooker.lua
@@ -0,0 +1,20 @@
+local _G = GLOBAL
+
+-- 硬直劫持
+AddStategraphPostInit("wilson", function(sg)
+ local originAttackedFn = sg.events.attacked.fn
+
+ sg.events.attacked = _G.EventHandler('attacked', function(inst, data, ...)
+ if -- 暗影生物打不出硬直
+ inst.replica.inventory:EquipHasTag("aip_no_shadow_stun") and
+ data.attacker and
+ (_G.aipIsShadowCreature(data.attacker) or data.attacker:HasTag("ghost"))
+ then
+ if not inst.sg:HasStateTag('frozen') and not inst.sg:HasStateTag('sleeping') then
+ return
+ end
+ end
+
+ return originAttackedFn(inst, data, ...)
+ end)
+end)
\ No newline at end of file
diff --git a/scripts/stategraphs/SGaip_dragon_tail.lua b/scripts/stategraphs/SGaip_dragon_tail.lua
index e2049a7a..d024acaa 100644
--- a/scripts/stategraphs/SGaip_dragon_tail.lua
+++ b/scripts/stategraphs/SGaip_dragon_tail.lua
@@ -106,4 +106,4 @@ local states = {
CommonStates.AddWalkStates(states, nil, nil, true)
-return StateGraph("SGaip_dragon", states, events, "appear")
\ No newline at end of file
+return StateGraph("SGaip_dragon_tail", states, events, "appear")
\ No newline at end of file
diff --git a/scripts/stategraphs/SGaip_rubik_ghost.lua b/scripts/stategraphs/SGaip_rubik_ghost.lua
new file mode 100644
index 00000000..82710f68
--- /dev/null
+++ b/scripts/stategraphs/SGaip_rubik_ghost.lua
@@ -0,0 +1,88 @@
+require("stategraphs/commonstates")
+
+
+local function FinishExtendedSound(inst, soundid)
+ inst.SoundEmitter:KillSound("sound_"..tostring(soundid))
+ inst.sg.mem.soundcache[soundid] = nil
+end
+
+local function PlayExtendedSound(inst, soundname)
+ if inst.sg.mem.soundcache == nil then
+ inst.sg.mem.soundcache = {}
+ inst.sg.mem.soundid = 0
+ else
+ inst.sg.mem.soundid = inst.sg.mem.soundid + 1
+ end
+ inst.sg.mem.soundcache[inst.sg.mem.soundid] = true
+ inst.SoundEmitter:PlaySound(inst.sounds[soundname], "sound_"..tostring(inst.sg.mem.soundid))
+ inst:DoTaskInTime(5, FinishExtendedSound, inst.sg.mem.soundid)
+end
+
+local events = {
+ EventHandler("death", function(inst)
+ inst.sg:GoToState("death")
+ end),
+ CommonHandlers.OnAttack(),
+ CommonHandlers.OnLocomote(false, true),
+}
+
+local states = {
+ State{
+ name = "idle",
+ tags = { "idle", "canrotate" },
+
+ onenter = function(inst)
+ inst.components.locomotor:StopMoving()
+ if not inst.AnimState:IsCurrentAnimation("idle_loop") then
+ inst.AnimState:PlayAnimation("idle_loop", true)
+ end
+ end,
+ },
+
+ State{
+ name = "attack",
+ tags = { "attack", "busy" },
+
+ onenter = function(inst, target)
+ inst.sg.statemem.target = target
+ inst.Physics:Stop()
+ inst.components.combat:StartAttack()
+ inst.AnimState:PlayAnimation("attack")
+ -- PlayExtendedSound(inst, "attack_grunt")
+ inst.sg.statemem.target = inst.components.combat.target
+ end,
+
+ timeline = {-- 1 秒 30 次渲染/1000 帧,340 帧时造成伤害。
+ TimeEvent(0*FRAMES, function(inst) PlayExtendedSound(inst, "attack") end),
+ -- 30*340/1000
+ TimeEvent(10.2*FRAMES, function(inst) inst.components.combat:DoAttack(inst.sg.statemem.target) end),
+ },
+ events = {
+ EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
+ },
+ },
+
+ State{
+ name = "death",
+ tags = { "busy" },
+
+ onenter = function(inst)
+ PlayExtendedSound(inst, "death")
+
+ inst.AnimState:PlayAnimation("death", false)
+
+
+ inst.Physics:Stop()
+ RemovePhysicsColliders(inst)
+ inst:AddTag("NOCLICK")
+ inst.persists = false
+ end,
+ events = {
+ -- EventHandler("animover", function(inst) inst:Remove() end),
+ },
+ },
+}
+
+CommonStates.AddWalkStates(states, nil, nil, true)
+
+return StateGraph("SGaip_rubik_ghost", states, events, "idle")
\ No newline at end of file
diff --git a/scripts/utils/aip_vest_util.lua b/scripts/utils/aip_vest_util.lua
index f8b79441..39917678 100644
--- a/scripts/utils/aip_vest_util.lua
+++ b/scripts/utils/aip_vest_util.lua
@@ -69,8 +69,33 @@ function createGroudVest(bank, build, animate)
return inst
end
+-- 飞到目标点或者物
+function createProjectile(source, target, fn, color, speed)
+ local proj = aipSpawnPrefab(source, "aip_projectile")
+
+ -- 设置颜色
+ if color ~= nil then
+ proj.components.aipc_info_client:SetByteArray( -- 调整颜色
+ "aip_projectile_color", color
+ )
+ end
+
+ if speed ~= nil then
+ proj.components.aipc_projectile.speed = 10
+ end
+
+ if target ~= nil and target.prefab ~= nil then
+ proj.components.aipc_projectile:GoToTarget(target, fn)
+ else
+ proj.components.aipc_projectile:GoToPoint(target, fn)
+ end
+
+ return proj
+end
+
return {
createClientVest = createClientVest,
createEffectVest = createEffectVest,
createGroudVest = createGroudVest,
+ createProjectile = createProjectile,
}
\ No newline at end of file
diff --git a/scripts/widgetHooker.lua b/scripts/widgetHooker.lua
index dd786231..a7b0852a 100644
--- a/scripts/widgetHooker.lua
+++ b/scripts/widgetHooker.lua
@@ -1,8 +1,10 @@
local _G = GLOBAL
-local DestinationScreen = require("widgets/aip_dest_screen")
local PlayerHud = _G.require("screens/playerhud")
+------------------------------- 飞行 -------------------------------
+local DestinationScreen = require("widgets/aip_dest_screen")
+
function PlayerHud:OpenAIPDestination(inst, currentTotemId)
self.aipDestScreen = DestinationScreen(self.owner, currentTotemId)
self:OpenScreenUnderPause(self.aipDestScreen)
@@ -14,4 +16,4 @@ function PlayerHud:CloseAIPDestination()
self.aipDestScreen:Close()
self.aipDestScreen = nil
end
-end
\ No newline at end of file
+end
diff --git a/scripts/widgets/aip_rubik_screen.lua b/scripts/widgets/aip_rubik_screen.lua
new file mode 100644
index 00000000..73c4544b
--- /dev/null
+++ b/scripts/widgets/aip_rubik_screen.lua
@@ -0,0 +1,95 @@
+local Screen = require "widgets/screen"
+local Widget = require "widgets/widget"
+local Text = require "widgets/text"
+local TextEdit = require "widgets/textedit"
+local Menu = require "widgets/menu"
+local UIAnim = require "widgets/uianim"
+local ImageButton = require "widgets/imagebutton"
+
+local function oncancel(doer, widget)
+ if not widget.isopen then
+ return
+ end
+
+ doer.HUD:CloseAIPRubik()
+end
+
+local RubikScreen = Class(Screen, function(self, owner)
+ Screen._ctor(self, "AIP_RubikScreen")
+
+ self.owner = owner
+
+ self.isopen = false
+
+ ----------------------------------- 以下直接抄的木板代码 -----------------------------------
+ self._scrnw, self._scrnh = TheSim:GetScreenSize()
+
+ self:SetScaleMode(SCALEMODE_PROPORTIONAL)
+ self:SetMaxPropUpscale(MAX_HUD_SCALE)
+ self:SetPosition(0, 0, 0)
+ self:SetVAnchor(ANCHOR_MIDDLE)
+ self:SetHAnchor(ANCHOR_MIDDLE)
+
+ self.scalingroot = self:AddChild(Widget("writeablewidgetscalingroot"))
+ self.scalingroot:SetScale(TheFrontEnd:GetHUDScale())
+ self.inst:ListenForEvent("continuefrompause", function()
+ if self.isopen then
+ self.scalingroot:SetScale(TheFrontEnd:GetHUDScale())
+ end
+ end, TheWorld)
+ self.inst:ListenForEvent("refreshhudsize", function(hud, scale)
+ if self.isopen then
+ self.scalingroot:SetScale(scale)
+ end
+ end, owner.HUD.inst)
+
+ self.root = self.scalingroot:AddChild(Widget("writeablewidgetroot"))
+ local scale = 0.8
+ self.root:SetScale(scale, scale, scale)
+
+ -- 不可见的透明背景:mask
+ self.black = self.root:AddChild(Image("images/global.xml", "square.tex"))
+ self.black:SetVRegPoint(ANCHOR_MIDDLE)
+ self.black:SetHRegPoint(ANCHOR_MIDDLE)
+ self.black:SetVAnchor(ANCHOR_MIDDLE)
+ self.black:SetHAnchor(ANCHOR_MIDDLE)
+ self.black:SetScaleMode(SCALEMODE_FILLSCREEN)
+ self.black:SetTint(0, 0, 0, 0)
+ self.black.OnMouseButton = function() oncancel(self.owner, self) end
+
+ -- 木板 UI
+ self.bganim = self.root:AddChild(UIAnim())
+ self.bganim:SetHAnchor(ANCHOR_RIGHT)
+ self.bganim:SetVAnchor(ANCHOR_BOTTOM)
+ self.bganim:SetScale(0.5, 0.5, 0.5)
+
+ self.bganim:GetAnimState():SetBank("ui_board_5x3")
+ self.bganim:GetAnimState():SetBuild("ui_board_5x3")
+
+ self.bganim:SetPosition(-300, 150, 0)
+
+ ----------------------------------- 创建魔方 UI 内容物 -----------------------------------
+
+
+ ----------------------------------- 以下直接抄的木板代码 -----------------------------------
+ self.root:SetPosition(0, 0, 0)
+
+ self.isopen = true
+ self:Show()
+
+ self.bganim:GetAnimState():PlayAnimation("open")
+end)
+
+function RubikScreen:Close()
+ if self.isopen then
+ self.bganim:GetAnimState():PlayAnimation("close")
+
+ self.black:Kill()
+
+ self.isopen = false
+
+ self.inst:DoTaskInTime(.3, function() TheFrontEnd:PopScreen(self) end)
+ end
+end
+
+return RubikScreen
\ No newline at end of file
diff --git a/scripts/writeablesWrapper.lua b/scripts/writeablesWrapper.lua
index e535d055..eca000c5 100644
--- a/scripts/writeablesWrapper.lua
+++ b/scripts/writeablesWrapper.lua
@@ -13,6 +13,8 @@ kinds["aip_fly_totem"] = {
acceptbtn = { text = _G.STRINGS.SIGNS.MENU.ACCEPT, cb = nil, control = _G.CONTROL_ACCEPT },
}
+kinds["aip_fake_fly_totem"] = kinds["aip_fly_totem"]
+
local originMakescreen = writeables.makescreen
writeables.makescreen = function(inst, doer, ...)
diff --git a/tools-scripts/dist.js b/tools-scripts/dist.js
index 3128394b..b94c91af 100644
--- a/tools-scripts/dist.js
+++ b/tools-scripts/dist.js
@@ -99,8 +99,8 @@ async function doJob() {
text = text.replace(src, tgt);
fs.writeFileSync(filepath, text, 'utf8');
}
- replaceText('package/modinfo.lua', /"\(DEV MODE\)",/g, outputMark ? '"(OUTPUT)",' : '');
- replaceText('package/modinfo.lua', /name = "Additional Item Package DEV"/g, `name = "Additional Item Package${outputMark ? ' (OUTPUT)' : ''}"`);
+ replaceText('package/modinfo.lua', /"\(DEV MODE\)",/g, outputMark ? '"(内测)",' : '');
+ replaceText('package/modinfo.lua', /name = "Additional Item Package DEV"/g, `name = "Additional Item Package${outputMark ? ' (测试)' : ''}"`);
replaceText('package/modmain.lua', /TUNING.ZOMBIEJ_ADDTIONAL_PACKAGE = "Additional Item Package DEV"/g, 'TUNING.ZOMBIEJ_ADDTIONAL_PACKAGE = "Additional Item Package"');
// 压缩一下 LUA 代码
@@ -128,7 +128,7 @@ async function doJob() {
fs.copySync('package', relativePath);
console.log(chalk.cyan("Add name mark..."));
- replaceText(path.join(relativePath, '/modinfo.lua'), /name = "Additional Item Package"/g, 'name = "Additional Item Package (output)"');
+ replaceText(path.join(relativePath, '/modinfo.lua'), /name = "Additional Item Package"/g, 'name = "Additional Item Package (内测)"');
// 创建 zip 包
const parentFolder = path.dirname(relativePath);