├── examples
├── raw
│ └── pointy_dot.png
├── assets
│ └── atlas.atlas
├── example_rotation
│ ├── example_rotation_dot.script
│ ├── example_rotation_dot.go
│ ├── example_rotation_main.collection
│ └── example_rotation_main.script
├── example_with_defgraph
│ ├── example_with_defgraph_dot.script
│ ├── example_with_defgraph_dot.go
│ ├── example_with_defgraph_main.collection
│ └── example_with_defgraph_main.script
└── main.input_binding
├── .gitignore
├── game.project
├── LICENSE
├── README.md
└── defarmy
└── defarmy.lua
/examples/raw/pointy_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-masih/defarmy/HEAD/examples/raw/pointy_dot.png
--------------------------------------------------------------------------------
/examples/assets/atlas.atlas:
--------------------------------------------------------------------------------
1 | images {
2 | image: "/examples/raw/pointy_dot.png"
3 | }
4 | margin: 0
5 | extrude_borders: 2
6 | inner_padding: 0
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.internal
2 | /build
3 | .externalToolBuilders
4 | .DS_Store
5 | Thumbs.db
6 | .lock-wscript
7 | *.pyc
8 | .project
9 | .cproject
10 | builtins
--------------------------------------------------------------------------------
/game.project:
--------------------------------------------------------------------------------
1 | [project]
2 | title = DefArmy
3 | version = 1.0
4 | dependencies = https://github.com/dev-masih/defgraph/archive/master.zip
5 |
6 | [library]
7 | include_dirs = defarmy
8 |
9 | [input]
10 | game_binding = /examples/main.input_bindingc
11 |
12 | [bootstrap]
13 | main_collection = /examples/example_rotation/example_rotation_main.collectionc
14 |
15 | [script]
16 | shared_state = 1
17 |
18 |
--------------------------------------------------------------------------------
/examples/example_rotation/example_rotation_dot.script:
--------------------------------------------------------------------------------
1 | go.property("army_id", 1)
2 | go.property("debug", false)
3 |
4 | -- require defarmy
5 | local defarmy = require("defarmy.defarmy")
6 |
7 | function init(self)
8 | -- get army id from main
9 | self.soldier_id = defarmy.soldier_create(go.get_position(), vmath.vector3(0, 1, 0), self.army_id)
10 |
11 | if self.debug then
12 | defarmy.soldier_debug_on(self.soldier_id, vmath.vector4(1, 0, 0, 1))
13 | end
14 | end
15 |
16 | function update(self, dt)
17 | local soldier_position, soldier_rotation = defarmy.soldier_move(self.soldier_id, go.get_position(), 80 * dt)
18 | go.set_position(soldier_position)
19 | go.set_rotation(soldier_rotation)
20 | end
--------------------------------------------------------------------------------
/examples/example_with_defgraph/example_with_defgraph_dot.script:
--------------------------------------------------------------------------------
1 | go.property("army_id", 1)
2 | go.property("tint", vmath.vector4(0, 1, 0, 1))
3 |
4 | -- require defarmy
5 | local defarmy = require("defarmy.defarmy")
6 |
7 | function init(self)
8 | sprite.set_constant("#sprite", "tint", self.tint)
9 |
10 | -- Create a new soldier in defarmy
11 | self.soldier_id = defarmy.soldier_create(go.get_position(), vmath.vector3(0, 1, 0), self.army_id)
12 | end
13 |
14 | function update(self, dt)
15 | -- fetch each step position and rotation from defarmy
16 | local soldier_position, soldier_rotation = defarmy.soldier_move(self.soldier_id, go.get_position(), 80 * dt)
17 | go.set_position(soldier_position)
18 | go.set_rotation(soldier_rotation)
19 | end
--------------------------------------------------------------------------------
/examples/example_rotation/example_rotation_dot.go:
--------------------------------------------------------------------------------
1 | components {
2 | id: "dot"
3 | component: "/examples/example_rotation/example_rotation_dot.script"
4 | position {
5 | x: 0.0
6 | y: 0.0
7 | z: 0.0
8 | }
9 | rotation {
10 | x: 0.0
11 | y: 0.0
12 | z: 0.0
13 | w: 1.0
14 | }
15 | }
16 | embedded_components {
17 | id: "sprite"
18 | type: "sprite"
19 | data: "tile_set: \"/examples/assets/atlas.atlas\"\n"
20 | "default_animation: \"pointy_dot\"\n"
21 | "material: \"/builtins/materials/sprite.material\"\n"
22 | "blend_mode: BLEND_MODE_ALPHA\n"
23 | ""
24 | position {
25 | x: 0.0
26 | y: 0.0
27 | z: 0.0
28 | }
29 | rotation {
30 | x: 0.0
31 | y: 0.0
32 | z: 0.0
33 | w: 1.0
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/example_with_defgraph/example_with_defgraph_dot.go:
--------------------------------------------------------------------------------
1 | components {
2 | id: "dot"
3 | component: "/examples/example_with_defgraph/example_with_defgraph_dot.script"
4 | position {
5 | x: 0.0
6 | y: 0.0
7 | z: 0.0
8 | }
9 | rotation {
10 | x: 0.0
11 | y: 0.0
12 | z: 0.0
13 | w: 1.0
14 | }
15 | }
16 | embedded_components {
17 | id: "sprite"
18 | type: "sprite"
19 | data: "tile_set: \"/examples/assets/atlas.atlas\"\n"
20 | "default_animation: \"pointy_dot\"\n"
21 | "material: \"/builtins/materials/sprite.material\"\n"
22 | "blend_mode: BLEND_MODE_ALPHA\n"
23 | ""
24 | position {
25 | x: 0.0
26 | y: 0.0
27 | z: 0.0
28 | }
29 | rotation {
30 | x: 0.0
31 | y: 0.0
32 | z: 0.0
33 | w: 1.0
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/main.input_binding:
--------------------------------------------------------------------------------
1 | key_trigger {
2 | input: KEY_A
3 | action: "key_a"
4 | }
5 | key_trigger {
6 | input: KEY_S
7 | action: "key_s"
8 | }
9 | key_trigger {
10 | input: KEY_D
11 | action: "key_d"
12 | }
13 | key_trigger {
14 | input: KEY_1
15 | action: "key_1"
16 | }
17 | key_trigger {
18 | input: KEY_2
19 | action: "key_2"
20 | }
21 | key_trigger {
22 | input: KEY_3
23 | action: "key_3"
24 | }
25 | key_trigger {
26 | input: KEY_4
27 | action: "key_4"
28 | }
29 | key_trigger {
30 | input: KEY_5
31 | action: "key_5"
32 | }
33 | key_trigger {
34 | input: KEY_6
35 | action: "key_6"
36 | }
37 | mouse_trigger {
38 | input: MOUSE_BUTTON_1
39 | action: "left_click"
40 | }
41 | mouse_trigger {
42 | input: MOUSE_BUTTON_2
43 | action: "right_click"
44 | }
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 dev-masih
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/example_rotation/example_rotation_main.collection:
--------------------------------------------------------------------------------
1 | name: "default"
2 | scale_along_z: 0
3 | embedded_instances {
4 | id: "go"
5 | data: "components {\n"
6 | " id: \"script\"\n"
7 | " component: \"/examples/example_rotation/example_rotation_main.script\"\n"
8 | " position {\n"
9 | " x: 0.0\n"
10 | " y: 0.0\n"
11 | " z: 0.0\n"
12 | " }\n"
13 | " rotation {\n"
14 | " x: 0.0\n"
15 | " y: 0.0\n"
16 | " z: 0.0\n"
17 | " w: 1.0\n"
18 | " }\n"
19 | "}\n"
20 | "embedded_components {\n"
21 | " id: \"factory\"\n"
22 | " type: \"factory\"\n"
23 | " data: \"prototype: \\\"/examples/example_rotation/example_rotation_dot.go\\\"\\n"
24 | "load_dynamically: false\\n"
25 | "\"\n"
26 | " position {\n"
27 | " x: 0.0\n"
28 | " y: 0.0\n"
29 | " z: 0.0\n"
30 | " }\n"
31 | " rotation {\n"
32 | " x: 0.0\n"
33 | " y: 0.0\n"
34 | " z: 0.0\n"
35 | " w: 1.0\n"
36 | " }\n"
37 | "}\n"
38 | ""
39 | position {
40 | x: 300.0
41 | y: 300.0
42 | z: 0.0
43 | }
44 | rotation {
45 | x: 0.0
46 | y: 0.0
47 | z: 0.0
48 | w: 1.0
49 | }
50 | scale3 {
51 | x: 1.0
52 | y: 1.0
53 | z: 1.0
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/example_with_defgraph/example_with_defgraph_main.collection:
--------------------------------------------------------------------------------
1 | name: "default"
2 | scale_along_z: 0
3 | embedded_instances {
4 | id: "go"
5 | data: "components {\n"
6 | " id: \"script\"\n"
7 | " component: \"/examples/example_with_defgraph/example_with_defgraph_main.script\"\n"
8 | " position {\n"
9 | " x: 0.0\n"
10 | " y: 0.0\n"
11 | " z: 0.0\n"
12 | " }\n"
13 | " rotation {\n"
14 | " x: 0.0\n"
15 | " y: 0.0\n"
16 | " z: 0.0\n"
17 | " w: 1.0\n"
18 | " }\n"
19 | "}\n"
20 | "embedded_components {\n"
21 | " id: \"factory\"\n"
22 | " type: \"factory\"\n"
23 | " data: \"prototype: \\\"/examples/example_with_defgraph/example_with_defgraph_dot.go\\\"\\n"
24 | "load_dynamically: false\\n"
25 | "\"\n"
26 | " position {\n"
27 | " x: 0.0\n"
28 | " y: 0.0\n"
29 | " z: 0.0\n"
30 | " }\n"
31 | " rotation {\n"
32 | " x: 0.0\n"
33 | " y: 0.0\n"
34 | " z: 0.0\n"
35 | " w: 1.0\n"
36 | " }\n"
37 | "}\n"
38 | ""
39 | position {
40 | x: 300.0
41 | y: 300.0
42 | z: 0.0
43 | }
44 | rotation {
45 | x: 0.0
46 | y: 0.0
47 | z: 0.0
48 | w: 1.0
49 | }
50 | scale3 {
51 | x: 1.0
52 | y: 1.0
53 | z: 1.0
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/example_with_defgraph/example_with_defgraph_main.script:
--------------------------------------------------------------------------------
1 | -- require defarmy
2 | local defarmy = require "defarmy.defarmy"
3 |
4 | local defgraph = require "defgraph.defgraph"
5 |
6 | function init(self)
7 | msg.post(".", "acquire_input_focus")
8 |
9 | self.speed = 50
10 |
11 | -- Creating a graph with defgraph
12 | defgraph.map_set_properties(2, 5, 5, 50, true)
13 | defgraph.debug_set_properties(vmath.vector4(1, 0, 1, 1), vmath.vector4(0, 1, 0, 1), vmath.vector4(0, 1, 1, 1), 3)
14 | local node01 = defgraph.map_add_node(vmath.vector3(100, 100, 0))
15 | local node02 = defgraph.map_add_node(vmath.vector3(100, 270, 0))
16 | local node03 = defgraph.map_add_node(vmath.vector3(100, 500, 0))
17 | local node04 = defgraph.map_add_node(vmath.vector3(500, 500, 0))
18 | local node05 = defgraph.map_add_node(vmath.vector3(500, 270, 0))
19 | local node06 = defgraph.map_add_node(vmath.vector3(500, 100, 0))
20 | defgraph.map_add_route(node01, node02, true)
21 | defgraph.map_add_route(node02, node03, true)
22 | defgraph.map_add_route(node03, node04, true)
23 | defgraph.map_add_route(node04, node05, true)
24 | defgraph.map_add_route(node05, node06, true)
25 | defgraph.map_add_route(node06, node01, true)
26 |
27 | self.go_pos_1 = vmath.vector3(270, 300, 0)
28 | self.go_pos_2 = vmath.vector3(320, 300, 0)
29 | self.movement_data_1 = defgraph.move_initialize(self.go_pos_1, { node02, node05 }, defgraph.ROUTETYPE.CYCLE, vmath.vector3(0, 1, 0))
30 | self.movement_data_2 = defgraph.move_initialize(self.go_pos_2, { node02, node05 }, defgraph.ROUTETYPE.CYCLE, vmath.vector3(0, 1, 0))
31 |
32 | -- Creating an army and a soldier
33 | self.army_id_1 = defarmy.army_create(self.go_pos_1, go.get_rotation(), 18, true, defarmy.PATTERN.TOP_TO_BOTTOM_SQUARE)
34 | self.army_id_2 = defarmy.army_create(self.go_pos_2, go.get_rotation(), 18, false, defarmy.PATTERN.BOTTOM_TO_TOP_SQUARE)
35 |
36 | factory.create("#factory", vmath.vector3(300, 300, 0), nil, { army_id = self.army_id_1, tint = vmath.vector4(1, 0, 0, 1) }, 0.35)
37 | factory.create("#factory", vmath.vector3(300, 300, 0), nil, { army_id = self.army_id_2, tint = vmath.vector4(0, 1, 0, 1) }, 0.35)
38 |
39 | timer.delay(1.5, true, function()
40 | factory.create("#factory", vmath.vector3(300, 300, 0), nil, { army_id = self.army_id_1, tint = vmath.vector4(1, 0, 0, 1) }, 0.35)
41 | factory.create("#factory", vmath.vector3(300, 300, 0), nil, { army_id = self.army_id_2, tint = vmath.vector4(0, 1, 0, 1) }, 0.35)
42 | end)
43 | end
44 |
45 | function on_input(self, action_id, action)
46 | if action_id == hash("left_click") and action.pressed then
47 | factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, { army_id = self.army_id_1, tint = vmath.vector4(1, 0, 0, 1) }, 0.35)
48 | end
49 | if action_id == hash("right_click") and action.pressed then
50 | factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, { army_id = self.army_id_2, tint = vmath.vector4(0, 1, 0, 1) }, 0.35)
51 | end
52 | end
53 |
54 | function update(self, dt)
55 |
56 | self.movement_data_1, self.move_result_1 = defgraph.move_player(self.go_pos_1, self.speed * dt, self.movement_data_1)
57 | self.movement_data_2, self.move_result_2 = defgraph.move_player(self.go_pos_2, self.speed * dt, self.movement_data_2)
58 |
59 | self.go_pos_1 = self.move_result_1.position
60 | self.go_pos_2 = self.move_result_2.position
61 |
62 | defarmy.army_update_position(self.army_id_1, self.move_result_1.position)
63 | defarmy.army_update_position(self.army_id_2, self.move_result_2.position)
64 |
65 | defarmy.army_update_rotation(self.army_id_1, self.move_result_1.rotation)
66 | defarmy.army_update_rotation(self.army_id_2, self.move_result_2.rotation)
67 |
68 | -- draw debug info of nodes and routes
69 | defgraph.debug_draw_map_nodes()
70 | defgraph.debug_draw_map_routes()
71 |
72 | defgraph.debug_draw_player_move(self.movement_data_1, vmath.vector4(1, 1, 0, 1))
73 | defgraph.debug_draw_player_move(self.movement_data_2, vmath.vector4(1, 0, 1, 1))
74 |
75 | msg.post("@render:", "draw_text", { text = "example with DefGraph", position = vmath.vector3(20, 630, 0) } )
76 | msg.post("@render:", "draw_text", { text = "left click: deploy red dot - right click: deploy green dot", position = vmath.vector3(20, 610, 0) } )
77 | msg.post("@render:", "draw_text", { text = "red pattern: TOP_TO_BOTTOM_SQUARE - green pattern: BOTTOM_TO_TOP_SQUARE", position = vmath.vector3(20, 590, 0) } )
78 | msg.post("@render:", "draw_text", { text = "red army: sticky - green army: not sticky", position = vmath.vector3(20, 570, 0) } )
79 | end
--------------------------------------------------------------------------------
/examples/example_rotation/example_rotation_main.script:
--------------------------------------------------------------------------------
1 | -- require defarmy
2 | local defarmy = require "defarmy.defarmy"
3 |
4 | function init(self)
5 | msg.post(".", "acquire_input_focus")
6 |
7 | self.soldier_debug = false
8 | self.army_debug = false
9 | self.army_is_sticky = true
10 | self.army_pattern = defarmy.PATTERN.BOTTOM_TO_TOP_SQUARE
11 |
12 | -- Creating an army and a soldier
13 | self.army_id = defarmy.army_create(vmath.vector3(250, 250, 0), vmath.quat(), 25, self.army_is_sticky, self.army_pattern)
14 |
15 | for i = 1, 50 do
16 | factory.create("#factory", vmath.vector3(250, 250, 0), nil, { army_id = self.army_id, debug = self.soldier_debug }, 0.45)
17 | end
18 | end
19 |
20 | local function custom_pattern_function(count)
21 | local result = {}
22 | local sum = count
23 | for i = 1 ,count do
24 | local partial = math.modf(sum / 4)
25 | if partial == 1 then
26 | table.insert(result, sum)
27 | return result
28 | end
29 | table.insert(result, partial)
30 | sum = sum - partial
31 | end
32 | end
33 |
34 | function on_input(self, action_id, action)
35 | if action_id == hash("left_click") and action.pressed then
36 | factory.create("#factory", vmath.vector3(action.x, action.y, 0), nil, { army_id = self.army_id, debug = self.soldier_debug }, 0.45)
37 | end
38 | if action_id == hash("right_click") then
39 | local direction_vector = vmath.vector3(action.x, action.y, 0) - vmath.vector3(250, 250, 0)
40 | direction_vector = vmath.normalize(direction_vector)
41 | local quat = vmath.quat_from_to(vmath.vector3(0, 1, 0), direction_vector)
42 |
43 | defarmy.army_update_rotation(self.army_id, quat)
44 | end
45 | if action_id == hash("key_a") and action.pressed then
46 | local members = defarmy.army_members(self.army_id)
47 | if self.soldier_debug then
48 | for i = 1, #members do defarmy.soldier_debug_off(members[i]) end
49 | self.soldier_debug = false
50 | else
51 | for i = 1, #members do defarmy.soldier_debug_on(members[i], vmath.vector4(1, 0, 0, 1)) end
52 | self.soldier_debug = true
53 | end
54 | end
55 | if action_id == hash("key_s") and action.pressed then
56 | if self.army_debug then self.army_debug = false else self.army_debug = true end
57 | end
58 | if action_id == hash("key_d") and action.pressed then
59 | if self.army_is_sticky then
60 | self.army_is_sticky = false
61 | else
62 | self.army_is_sticky = true
63 | end
64 | defarmy.army_update_stickiness(self.army_id, self.army_is_sticky)
65 | end
66 | if action_id == hash("key_1") and action.pressed then
67 | self.army_pattern = defarmy.PATTERN.BOTTOM_TO_TOP_SQUARE
68 | defarmy.army_update_pattern(self.army_id, self.army_pattern)
69 | end
70 | if action_id == hash("key_2") and action.pressed then
71 | self.army_pattern = defarmy.PATTERN.TOP_TO_BOTTOM_SQUARE
72 | defarmy.army_update_pattern(self.army_id, self.army_pattern)
73 | end
74 | if action_id == hash("key_3") and action.pressed then
75 | self.army_pattern = defarmy.PATTERN.TRIANGLE
76 | defarmy.army_update_pattern(self.army_id, self.army_pattern)
77 | end
78 | if action_id == hash("key_4") and action.pressed then
79 | self.army_pattern = defarmy.PATTERN.RHOMBUS_TALL
80 | defarmy.army_update_pattern(self.army_id, self.army_pattern)
81 | end
82 | if action_id == hash("key_5") and action.pressed then
83 | self.army_pattern = defarmy.PATTERN.RHOMBUS_SHORT
84 | defarmy.army_update_pattern(self.army_id, self.army_pattern)
85 | end
86 | if action_id == hash("key_6") and action.pressed then
87 | self.army_pattern = defarmy.PATTERN.CUSTOMIZED
88 | defarmy.army_update_pattern(self.army_id, self.army_pattern, custom_pattern_function)
89 | end
90 | end
91 |
92 | function update(self, dt)
93 | if self.army_debug then
94 | defarmy.army_debug_draw(self.army_id, vmath.vector4(0, 1, 0, 1))
95 | end
96 |
97 | msg.post("@render:", "draw_text", { text = "example rotation", position = vmath.vector3(20, 630, 0) } )
98 | msg.post("@render:", "draw_text", { text = "left click: deploy soldier - right click: change rotation", position = vmath.vector3(20, 610, 0) } )
99 |
100 | if self.soldier_debug then
101 | msg.post("@render:", "draw_text", { text = "key \"A\" trigger soldiers debug draw: ON", position = vmath.vector3(20, 590, 0) } )
102 | else
103 | msg.post("@render:", "draw_text", { text = "key \"A\" trigger soldiers debug draw: OFF", position = vmath.vector3(20, 590, 0) } )
104 | end
105 |
106 | if self.army_debug then
107 | msg.post("@render:", "draw_text", { text = "key \"S\" trigger army debug draw: ON", position = vmath.vector3(20, 570, 0) } )
108 | else
109 | msg.post("@render:", "draw_text", { text = "key \"S\" trigger army debug draw: OFF", position = vmath.vector3(20, 570, 0) } )
110 | end
111 |
112 | if self.army_is_sticky then
113 | msg.post("@render:", "draw_text", { text = "key \"D\" trigger army stickiness: Sticky", position = vmath.vector3(20, 550, 0) } )
114 | else
115 | msg.post("@render:", "draw_text", { text = "key \"D\" trigger army stickiness: Not Sticky", position = vmath.vector3(20, 550, 0) } )
116 | end
117 |
118 | if self.army_pattern == defarmy.PATTERN.BOTTOM_TO_TOP_SQUARE then
119 | msg.post("@render:", "draw_text", { text = "key \"1-2-3-4-5-6\" change army pattern: BOTTOM_TO_TOP_SQUARE", position = vmath.vector3(20, 530, 0) } )
120 | elseif self.army_pattern == defarmy.PATTERN.TOP_TO_BOTTOM_SQUARE then
121 | msg.post("@render:", "draw_text", { text = "key \"1-2-3-4-5-6\" change army pattern: TOP_TO_BOTTOM_SQUARE", position = vmath.vector3(20, 530, 0) } )
122 | elseif self.army_pattern == defarmy.PATTERN.TRIANGLE then
123 | msg.post("@render:", "draw_text", { text = "key \"1-2-3-4-5-6\" change army pattern: TRIANGLE", position = vmath.vector3(20, 530, 0) } )
124 | elseif self.army_pattern == defarmy.PATTERN.RHOMBUS_TALL then
125 | msg.post("@render:", "draw_text", { text = "key \"1-2-3-4-5-6\" change army pattern: RHOMBUS_TALL", position = vmath.vector3(20, 530, 0) } )
126 | elseif self.army_pattern == defarmy.PATTERN.RHOMBUS_SHORT then
127 | msg.post("@render:", "draw_text", { text = "key \"1-2-3-4-5-6\" change army pattern: RHOMBUS_SHORT", position = vmath.vector3(20, 530, 0) } )
128 | else
129 | msg.post("@render:", "draw_text", { text = "key \"1-2-3-4-5-6\" change army pattern: CUSTOMIZED", position = vmath.vector3(20, 530, 0) } )
130 | end
131 | end
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DefArmy v1
2 |
3 |
4 |
5 | This module helps you to create groups (army) of game objects (soldiers) and organize them in several different patterns or your customized pattern and manage moving and rotating game objects as a customizable group.
6 |
7 | The bellow git shows how DefArmy groups members and how it handles changing army pattern in real-time.
8 |
9 |
10 | This module can easily integrate with my other extension, [DefGraph](https://github.com/dev-masih/defgraph). You can use DefGraph to handle movements and rotation of the entire army and then use DefArmy to handle each soldier in the army.
11 | An example of integrating DefGraph with DefArmy, All green soldiers are in a separate army as red soldiers.
12 |
13 |
14 | This is a community project you are welcome to contribute to it, sending PR, suggest a feature or report a bug.
15 |
16 | ## Installation
17 | You can use DefArmy in your project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add:
18 |
19 | https://github.com/dev-masih/defarmy/archive/master.zip
20 |
21 | Once added, you must require the main Lua module via
22 |
23 | ```
24 | local defarmy = require("defarmy.defarmy")
25 | ```
26 | Then you can use the DefArmy functions using this module.
27 |
28 | [Official Defold game asset page for DefArmy](https://defold.com/assets/defarmy/)
29 |
30 | ## Army Settings
31 | There are several parameters that you can assign to an army and you can change these parameters at any time.
32 | #### **Pattern:**
33 | This parameter determines the placement shape of soldiers in an army. this property is an enumeration value that can access via `defarmy.PATTERN` table. you can pass a custom function to create army pattern or choose one of the 5 built-in patterns. for example, in bellow images showed how each pattern will place 18 soldiers.
34 |
35 |
|
|
36 | :-------------: | :-------------: | :-------------:
37 | **BOTTOM_TO_TOP_SQUARE** | **TOP_TO_BOTTOM_SQUARE** | **TRIANGLE**
38 |
39 |
|
40 | :-------------: | :-------------:
41 | **RHOMBUS_TALL** | **RHOMBUS_SHORT**
42 |
43 | **CUSTOMIZED**
44 | You can create a customized pattern, you should select `defarmy.PATTERN.CUSTOMIZED` as the pattern and pass a customized function to create pattern schema. This function will get a `number` as a parameter that is the total count of members in army and return a `table` that specify that each row of army pattern should have what number of soldiers in it, row count should **start from the bottom of the army and ends with top**. note that the sum of row values must be equal to the total count that passed to the customized pattern function. the 0 value rows will automatically remove from the table.
45 |
46 | > example:
47 | >
48 | > for this pattern customize function should return a table like: `{3, 5, 4, 3, 2, 1}` with exactly this order.
49 |
50 | #### **Stickiness:**
51 | This parameter determines is soldiers completely stick to their placements in an army or they follow their placements. this parameter mostly seen when an army is rotating.
52 |
53 |
|
54 | :-------------: | :-------------:
55 | **Sticky** | **Not Sticky**
56 |
57 | ## Functions
58 | These are the list of available functions to use, for better understanding of how this module works, please take a look at project examples.
59 |
60 | ### `defarmy.army_create(army_center_position, army_initial_rotation, member_padding, is_sticky, army_pattern, [pattern_func])`
61 | Create a group of game objects (army) with specified member padding and pattern.
62 | #### **arguments:**
63 | * **army_center_position** `(vector3)` - Army center position
64 | * **army_initial_rotation** `(quat)` - Army initial rotation quat
65 | * **member_padding** `(number)` - Padding between members
66 | * **is_sticky** `(boolean)` - Is members glued to their places in army
67 | * **army_pattern** `(PATTERN)` - Army pattern
68 | * **pattern_func** `(func)` - Optional army customized pattern function `[nil]` *(see Pattern.CUSTOMIZED section)*
69 | #### **return:**
70 | * `(number)` - Newly added army id
71 |
72 | ### `defarmy.army_remove(army_id)`
73 | Remove a grouping of game objects (army) and release it's members.
74 | #### **arguments:**
75 | * **army_id** `(number)` - Army id number
76 |
77 | ### `defarmy.army_members(army_id)`
78 | Return an army members (soldiers) id.
79 | #### **arguments:**
80 | * **army_id** `(number)` - Army id number
81 | #### **return:**
82 | * `(table)` - List of soldier's id that were members of that army
83 |
84 | ### `defarmy.army_update_position(army_id, army_center_position)`
85 | Update an army center position.
86 | #### **arguments:**
87 | * **army_id** `(number)` - Army id number
88 | * **army_center_position** `(vector3)` - New army center position
89 |
90 | ### `defarmy.army_update_rotation(army_id, army_rotation)`
91 | Update an army rotation.
92 | #### **arguments:**
93 | * **army_id** `(number)` - Army id number
94 | * **army_rotation** `(quat)` - New army rotation quat
95 |
96 | ### `defarmy.army_update_pattern(army_id, army_pattern, [pattern_func])`
97 | Update an army pattern.
98 | #### **arguments:**
99 | * **army_id** `(number)` - Army id number
100 | * **army_pattern** `(PATTERN)` - New army pattern
101 | * **pattern_func** `(func)` - Optional army customized pattern function `[nil]` *(see Pattern.CUSTOMIZED section)*
102 |
103 | ### `defarmy.army_update_stickiness(army_id, is_sticky)`
104 | Update an army stickiness.
105 | #### **arguments:**
106 | * **army_id** `(number)` - Army id number
107 | * **is_sticky** `(boolean)` - Is members glued to their places in army
108 |
109 | ### `defarmy.soldier_create(position, initial_direction, [army_id])`
110 | Create a new soldier and optionally assign it to an army.
111 | #### **arguments:**
112 | * **position** `(vector3)` - Soldier current position
113 | * **initial_direction** `(vector3)` - Soldier initial direction vecotr
114 | * **army_id** `(optinal number)` - Optional army id number `[nil]`
115 | #### **return:**
116 | * `(number)` - Newly added soldier id
117 |
118 | ### `defarmy.soldier_join_army(soldier_id, army_id)`
119 | Assign an existing soldier to a given army.
120 | #### **arguments:**
121 | * **soldier_id** `(number)` - Soldier id number
122 | * **army_id** `(number)` - Army id number
123 |
124 | ### `defarmy.soldier_leave_army(soldier_id)`
125 | Deassign a soldier from army.
126 | #### **arguments:**
127 | * **soldier_id** `(number)` - Soldier id number
128 |
129 | ### `defarmy.soldier_remove(soldier_id)`
130 | Completely remove a soldier.
131 | #### **arguments:**
132 | * **soldier_id** `(number)` - Soldier id number
133 |
134 | ### `defarmy.soldier_move(soldier_id, current_position, speed, [threshold])`
135 | Calculate a soldier next postion and rotation.
136 | #### **arguments:**
137 | * **soldier_id** `(number)` - Soldier id number
138 | * **current_position** `(number)` - Soldier current position
139 | * **speed** `(number)` - Soldier speed
140 | * **threshold** `(optinal number)` - Optional soldier placement detection threshold `[1]`
141 | #### **return:**
142 | * `(vector3)` - Soldier next position
143 | * `(quat)` - Soldier next rotation
144 |
145 | ### `defarmy.army_debug_draw(army_id, debug_color)`
146 | Army debugging.
147 | #### **arguments:**
148 | * **army_id** `(number)` - Army id number
149 | * **debug_color** `(vector4)` - Color used for debugging
150 |
151 | ### `defarmy.soldier_debug_on(soldier_id, debug_color)`
152 | Turn on soldier position debugging.
153 | #### **arguments:**
154 | * **soldier_id** `(number)` - Soldier id number
155 | * **debug_color** `(vector4)` - Color used for debugging
156 |
157 | ### `defarmy.soldier_debug_off(soldier_id)`
158 | Turn off soldier position debugging.
159 | #### **arguments:**
160 | * **soldier_id** `(number)` - Soldier id number
161 |
162 | ## Donations
163 | If you really like my work and want to support me, consider donating to me with LTC, BTC or ETH. All donations are optional and are greatly appreciated. 🙏
164 |
165 | | LTC | BTC | ETH |
166 | | ------------- | ------------- | ------------- |
167 | |  |  |  |
168 | | ltc1qm6r32vjahm8wwd688enxnutks0jffc3kqg7ps5 | bc1qcuuc5r4jw38vf2eztsxag68papuwzd25chrepx | 0x02c22832bc115933Ac11388D5A91e0990eE84667 |
169 |
170 | ## License
171 | DefArmy is released under the MIT License. See the [bundled LICENSE](https://github.com/dev-masih/defarmy/blob/master/LICENSE) file for details.
172 |
--------------------------------------------------------------------------------
/defarmy/defarmy.lua:
--------------------------------------------------------------------------------
1 | -- DefArmy
2 | -- This module helps you to create groups (army) of game objects (soldiers) and organize
3 | -- them in several different patterns or your customized pattern and manage moving and
4 | -- rotating game objects as a group.
5 |
6 | local M = {}
7 |
8 | --- Store army data and it's members.
9 | --- Structure: army_list[army_id] = { center_position, pattern_func, radius, rotation, member_padding, is_sticky, member_id_list[]:number }
10 | local army_list = {}
11 |
12 | --- Store soldiers data.
13 | --- Structure: soldier_list[soldier_id] = { army_id, position, direction, status, is_assigned, initial_direction, distance_order, debug_color }
14 | local soldier_list = {}
15 |
16 | local army_id_iterator = 0
17 | local soldier_id_iterator = 0
18 |
19 | -- Math functions
20 | local huge = math.huge
21 | local sqrt = math.sqrt
22 | local pow = math.pow
23 | local modf = math.modf
24 | local atan2 = math.atan2
25 | local max = math.max
26 | local fmod = math.fmod
27 | local ceil = math.ceil
28 |
29 | local SOLDIER_STATUS = {}
30 | SOLDIER_STATUS.OUTSIDE = hash("soldier_status_outside")
31 | SOLDIER_STATUS.WAITING = hash("soldier_status_waiting")
32 | SOLDIER_STATUS.INSIDE = hash("soldier_status_inside")
33 | SOLDIER_STATUS.INPLACE = hash("soldier_status_inplace")
34 |
35 | --- Army patterns
36 | M.PATTERN = {}
37 | M.PATTERN.BOTTOM_TO_TOP_SQUARE = hash("pattern_bottom_to_top_square")
38 | M.PATTERN.TOP_TO_BOTTOM_SQUARE = hash("pattern_top_to_bottom_square")
39 | M.PATTERN.TRIANGLE = hash("pattern_triangle")
40 | M.PATTERN.RHOMBUS_TALL = hash("pattern_rhombus_tall")
41 | M.PATTERN.RHOMBUS_SHORT = hash("pattern_rhombus_short")
42 | M.PATTERN.CUSTOMIZED = hash("pattern_customized")
43 |
44 | local function distance(source, destination)
45 | return sqrt(pow(source.x - destination.x, 2) + pow(source.y - destination.y, 2))
46 | end
47 |
48 | local function debug_mark_soldier(position, soldier_id)
49 | if soldier_list[soldier_id].debug_color then
50 | local padding = army_list[soldier_list[soldier_id].army_id].member_padding / 2
51 | msg.post("@render:", "draw_line", { start_point = position + vmath.vector3(padding, padding, 0), end_point = position + vmath.vector3(-padding, -padding, 0), color = soldier_list[soldier_id].debug_color } )
52 | msg.post("@render:", "draw_line", { start_point = position + vmath.vector3(-padding, padding, 0), end_point = position + vmath.vector3(padding, -padding, 0), color = soldier_list[soldier_id].debug_color } )
53 | end
54 | end
55 |
56 | --- Army debugging.
57 | --- @param army_id (number) army id number
58 | --- @param debug_color (vector4) color used for debugging
59 | function M.army_debug_draw(army_id, debug_color)
60 | assert(army_id, "You must provide a army id")
61 | assert(debug_color, "You must provide a debug color")
62 |
63 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
64 |
65 | msg.post("@render:", "draw_line", { start_point = army_list[army_id].center_position + vmath.vector3(5, 5, 0), end_point = army_list[army_id].center_position + vmath.vector3(-5, -5, 0), color = debug_color } )
66 | msg.post("@render:", "draw_line", { start_point = army_list[army_id].center_position + vmath.vector3(-5, 5, 0), end_point = army_list[army_id].center_position + vmath.vector3(5, -5, 0), color = debug_color } )
67 | msg.post("@render:", "draw_line", { start_point = army_list[army_id].center_position + vmath.vector3(-5, 0, 0), end_point = army_list[army_id].center_position + vmath.vector3(5, 0, 0), color = debug_color } )
68 | msg.post("@render:", "draw_line", { start_point = army_list[army_id].center_position + vmath.vector3(0, -5, 0), end_point = army_list[army_id].center_position + vmath.vector3(0, 5, 0), color = debug_color } )
69 |
70 | local far_point = vmath.vector3(0, (army_list[army_id].radius + 1) * army_list[army_id].member_padding / 2, 0)
71 | far_point = vmath.rotate(army_list[army_id].rotation, far_point) + army_list[army_id].center_position
72 |
73 | msg.post("@render:", "draw_line", { start_point = army_list[army_id].center_position, end_point = far_point, color = debug_color } )
74 |
75 | msg.post("@render:", "draw_line", { start_point = far_point + vmath.vector3(3, 3, 0), end_point = far_point + vmath.vector3(-3, -3, 0), color = debug_color } )
76 | msg.post("@render:", "draw_line", { start_point = far_point + vmath.vector3(-3, 3, 0), end_point = far_point + vmath.vector3(3, -3, 0), color = debug_color } )
77 | msg.post("@render:", "draw_line", { start_point = far_point + vmath.vector3(-3, 0, 0), end_point = far_point + vmath.vector3(3, 0, 0), color = debug_color } )
78 | msg.post("@render:", "draw_line", { start_point = far_point + vmath.vector3(0, -3, 0), end_point = far_point + vmath.vector3(0, 3, 0), color = debug_color } )
79 | end
80 |
81 | --- Turn on soldier position debugging.
82 | --- @param soldier_id (number) soldier id number
83 | --- @param debug_color (vector4) color used for debugging
84 | function M.soldier_debug_on(soldier_id, debug_color)
85 | assert(soldier_id, "You must provide a soldier id")
86 | assert(debug_color, "You must provide a debug color")
87 |
88 | assert(soldier_list[soldier_id], ("Unknown soldier id %s"):format(tostring(soldier_id)))
89 |
90 | soldier_list[soldier_id].debug_color = debug_color
91 | end
92 |
93 | --- Turn off soldier position debugging.
94 | --- @param soldier_id (number) soldier id number
95 | function M.soldier_debug_off(soldier_id)
96 | assert(soldier_id, "You must provide a soldier id")
97 |
98 | assert(soldier_list[soldier_id], ("Unknown soldier id %s"):format(tostring(soldier_id)))
99 |
100 | soldier_list[soldier_id].debug_color = nil
101 | end
102 |
103 | local function pattern_bottom_to_top_square(total_count)
104 | local army_schema = {}
105 | local row_width = ceil(sqrt(total_count))
106 | local reminder = fmod(total_count, row_width)
107 | for i = 1, row_width do
108 | local value = modf(total_count / row_width)
109 | table.insert(army_schema, i, value)
110 | if reminder > 0 then
111 | army_schema[i] = army_schema[i] + 1
112 | reminder = reminder - 1
113 | end
114 | end
115 | return army_schema
116 | end
117 |
118 | local function pattern_top_to_bottom_square(total_count)
119 | local army_schema = {}
120 | local row_width = ceil(sqrt(total_count))
121 | local reminder = fmod(total_count, row_width)
122 | local value = modf(total_count / row_width)
123 | for i = 1, value do
124 | table.insert(army_schema, row_width)
125 | end
126 | if reminder > 0 then
127 | table.insert(army_schema, reminder)
128 | end
129 | return army_schema
130 | end
131 |
132 | local function pattern_triangle(total_count)
133 | local army_schema = {}
134 | local sum = total_count
135 | local row_width = 1
136 | for i = 1, total_count / 2 + 1 do table.insert(army_schema, 0) end
137 | for i = modf(total_count / 2 + 1), 1, -1 do
138 | if sum < row_width then
139 | army_schema[i] = sum
140 | break
141 | else
142 | sum = sum - row_width
143 | army_schema[i] = row_width
144 | row_width = row_width + 1
145 | end
146 | end
147 | return army_schema
148 | end
149 |
150 | local function pattern_rhombus_tall(total_count)
151 | local army_schema = {}
152 | local sum = total_count
153 | local row_width = 0
154 | local middle = modf(total_count / 4) + 1
155 | for i = 1, total_count / 2 + 1 do table.insert(army_schema, 0) end
156 | for i = 1, total_count do
157 | army_schema[middle] = army_schema[middle] + 1
158 | sum = sum - 1
159 | if sum == 0 then break end
160 | for i = 1, row_width do
161 | army_schema[middle + i] = army_schema[middle + i] + 1
162 | sum = sum - 1
163 | if sum == 0 then break end
164 | army_schema[middle - i] = army_schema[middle - i] + 1
165 | sum = sum - 1
166 | if sum == 0 then break end
167 | end
168 | if sum == 0 then break end
169 | row_width = row_width + 1
170 | end
171 | return army_schema
172 | end
173 |
174 | local function pattern_rhombus_short(total_count)
175 | local army_schema = {}
176 | local row_width = 0
177 | local sum = total_count
178 | local middle = modf(total_count / 4) + 1
179 | for i = 1, total_count / 2 + 1 do table.insert(army_schema, 0) end
180 | for i = 1, total_count do
181 | army_schema[middle] = army_schema[middle] + 1
182 | sum = sum - 1
183 | if sum == 0 then break end
184 | for i = 1, modf(row_width) do
185 | army_schema[middle + i] = army_schema[middle + i] + 1
186 | sum = sum - 1
187 | if sum == 0 then break end
188 | army_schema[middle - i] = army_schema[middle - i] + 1
189 | sum = sum - 1
190 | if sum == 0 then break end
191 | end
192 | if sum == 0 then break end
193 | row_width = row_width + 0.5
194 | end
195 | return army_schema
196 | end
197 |
198 | --- Create a group of game objects (army) with specified member padding and pattern.
199 | --- @param army_center_position (vector3) army center position
200 | --- @param army_initial_rotation (quat) army initial rotation quat
201 | --- @param member_padding (number) padding between members
202 | --- @param is_sticky (boolean) is members glued to their places in army
203 | --- @param army_pattern (PATTERN) army pattern
204 | --- @param pattern_func (func|nil) army customized pattern function [nil]
205 | --- @return (number) army id
206 | function M.army_create(army_center_position, army_initial_rotation, member_padding, is_sticky, army_pattern, pattern_func)
207 | assert(army_center_position, "You must provide an army center position")
208 | assert(army_initial_rotation, "You must provide an army initial rotation")
209 | assert(member_padding, "You must provide an member padding")
210 | if is_sticky == nil then assert(is_sticky, "You must provide a stickiness") end
211 | assert(army_pattern, "You must provide an army pattern")
212 |
213 | local pattern_function
214 | if army_pattern == M.PATTERN.BOTTOM_TO_TOP_SQUARE then
215 | pattern_function = pattern_bottom_to_top_square
216 | elseif army_pattern == M.PATTERN.TOP_TO_BOTTOM_SQUARE then
217 | pattern_function = pattern_top_to_bottom_square
218 | elseif army_pattern == M.PATTERN.TRIANGLE then
219 | pattern_function = pattern_triangle
220 | elseif army_pattern == M.PATTERN.RHOMBUS_TALL then
221 | pattern_function = pattern_rhombus_tall
222 | elseif army_pattern == M.PATTERN.RHOMBUS_SHORT then
223 | pattern_function = pattern_rhombus_short
224 | elseif army_pattern == M.PATTERN.CUSTOMIZED then
225 | pattern_function = pattern_func
226 | assert(pattern_function, "You must provide an army pattern function")
227 | else
228 | assert(false, ("Unknown army pattern %s"):format(tostring(army_pattern)))
229 | end
230 |
231 | army_id_iterator = army_id_iterator + 1
232 | local army_id = army_id_iterator
233 | army_list[army_id] = { center_position = army_center_position, member_padding = member_padding, radius = member_padding,
234 | rotation = army_initial_rotation, pattern_func = pattern_function, is_sticky = is_sticky, member_id_list = {} }
235 | return army_id
236 | end
237 |
238 | --- Return an army members id.
239 | --- @param army_id (number) army id number
240 | --- @return (table) list of soldier's id that were members of that army
241 | function M.army_members(army_id)
242 | assert(army_id, "You must provide an army id")
243 |
244 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
245 |
246 | return army_list[army_id].member_id_list
247 | end
248 |
249 | --- Remove a grouping of game objects (army) and release it's members.
250 | --- @param army_id (number) army id number
251 | function M.army_remove(army_id)
252 | assert(army_id, "You must provide an army id")
253 |
254 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
255 |
256 | for i = 1, #army_list[army_id].member_id_list do
257 | soldier_list[army_list[army_id].member_id_list[i]].army_id = 0
258 | soldier_list[army_list[army_id].member_id_list[i]].status = SOLDIER_STATUS.OUTSIDE
259 | soldier_list[army_list[army_id].member_id_list[i]].is_assigned = false
260 | end
261 | army_list[army_id] = nil
262 | end
263 |
264 | local function count_soldiers_not_outside_army(army_id)
265 | local count = 0
266 | for _, soldier_id in pairs(army_list[army_id].member_id_list) do
267 | if soldier_list[soldier_id].status ~= SOLDIER_STATUS.OUTSIDE then
268 | count = count + 1
269 | end
270 | end
271 | return count
272 | end
273 |
274 | local function rearenge_army(army_id, prefered_start_position)
275 | -- create army schema according to pattern function
276 | local total_count = count_soldiers_not_outside_army(army_id)
277 | local army_schema = army_list[army_id].pattern_func(total_count)
278 |
279 | -- remove empty rows in schema
280 | local counter = 0
281 | local j, len = 1, #army_schema
282 | for i = 1, len do
283 | if (army_schema[i] ~= 0) then
284 | counter = counter + army_schema[i]
285 | if (i ~= j) then
286 | army_schema[j] = army_schema[i]
287 | army_schema[i] = nil
288 | end
289 | j = j + 1
290 | else
291 | army_schema[i] = nil
292 | end
293 | end
294 | assert(counter == total_count, "Pattern is not add up to total count")
295 |
296 | -- assign army radius
297 | army_list[army_id].radius = #army_schema
298 |
299 | -- Store placeholder data.
300 | -- Structure: placeholder_list[placeholder_id] = { position, is_occupied, distance_order }
301 | local placeholder_list = {}
302 |
303 | -- create pattern placeholder from schema
304 | local reltive_x = 0
305 | local reltive_y = 0
306 | local padding = army_list[army_id].member_padding
307 | for i = 1, #army_schema do
308 | for j = 1, army_schema[i] do
309 | local mod_cell = army_schema[i] % 2
310 | local mod_row = #army_schema % 2
311 |
312 | if mod_cell == 0 then
313 | local rel_x = j - (army_schema[i] / 2)
314 | reltive_x = (rel_x - 1) * padding + (padding / 2)
315 | else
316 | local rel_x = j - ((army_schema[i] + 1) / 2)
317 | reltive_x = rel_x * padding
318 | end
319 |
320 | if mod_row == 0 then
321 | local rel_y = i - (#army_schema / 2)
322 | reltive_y = (rel_y - 1) * padding + (padding / 2)
323 | else
324 | local rel_y = i - ((#army_schema + 1) / 2)
325 | reltive_y = rel_y * padding
326 | end
327 |
328 | table.insert(placeholder_list, { position = vmath.vector3(reltive_x, reltive_y, 0), is_occupied = false, distance_order = 0 })
329 | end
330 | army_list[army_id].radius = max(army_list[army_id].radius, army_schema[i])
331 | end
332 |
333 | -- restart soldiers assigned flag
334 | local waiting_soldier_member_id = 1
335 | for i = 1, #army_list[army_id].member_id_list do
336 | soldier_list[army_list[army_id].member_id_list[i]].is_assigned = false
337 | if soldier_list[army_list[army_id].member_id_list[i]].status == SOLDIER_STATUS.INPLACE then
338 | soldier_list[army_list[army_id].member_id_list[i]].status = SOLDIER_STATUS.INSIDE
339 | elseif soldier_list[army_list[army_id].member_id_list[i]].status == SOLDIER_STATUS.WAITING then
340 | waiting_soldier_member_id = i
341 | end
342 | end
343 |
344 | -- allow the waiting soldier to first pick nearest placeholder if prefered_start_position was not set
345 | if not prefered_start_position then
346 | local min_dist = huge
347 | local min_placeholder_index = 1
348 |
349 | for i = 1, #placeholder_list do
350 | local new_dist = distance(vmath.rotate(army_list[army_id].rotation, placeholder_list[i].position),
351 | soldier_list[army_list[army_id].member_id_list[waiting_soldier_member_id]].position)
352 | if new_dist < min_dist then
353 | min_dist = new_dist
354 | min_placeholder_index = i
355 | end
356 | end
357 |
358 | prefered_start_position = placeholder_list[min_placeholder_index].position
359 | soldier_list[army_list[army_id].member_id_list[waiting_soldier_member_id]].position = prefered_start_position
360 | soldier_list[army_list[army_id].member_id_list[waiting_soldier_member_id]].is_assigned = true
361 | placeholder_list[min_placeholder_index].is_occupied = true
362 | end
363 |
364 | -- calculate each placeholder distance_order in relative to first picked placeholder
365 | for i = 1, #placeholder_list do
366 | placeholder_list[i].distance_order = distance(placeholder_list[i].position, prefered_start_position)
367 | end
368 |
369 | -- sort placeholders from high to low distance_order
370 | table.sort(placeholder_list, function(x, y) return x.distance_order > y.distance_order end)
371 |
372 | -- calculate each soldier distance_order in relative to first picked soldier
373 | for i = 1, #army_list[army_id].member_id_list do
374 | soldier_list[army_list[army_id].member_id_list[i]].distance_order = distance(soldier_list[army_list[army_id].member_id_list[i]].position,
375 | prefered_start_position)
376 | end
377 |
378 | -- sort soldiers from high to low distance_order
379 | table.sort(army_list[army_id].member_id_list, function(x, y) return soldier_list[x].distance_order > soldier_list[y].distance_order end)
380 |
381 | -- assign each placeholder to it's nearest soldier
382 | for i = 1, #placeholder_list do
383 | if not placeholder_list[i].is_occupied then
384 | local min_dist = huge
385 | local min_soldier_id = 1
386 |
387 | for j = 1, #army_list[army_id].member_id_list do
388 | if not soldier_list[army_list[army_id].member_id_list[j]].is_assigned then
389 | local dist = distance(placeholder_list[i].position,
390 | soldier_list[army_list[army_id].member_id_list[j]].position)
391 |
392 | if soldier_list[army_list[army_id].member_id_list[j]].status ~= SOLDIER_STATUS.OUTSIDE then
393 | local far_dist = distance(soldier_list[army_list[army_id].member_id_list[j]].position, prefered_start_position) -
394 | distance(placeholder_list[i].position, prefered_start_position)
395 | dist = dist - far_dist
396 | end
397 |
398 | if dist < min_dist then
399 | min_dist = dist
400 | min_soldier_id = j
401 | end
402 | end
403 | end
404 |
405 | soldier_list[army_list[army_id].member_id_list[min_soldier_id]].position = placeholder_list[i].position
406 | soldier_list[army_list[army_id].member_id_list[min_soldier_id]].is_assigned = true
407 | placeholder_list[i].is_occupied = true
408 | end
409 | end
410 | end
411 |
412 | --- Update an army center position.
413 | --- @param army_id (number) army id number
414 | --- @param army_center_position (vector3) new army center position
415 | function M.army_update_position(army_id, army_center_position)
416 | assert(army_id, "You must provide an army id")
417 | assert(army_center_position, "You must provide an army center position")
418 |
419 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
420 |
421 | army_list[army_id].center_position = army_center_position
422 | end
423 |
424 | --- Update an army rotation.
425 | --- @param army_id (number) army id number
426 | --- @param army_rotation (quat) new army rotation quat
427 | function M.army_update_rotation(army_id, army_rotation)
428 | assert(army_id, "You must provide an army id")
429 | assert(army_rotation, "You must provide an army rotation")
430 |
431 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
432 |
433 | army_list[army_id].rotation = army_rotation
434 | end
435 |
436 | --- Update an army pattern.
437 | --- @param army_id (number) army id number
438 | --- @param army_pattern (PATTERN) new army pattern
439 | --- @param pattern_func (func|nil) army customized pattern function [nil]
440 | function M.army_update_pattern(army_id, army_pattern, pattern_func)
441 | assert(army_id, "You must provide an army id")
442 | assert(army_pattern, "You must provide an army pattern")
443 |
444 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
445 |
446 | local pattern_function
447 | if army_pattern == M.PATTERN.BOTTOM_TO_TOP_SQUARE then
448 | pattern_function = pattern_bottom_to_top_square
449 | elseif army_pattern == M.PATTERN.TOP_TO_BOTTOM_SQUARE then
450 | pattern_function = pattern_top_to_bottom_square
451 | elseif army_pattern == M.PATTERN.TRIANGLE then
452 | pattern_function = pattern_triangle
453 | elseif army_pattern == M.PATTERN.RHOMBUS_TALL then
454 | pattern_function = pattern_rhombus_tall
455 | elseif army_pattern == M.PATTERN.RHOMBUS_SHORT then
456 | pattern_function = pattern_rhombus_short
457 | elseif army_pattern == M.PATTERN.CUSTOMIZED then
458 | pattern_function = pattern_func
459 | assert(pattern_function, "You must provide an army pattern function")
460 | else
461 | assert(false, ("Unknown army pattern %s"):format(tostring(army_pattern)))
462 | end
463 |
464 | army_list[army_id].pattern_func = pattern_function
465 | rearenge_army(army_id, vmath.vector3(0, 0, 0))
466 | end
467 |
468 | --- Update an army stickiness.
469 | --- @param army_id (number) army id number
470 | --- @param is_sticky (boolean) is members glued to their places in army
471 | function M.army_update_stickiness(army_id, is_sticky)
472 | assert(army_id, "You must provide an army id")
473 | if is_sticky == nil then assert(is_sticky, "You must provide a stickiness") end
474 |
475 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
476 |
477 | army_list[army_id].is_sticky = is_sticky
478 |
479 | for i = 1, #army_list[army_id].member_id_list do
480 | if soldier_list[army_list[army_id].member_id_list[i]].status == SOLDIER_STATUS.INPLACE then
481 | soldier_list[army_list[army_id].member_id_list[i]].status = SOLDIER_STATUS.INSIDE
482 | end
483 | end
484 | end
485 |
486 | --- Create a new soldier and optionally assign it to an army.
487 | --- @param position (vector3) soldier current position
488 | --- @param initial_direction (vector3) soldier initial direction vecotr
489 | --- @param army_id (number|nil) optional army id number
490 | --- @return (number) soldier id
491 | function M.soldier_create(position, initial_direction, army_id)
492 | assert(position, "You must provide a position")
493 | assert(initial_direction, "You must provide an initial direction")
494 |
495 | if army_id then
496 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
497 | else
498 | army_id = 0
499 | end
500 |
501 | soldier_id_iterator = soldier_id_iterator + 1
502 | local soldier_id = soldier_id_iterator
503 |
504 | soldier_list[soldier_id] = { army_id = army_id, position = position, direction = initial_direction,
505 | status = SOLDIER_STATUS.OUTSIDE, is_assigned = false,
506 | initial_direction = initial_direction, distance_order = 0 }
507 |
508 | if army_id ~= 0 then
509 | table.insert(army_list[army_id].member_id_list, soldier_id)
510 | end
511 |
512 | return soldier_id
513 | end
514 |
515 | --- Assign an existing soldier to a given army.
516 | --- @param soldier_id (number) soldier id number
517 | --- @param army_id (number) army id number
518 | function M.soldier_join_army(soldier_id, army_id)
519 | assert(soldier_id, "You must provide a soldier id")
520 | assert(army_id, "You must provide an army id")
521 |
522 | assert(soldier_list[soldier_id], ("Unknown soldier id %s"):format(tostring(soldier_id)))
523 | assert(army_list[army_id], ("Unknown army id %s"):format(tostring(army_id)))
524 |
525 | local last_army_id = soldier_list[soldier_id].army_id
526 | if last_army_id ~= 0 then
527 | for i = 1, #army_list[last_army_id].member_id_list do
528 | if soldier_id == army_list[last_army_id].member_id_list[i] then
529 | table.remove(army_list[last_army_id].member_id_list, i)
530 | break
531 | end
532 | end
533 | end
534 |
535 | soldier_list[soldier_id].army_id = army_id
536 | table.insert(army_list[army_id].member_id_list, soldier_id)
537 | end
538 |
539 | --- Deassign a soldier from army.
540 | --- @param soldier_id (number) soldier id number
541 | function M.soldier_leave_army(soldier_id)
542 | assert(soldier_id, "You must provide a soldier id")
543 |
544 | assert(soldier_list[soldier_id], ("Unknown soldier id %s"):format(tostring(soldier_id)))
545 |
546 | if soldier_list[soldier_id].army_id ~= 0 then
547 | local army_id = soldier_list[soldier_id].army_id
548 |
549 | soldier_list[soldier_id].army_id = 0
550 | soldier_list[soldier_id].status = SOLDIER_STATUS.OUTSIDE
551 | soldier_list[soldier_id].is_assigned = false
552 |
553 | for i = 1, #army_list[army_id].member_id_list do
554 | if soldier_id == army_list[army_id].member_id_list[i] then
555 | table.remove(army_list[army_id].member_id_list, i)
556 | break
557 | end
558 | end
559 |
560 | rearenge_army(army_id, soldier_list[soldier_id].postion)
561 | end
562 | end
563 |
564 | --- Completely remove a soldier.
565 | --- @param soldier_id (number) soldier id number
566 | function M.soldier_remove(soldier_id)
567 | assert(soldier_id, "You must provide a soldier id")
568 |
569 | assert(soldier_list[soldier_id], ("Unknown soldier id %s"):format(tostring(soldier_id)))
570 |
571 | local army_id = soldier_list[soldier_id].army_id
572 | local last_position = soldier_list[soldier_id].postion
573 | soldier_list[soldier_id] = nil
574 |
575 | if army_id ~= 0 then
576 | for i = 1, #army_list[army_id].member_id_list do
577 | if soldier_id == army_list[army_id].member_id_list[i] then
578 | table.remove(army_list[army_id].member_id_list, i)
579 | break
580 | end
581 | end
582 | rearenge_army(army_id, last_position)
583 | end
584 | end
585 |
586 | local function normalize(vec)
587 | if vec.x == 0 and vec.y == 0 then
588 | return vmath.vector3()
589 | else
590 | return vmath.normalize(vec)
591 | end
592 | end
593 |
594 | --- Calculate a soldier next postion and rotation.
595 | --- @param soldier_id (number) soldier id number
596 | --- @param current_position (vecotr3) soldier current position
597 | --- @param speed (number) soldier speed
598 | --- @param threshold (number|nil) optional soldier placement detection threshold [1]
599 | --- @return (vector3) soldier next position
600 | --- @return (quat) soldier next rotation
601 | function M.soldier_move(soldier_id, current_position, speed, threshold)
602 | assert(soldier_id, "You must provide a soldier id")
603 | assert(current_position, "You must provide a current position")
604 | assert(speed, "You must provide a speed")
605 |
606 | -- if soldier removed
607 | if soldier_list[soldier_id] == nil then
608 | return nil, nil
609 | end
610 |
611 | -- if soldier is not in any army
612 | if soldier_list[soldier_id].army_id == 0 then
613 | local rotation = vmath.quat_rotation_z(atan2(soldier_list[soldier_id].direction.y, soldier_list[soldier_id].direction.x)
614 | - atan2(soldier_list[soldier_id].initial_direction.y, soldier_list[soldier_id].initial_direction.x))
615 |
616 | debug_mark_soldier(current_position, soldier_id)
617 | return current_position, rotation
618 | end
619 |
620 | local army_id = soldier_list[soldier_id].army_id
621 |
622 | if soldier_list[soldier_id].status == SOLDIER_STATUS.OUTSIDE then
623 | if distance(army_list[army_id].center_position, current_position) < (army_list[army_id].radius + 3) * army_list[army_id].member_padding / 2 then
624 | -- soldier is inside army area
625 | soldier_list[soldier_id].status = SOLDIER_STATUS.WAITING
626 | soldier_list[soldier_id].position = soldier_list[soldier_id].position - army_list[army_id].center_position
627 |
628 | rearenge_army(army_id)
629 |
630 | soldier_list[soldier_id].status = SOLDIER_STATUS.INSIDE
631 | local direction_vector = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].position) + army_list[army_id].center_position - current_position
632 | direction_vector.z = 0
633 | direction_vector = normalize(direction_vector)
634 | local position = (current_position + direction_vector * speed)
635 |
636 | local rotation_vector = vmath.lerp(0.2 * speed, soldier_list[soldier_id].direction, direction_vector)
637 | local rotation = vmath.quat_rotation_z(atan2(rotation_vector.y, rotation_vector.x) - atan2(soldier_list[soldier_id].initial_direction.y, soldier_list[soldier_id].initial_direction.x))
638 | soldier_list[soldier_id].direction = rotation_vector
639 |
640 | return position, rotation
641 | else
642 | -- soldier is outside of army area
643 | local dist_partial = (army_list[army_id].radius + 2) * army_list[army_id].member_padding / 2
644 | local dist_total = distance(current_position, army_list[army_id].center_position)
645 | soldier_list[soldier_id].position = vmath.lerp(dist_partial / dist_total, army_list[army_id].center_position, current_position)
646 |
647 | debug_mark_soldier(soldier_list[soldier_id].position, soldier_id)
648 |
649 | local direction_vector = soldier_list[soldier_id].position - current_position
650 | direction_vector.z = 0
651 | direction_vector = normalize(direction_vector)
652 | local position = (current_position + direction_vector * speed)
653 |
654 | local rotation_vector = vmath.lerp(0.2 * speed, soldier_list[soldier_id].direction, direction_vector)
655 | local rotation = vmath.quat_rotation_z(atan2(rotation_vector.y, rotation_vector.x) - atan2(soldier_list[soldier_id].initial_direction.y, soldier_list[soldier_id].initial_direction.x))
656 | soldier_list[soldier_id].direction = rotation_vector
657 |
658 | return position, rotation
659 | end
660 | elseif soldier_list[soldier_id].status == SOLDIER_STATUS.INPLACE then
661 | local position = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].position) + army_list[army_id].center_position
662 |
663 | local rotated_initial_direction = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].initial_direction)
664 | local rotation_vector = vmath.lerp(0.2 * speed, soldier_list[soldier_id].direction, rotated_initial_direction)
665 | local rotation = vmath.quat_rotation_z(atan2(rotation_vector.y, rotation_vector.x) - atan2(soldier_list[soldier_id].initial_direction.y, soldier_list[soldier_id].initial_direction.x))
666 | soldier_list[soldier_id].direction = rotation_vector
667 |
668 | debug_mark_soldier(position, soldier_id)
669 | return position, rotation
670 | else
671 | local soldier_position = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].position) + army_list[army_id].center_position
672 | if not threshold then threshold = 1 end
673 | if distance(soldier_position, current_position) > threshold then
674 | -- soldier is not in placeholder
675 | local direction_vector = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].position) + army_list[army_id].center_position
676 |
677 | debug_mark_soldier(direction_vector, soldier_id)
678 |
679 | direction_vector = direction_vector - current_position
680 | direction_vector.z = 0
681 | direction_vector = normalize(direction_vector)
682 | local position = (current_position + direction_vector * speed)
683 |
684 | local rotation_vector = vmath.lerp(0.2 * speed, soldier_list[soldier_id].direction, direction_vector)
685 | local rotation = vmath.quat_rotation_z(atan2(rotation_vector.y, rotation_vector.x) - atan2(soldier_list[soldier_id].initial_direction.y, soldier_list[soldier_id].initial_direction.x))
686 | soldier_list[soldier_id].direction = rotation_vector
687 |
688 | return position, rotation
689 | else
690 | -- soldier is in placeholder
691 | if army_list[army_id].is_sticky then
692 | soldier_list[soldier_id].status = SOLDIER_STATUS.INPLACE
693 | end
694 |
695 | local position = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].position) + army_list[army_id].center_position
696 |
697 | local rotated_initial_direction = vmath.rotate(army_list[army_id].rotation, soldier_list[soldier_id].initial_direction)
698 | local rotation_vector = vmath.lerp(0.2 * speed, soldier_list[soldier_id].direction, rotated_initial_direction)
699 | local rotation = vmath.quat_rotation_z(atan2(rotation_vector.y, rotation_vector.x) - atan2(soldier_list[soldier_id].initial_direction.y, soldier_list[soldier_id].initial_direction.x))
700 | soldier_list[soldier_id].direction = rotation_vector
701 |
702 | debug_mark_soldier(position, soldier_id)
703 | return position, rotation
704 | end
705 | end
706 | end
707 |
708 | return M
--------------------------------------------------------------------------------