1-- This file is scripting Kalya's attack animation, called by attack skills, in case she is attacking every foes at once.
2-- The initialize() function is called once, followed by calls to the update function.
3-- When the update function returns true, the attack is finished.
4
5-- Set the namespace
6local ns = {};
7setmetatable(ns, {__index = _G});
8kalya_attack_party_target = ns;
9setfenv(1, ns);
10
11-- local references
12local Battle = nil;
13local character = nil
14local target = nil
15-- An array referencing all the necessary data about the quarrels triggered.
16local target_arrows = {}
17local skill = nil
18
19local global_attack_step = 0;
20local global_attack_time = 0.0;
21
22-- character, the BattleActor attacking (here Kalya)
23-- target, the BattleEnemy target
24-- The skill id used on target
25function Initialize(_character, _target, _skill)
26    -- Keep the reference in memory
27    character = _character;
28    target = _target;
29    skill = _skill;
30    Battle = ModeManager:GetTop();
31
32    local ammo_filename = character:GetAmmoAnimationFile();
33
34    -- Don't attack if the character isn't alive
35    if (character:IsAlive() == false) then
36        return;
37    end
38
39    global_attack_step = 0;
40    global_attack_time = 0.0;
41
42    -- Register all the valid actors in the arrow table
43    local index = 0;
44    while (target:GetPartyActor(index) ~= nil) do
45        local actor = target:GetPartyActor(index)
46        if (actor:CanFight() == true) then
47          target_arrows[index] = {};
48          target_arrows[index].actor = actor
49        end
50
51        index = index + 1
52    end
53
54    -- Register each arrows data
55    for index, arrow_info in pairs(target_arrows) do
56        -- Set the arrow flying height members
57        arrow_info.arrow_height = (character:GetSpriteHeight() / 2.0) + 5.0;
58        arrow_info.total_distance = math.abs(arrow_info.actor:GetXLocation() - character:GetXLocation());
59        arrow_info.height_diff = arrow_info.arrow_height - (arrow_info.actor:GetSpriteHeight() / 2.0);
60        arrow_info.height_min = math.min(arrow_info.arrow_height, (arrow_info.actor:GetSpriteHeight() / 2.0));
61
62        -- Set the arrow starting position
63        arrow_info.arrow_pos_x = character:GetXLocation() + character:GetSpriteWidth() / 2.0;
64        arrow_info.arrow_pos_y = character:GetYLocation() - arrow_info.arrow_height;
65
66        -- Make the arrow reach the enemy center
67        arrow_info.enemy_pos_x = arrow_info.actor:GetXLocation();
68        arrow_info.enemy_pos_y = arrow_info.actor:GetYLocation() - arrow_info.actor:GetSpriteHeight() / 2.0;
69
70        distance_moved_x = SystemManager:GetUpdateTime() / vt_map.MapMode.NORMAL_SPEED * 210.0;
71        local x_diff = arrow_info.enemy_pos_x - arrow_info.arrow_pos_x;
72        local y_diff = arrow_info.arrow_pos_y - arrow_info.enemy_pos_y;
73
74        local a_coeff = 0.0;
75        local distance_moved_x = 0.0;
76        local distance_moved_y = 0.0;
77
78        if (y_diff == 0.0) then
79            a_coeff = 0.0;
80            distance_moved_y = 0.0;
81        elseif (x_diff == 0.0) then
82            a_coeff = 0.0;
83            distance_moved_y = distance_moved_x;
84            distance_moved_x = 0.0;
85        else
86            a_coeff =  y_diff / x_diff;
87            if (a_coeff < 0) then a_coeff = -a_coeff; end
88            distance_moved_y = a_coeff * distance_moved_x;
89        end
90        arrow_info.a_coeff = a_coeff;
91        arrow_info.distance_moved_x = distance_moved_x;
92        arrow_info.distance_moved_y = distance_moved_y;
93
94        arrow_info.attack_step = 0;
95        arrow_info.damage_triggered = false;
96
97        -- Set Kalya's missile sprites
98        arrow_info.arrow = Battle:CreateBattleAnimation(ammo_filename);
99        arrow_info.arrow_shadow = Battle:CreateBattleAnimation(ammo_filename);
100        arrow_info.arrow_shadow:GetAnimatedImage():SetGrayscale(true);
101
102        -- splash images
103        arrow_info.splash_image = Battle:CreateBattleAnimation("data/entities/battle/effects/hit_splash.lua");
104        arrow_info.splash_image:SetVisible(false);
105        arrow_info.splash_width = arrow_info.splash_image:GetAnimatedImage():GetWidth()
106        arrow_info.splash_height = arrow_info.splash_image:GetAnimatedImage():GetHeight()
107
108        index = index + 1
109    end
110end
111
112
113function Update()
114
115    -- Attack the enemy
116    if (global_attack_step == 0) then
117        character:ChangeSpriteAnimation("attack")
118        global_attack_step = 1
119        return false;
120    end
121    -- Make the character go back to idle once attacked
122    if (global_attack_step == 1) then
123        global_attack_time = global_attack_time + SystemManager:GetUpdateTime();
124        if (global_attack_time > 750.0) then
125            character:ChangeSpriteAnimation("idle")
126            global_attack_step = 2;
127        end
128        return false;
129    end
130
131    for index, arrow_info in pairs(target_arrows) do
132
133        -- Make the shadow visible
134        if (arrow_info.attack_step == 0) then
135
136            if (arrow_info.arrow ~= nil) then
137                arrow_info.arrow:SetVisible(true);
138                arrow_info.arrow:SetXLocation(arrow_info.arrow_pos_x);
139                arrow_info.arrow:SetYLocation(arrow_info.arrow_pos_y);
140            end
141
142            if (arrow_info.arrow_shadow ~= nil) then
143                arrow_info.arrow_shadow:SetVisible(true);
144                arrow_info.arrow_shadow:SetXLocation(arrow_info.arrow_pos_x);
145                arrow_info.arrow_shadow:SetYLocation(arrow_info.arrow_pos_y + arrow_info.arrow_height);
146            end
147
148            arrow_info.attack_step = 1
149        end
150
151        -- The update time can vary, so update the distance on each update as well.
152        arrow_info.distance_moved_x = SystemManager:GetUpdateTime() / vt_map.MapMode.NORMAL_SPEED * 210.0;
153        if (arrow_info.a_coeff ~= 0.0) then
154            arrow_info.distance_moved_y = arrow_info.a_coeff * arrow_info.distance_moved_x;
155        end
156
157        -- Make the speed the same whatever the angle between the character and the enemy is.
158        -- We deal only with a coefficients > 1.0 for simplification purpose.
159        if (arrow_info.a_coeff > 1.0 and arrow_info.distance_moved_x ~= 0.0 and arrow_info.distance_moved_y ~= 0.0) then
160            arrow_info.distance_moved_x = arrow_info.distance_moved_x * (arrow_info.distance_moved_x / arrow_info.distance_moved_y);
161            if (arrow_info.a_coeff ~= 0.0) then
162                arrow_info.distance_moved_y = arrow_info.a_coeff * arrow_info.distance_moved_x;
163            end
164            --print ("new_ratio: ", arrow_info.a_coeff, arrow_info.distance_moved_x / arrow_info.distance_moved_y)
165        end
166
167        -- Update the arrow flying height according to the distance
168        -- Get the % of of x distance left
169        local distance_left = math.abs((arrow_info.arrow_pos_x + arrow_info.distance_moved_x) - arrow_info.enemy_pos_x);
170
171        if (arrow_info.total_distance > 0.0) then
172            if (arrow_info.height_diff > 0.0) then
173                arrow_info.arrow_height = arrow_info.height_min + ((distance_left / arrow_info.total_distance) * arrow_info.height_diff);
174            else
175                arrow_info.arrow_height = arrow_info.height_min + (((arrow_info.total_distance - distance_left) / arrow_info.total_distance) * -arrow_info.height_diff);
176            end
177        end
178
179        -- Triggers the arrow animation
180        if (arrow_info.attack_step == 1) then
181            if (arrow_info.arrow_pos_x > arrow_info.enemy_pos_x) then
182                arrow_info.arrow_pos_x = arrow_info.arrow_pos_x - arrow_info.distance_moved_x;
183                if arrow_info.arrow_pos_x < arrow_info.enemy_pos_x then arrow_info.arrow_pos_x = arrow_info.enemy_pos_x end
184            end
185            if (arrow_info.arrow_pos_x < arrow_info.enemy_pos_x) then
186                arrow_info.arrow_pos_x = arrow_info.arrow_pos_x + arrow_info.distance_moved_x;
187                if arrow_info.arrow_pos_x > arrow_info.enemy_pos_x then arrow_info.arrow_pos_x = arrow_info.enemy_pos_x end
188            end
189            if (arrow_info.arrow_pos_y > arrow_info.enemy_pos_y) then
190                arrow_info.arrow_pos_y = arrow_info.arrow_pos_y - arrow_info.distance_moved_y;
191                if arrow_info.arrow_pos_y < arrow_info.enemy_pos_y then arrow_info.arrow_pos_y = arrow_info.enemy_pos_y end
192            end
193            if (arrow_info.arrow_pos_y < arrow_info.enemy_pos_y) then
194                arrow_info.arrow_pos_y = arrow_info.arrow_pos_y + arrow_info.distance_moved_y;
195                if arrow_info.arrow_pos_y > arrow_info.enemy_pos_y then arrow_info.arrow_pos_y = arrow_info.enemy_pos_y end
196            end
197
198            -- Updates the arrow location
199            if (arrow_info.arrow ~= nil) then
200                arrow_info.arrow:SetXLocation(arrow_info.arrow_pos_x);
201                arrow_info.arrow:SetYLocation(arrow_info.arrow_pos_y);
202            end
203
204            if (arrow_info.arrow_shadow ~= nil) then
205                arrow_info.arrow_shadow:SetXLocation(arrow_info.arrow_pos_x);
206                arrow_info.arrow_shadow:SetYLocation(arrow_info.arrow_pos_y + arrow_info.arrow_height);
207            end
208
209            if (arrow_info.arrow_pos_x >= arrow_info.enemy_pos_x and arrow_info.arrow_pos_y == arrow_info.enemy_pos_y) then
210                arrow_info.attack_step = 2;
211            end
212        end
213
214        if (arrow_info.attack_step == 2) then
215            -- Remove the arrow has reached the enemy
216            if (arrow_info.damage_triggered == false) then
217                arrow_info.damage_triggered = true;
218                if (arrow_info.arrow ~= nil) then
219                    arrow_info.arrow:SetVisible(false);
220                    arrow_info.arrow:Remove();
221                    arrow_info.arrow = nil;
222                end
223                if (arrow_info.arrow_shadow ~= nil) then
224                    arrow_info.arrow_shadow:SetVisible(false);
225                    arrow_info.arrow_shadow:Remove();
226                    arrow_info.arrow_shadow = nil;
227                end
228                -- Show the hit splash image
229                arrow_info.splash_image:SetXLocation(arrow_info.arrow_pos_x);
230                arrow_info.splash_image:SetYLocation(arrow_info.arrow_pos_y);
231                arrow_info.splash_image:SetVisible(true);
232                arrow_info.splash_image:Reset();
233                arrow_info.splash_time = 0;
234            end
235            arrow_info.attack_step = 3
236        end
237
238        -- Update the splash image
239        if (arrow_info.attack_step == 3) then
240            arrow_info.splash_time = arrow_info.splash_time + SystemManager:GetUpdateTime();
241            arrow_info.splash_image:GetAnimatedImage():SetDimensions(arrow_info.splash_width * arrow_info.splash_time / 100.0,
242                                                                     arrow_info.splash_height * arrow_info.splash_time / 100.0)
243            if (arrow_info.splash_time > 100) then
244                if (arrow_info.splash_image ~= nil) then
245                    arrow_info.splash_image:SetVisible(false);
246                    arrow_info.splash_image:Remove();
247                    arrow_info.splash_image = nil;
248                end
249                arrow_info.attack_step = 4;
250            end
251        end
252    end
253
254    -- Check whether every target has received an arrow
255    for index, arrow_info in pairs(target_arrows) do
256        if (arrow_info.attack_step < 4) then
257            return false;
258        end
259    end
260
261    -- Remove the skill points at the end of all attacks
262    skill:ExecuteBattleFunction(character, target);
263    character:SubtractSkillPoints(skill:GetSPRequired());
264    return true;
265end
266