File size: 9,636 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
-- Swaying of all canvas objects depends on the wind slot they lie in. Each slot has wind direction and strength.
-- Wind strength can be strong or weak depending on const.StrongWindThreshold which is % of the const.WindMaxStrength
-- absolute value. Canvasses have different sway animation for weak and for strong wind. They also have 2 two different
-- animations depending on whether they are placed "Next to Wall" or "Freely Sway"("Sway Type" property). 
-- This makes for total of 4 animations. There is also "Never Sway" option for that property which just places the object in
-- "idle" state. The animations have these strange names depending on wind strength/sway type:
--
--    - Strong Wind:
--			* "Next to Wall"		- idle_Wind
--			* "Freely Sway" 		- idle_Wind_No_Wall
--    - Weak Wind:
--			* "Next to Wall" 	- idle_Wall
--			* "Freely Sway" 		- idle_No_Wall
--
-- In case the Canvas object is SlabWallWindow it can be also either broken or intact. For its broken version the above
-- animations are using "broken_" as prefix instead of "idle_".
--
-- Changing light model or weather(via GameState) forces updating of the wind animations of Canvas objects. If the object is
-- not "Never Sway" its animation is choosen using the table above. For windows there is additional chance of not swaying
-- even when "Sway Type" is not set to "Never Sway". This chance is different for weak and strong wind and is defined by
-- const.WindStrongSwayChance and const.WindWeakSwayChance. Anyway the canvasses can sway only if they lie on the grid
-- with some positive wind in their slot.
--
-- In the case of weak wind the corresponding canvas animation is blended with the idle/broken base animation using
-- wind strength as animation weight factor. In case of strong wind no blending is executed. Blending can be disabled
-- per entity from Entity Spec Editor using the "Disable Canvas Wind Blending" property.

-- If during the update the animation is changed its phase is randomized so the objects are not synced. 
--
-- FX-es are fired upon changing animations, e.g.:
--		WindWeak-start/end-Window0_1-Wall
--		WindStrong-end-MilitaryFlag_01-No Wall
--
--
-- NOTE: currently const.StrongWindThreshold is 100%.

DefineClass.Canvas = {
	__parents = {"WindAffected", "Object", "PropertyObject"},
	flags = {gofRealTimeAnim = true},
	
	properties = {
		{category = "Canvas", id = "SwayType", name = "Sway Type", editor = "dropdownlist",
			items = {"Never Sway", "Next To Wall", "Freely Sway"}, default = "Freely Sway"},
		{category = "Canvas", id = "StateText", editor = "combo", default = "idle",
			items       = function(obj) return obj:GetStatesTextTable(obj.StateCategory) end,
			OnStartEdit = function(obj) obj:SetRealtimeAnim(true) end,
			OnStopEdit  = function(obj) obj:SetRealtimeAnim(false) end, 
			buttons = {{name = "Play once", func = "BtnTestOnce"}, {name = "Loop", func = "BtnTestLoop"}, {name = "Test", func = "BtnTestState"}},			dont_save = true,
			no_edit = true, dont_save = true,
		},
	},
	
	fx_actor_class = "Canvas",
}

function Canvas:GetBaseWindState()
	local base_state = "idle"
	if IsKindOf(self, "SlabWallWindow") then
		if self.is_destroyed then
			local _
			_, base_state = self:GetDestroyedEntityAndState()
		else
			base_state = self:IsBroken() and "broken" or base_state
		end
	end
	
	if not IsValidAnim(self, base_state) then
		StoreErrorSource(self, string.format("Canvas window does not have '%s' animation, falling back to 'idle'", base_state))
		base_state = "idle"
	end
	
	return base_state
end

function Canvas:RandomizePhase(second_channel)
	local duration = GetAnimDuration(self:GetEntity(), self:GetState())
	local phase = self:Random(duration)
	self:SetAnimPhase(1, phase)
	if second_channel then
		self:SetAnimPhase(2, phase)
	end
end

function Canvas:GetWindAnim(wind_state, base_state)
	local anim = string.format(wind_state, base_state)
	if not IsValidAnim(self, anim) then
		StoreWarningSource(self, string.format("Canvas object without wind animation '%s', falling back to '%s'", anim, base_state))
		return base_state
	end
	
	return anim
end

local strong_chance = const.WindStrongSwayChance
local weak_chance = const.WindWeakSwayChance

function Canvas:ShouldSway()
	local should_sway = self.SwayType ~= "Never Sway" and self.SwayType ~= "Never Sway Broken"
	
	return should_sway and self:GetWindStrength() > 0
end

function Canvas:UpdateWind(sync)
	local base_state = self:GetBaseWindState()
	if not self:ShouldSway(sync) then
		if base_state == "idle" then
			if self:HasState("idle_Static") then
				self:ClearAnim(2)
				self:SetState("idle_Static")
			else
				--StoreWarningSource(self, string.format("Canvas window does not have idle_Static animation, falling back to 'idle'"))
				self:SetState(base_state)
			end
		else
			self:SetState(base_state)
		end
		return
	end
	
	local entity_data = EntityData[self.class] and EntityData[self.class].entity
	local wind_blending_disabled = entity_data.DisableCanvasWindBlending or self:IsStaticAnim(GetStateIdx(base_state))
	local state = self:GetStateText()
	if self:IsStrongWind() then
		wind_blending_disabled = true
		if self.SwayType == "Next To Wall" or self.SwayType == "Next To Wall Broken" then
			local anim = self:GetWindAnim("%s_Wind", base_state)
			if state ~= anim then
				PlayFX("WindWeak", "end", self, "Wall")
				self:SetState(anim)
				self:RandomizePhase()
				PlayFX("WindStrong", "start", self, "Wall")
			end
		elseif self.SwayType == "Freely Sway" then
			local anim = self:GetWindAnim("%s_Wind_No_Wall", base_state)
			if state ~= anim then
				PlayFX("WindWeak", "end", self, "No Wall")
				self:SetState(anim)
				self:RandomizePhase()
				PlayFX("WindStrong", "start", self, "No Wall")
			end
		end
	else
		if self.SwayType == "Next To Wall" or self.SwayType == "Next To Wall Broken" then
			local anim = self:GetWindAnim("%s_Wall", base_state)
			if state ~= anim then
				PlayFX("WindStrong", "end", self, "Wall")
				self:SetAnim(1, anim)
				self:SetAnim(2, base_state)
				self:RandomizePhase(true)
				PlayFX("Blend WindWeak", "start", self, "Wall")
			end
		elseif self.SwayType == "Freely Sway" then
			local anim = self:GetWindAnim("%s_No_Wall", base_state)
			if state ~= anim then
				PlayFX("WindStrong", "end", self, "No Wall")
				self:SetAnim(1, anim)
				self:SetAnim(2, base_state)
				self:RandomizePhase(true)
				PlayFX("WindWeak", "start", self, "No Wall")
			end
		end
		-- change the weighting
		if wind_blending_disabled then
			if not self:IsStaticAnim(self:GetState()) then
				self:SetAnimWeight(1, 100)
				self:SetAnimWeight(2, 0)
			end
		else
			local strong_wind_threshold = GetStrongWindThreshold()
			local wind_strength = self:GetWindStrength()
			assert(wind_strength <= strong_wind_threshold)
			local wind_anim_weight = Max(MulDivTrunc(wind_strength, 100, strong_wind_threshold), 1)
			self:SetAnimWeight(1, wind_anim_weight)
			self:SetAnimWeight(2, 100 - wind_anim_weight)
		end
		self:RandomizePhase(self:GetAnim(2) > 0)
	end
end

function Canvas:OnEditorSetProperty(prop_id)
	if prop_id == "SwayType" then
		self:UpdateWind()
	end
end

DefineClass.CanvasNextToWallOnly = {
	__parents = {"Canvas"},
	
	properties = {
		{category = "Canvas", id = "SwayType", name = "Sway Type", editor = "dropdownlist", read_only = true,
			items = {"Never Sway", "Never Sway Broken", "Next To Wall"}, default = "Next To Wall"},
	},
}

DefineClass.CanvasWindow = {
	__parents = {"CanvasNextToWallOnly", "SlabWallWindow", "AutoAttachCallback"},
	properties = {
		{category = "Canvas", id = "SwayType", name = "Sway Type", editor = "dropdownlist", default = "Next To Wall",
			items = {"Never Sway", "Never Sway Broken", "Next To Wall", "Next To Wall Broken"}
		},
	},
}

function CanvasWindow:PostLoad()
	self:SetProperState()
end

function CanvasWindow:ShouldSway(sync)
	if not Canvas.ShouldSway(self, sync) then
		return false
	end
	local rand = sync and InteractionRand(100) or AsyncRand(100)
	return rand < (self:IsStrongWind() and strong_chance or weak_chance)
end

function CanvasWindow:OnAttachToParent(parent, spot)
	self:SetProperty("SwayType", "Never Sway")
	self:SetProperState()
end

function CanvasWindow:SetWindowState(window_state, no_fx)
	if self.pass_through_state == "intact" and window_state == "broken" then
		self:SetState("idle")
		if not no_fx then
			PlayFX("WindowBreak", "start", self)
		end
	end
	self.pass_through_state = window_state
	self:UpdateWind()
end

function CanvasWindow:SetProperState()
	local broken = self.SwayType == "Next To Wall Broken" or self.SwayType == "Never Sway Broken"
	local state = broken and "broken" or "idle"
	local wind = self:IsStrongWind() and "Wind" or "Wall"
	local anim = string.format("%s_%s", state, wind)
	self.pass_through_state = broken and "broken" or "intact"
	if self.pass_through_state == "intact" and IsKindOf(self, "SlabWallWindowOpen") then
		self.pass_through_state = "open"
	end
	self:SetState(IsValidAnim(self, anim) and anim or state)
end

function CanvasWindow:OnEditorSetProperty(prop_id)
	if prop_id == "SwayType" then
		self:SetProperState()
		self:UpdateWind()
	end
end

DefineClass.CanvasWindowWindStateFallback = {
	__parents = {"CanvasWindow"},
}

function CanvasWindowWindStateFallback:GetWindAnim(wind_state, base_state)
	local anim = string.format(wind_state, base_state)
	if not IsValidAnim(self, anim) then
		return base_state
	end
	
	return anim
end

DefineClass.MilitaryCamp_LegionFlag_Short = {
	__parents = {"Canvas"}
}

local offset = point(3 * guim, 0, 0)

function MilitaryCamp_LegionFlag_Short:GetWindSamplePos()
	return self:GetPos() + Rotate(offset, self:GetAngle())
end