vrtc / sorcererw3n (public) (License: CC0) (since 2022-12-13) (hash sha1)
Custom campaign for WarCraft3-1.27a. It requires complex build tools that are reusable and may serve as an example of dos and donts. Hence, the need for a managed repository.
List of commits:
Subject Hash Author Date (UTC)
feat: Add Dampening Field spell stub b7132682830c7549014a8727b837bf089116b702 Vladyslav Bondarenko 2023-07-28 23:00:39
feat(build)!: Add first Apache Ant build descriptor d31e56fdf056a8928e45d5778e705f7c0984e49f Vladyslav Bondarenko 2023-01-08 17:23:01
feat(build)!: Create valid custom campaign archive 72cf187d0fc7d5438145a6ddf7ad72e23beec42c Vladyslav Bondarenko 2023-01-07 16:12:47
fix(build): Strip unnecessary empty lines 68582b9b3f03620608dd40730a6e23e42f8b8400 Vladyslav Bondarenko 2022-12-17 22:21:46
feat: Parametrize individual spell00 spellcasts 08560a980369527f3fc962e3da384da1ef97ce6f Vladyslav Bondarenko 2022-12-17 22:15:56
feat(doc): Add article on m4 pitfalls 2d95ed54414b150e45c391bc04b81fa6845c2797 Vladyslav Bondarenko 2022-12-17 13:23:09
fix(build)!: Include scripts for preprocessing b16c534a4018408be9df186870f293620696603b Vladyslav Bondarenko 2022-12-17 13:20:30
feat: Add spell stub and it's required resources d2ddbeca89c691847f79233aba8b47792b35c52c Vladyslav Bondarenko 2022-12-15 05:12:18
feat: Package custom unit data when possible e28d95f180bd6511a2a509b4c07a1932237c2273 Vladyslav Bondarenko 2022-12-14 02:23:42
fix!: Show quest buttons and messages correctly 4f17ffd9e6653d1d8f04587291ec9c95818643ad Vladyslav Bondarenko 2022-12-13 21:52:45
feat!: Initial commit b1de946d39ba2c2927cd283c0cb5164fd71e6fa0 Vladyslav Bondarenko 2022-12-13 08:58:53
Commit b7132682830c7549014a8727b837bf089116b702 - feat: Add Dampening Field spell stub
Add basic prototype implementation of Dampening Field spell. See the
description of the spell in the docs.

Graphically the spell is not represented correctly. There are likely
fatal bugs still present in the source.

Next step is likely to be rendering an effective time remaining for the
duration of the spell effect.
Author: Vladyslav Bondarenko
Author date (UTC): 2023-07-28 23:00
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2023-07-28 23:56
Parent(s): 53e008bc1b455d738fcb66273bdf1bf2b0a8ff0c
Signer:
Signing key: EFF9624877D25D02
Signing status: E
Tree: 1b3089b2b1cb98fead47d5fecf094cad5db2e3ed
File Lines added Lines deleted
src/map1/main.j 0 1
src/map1/map1.m4 7 4
src/sorcerer/cmpgn.j 1 0
src/sorcerer/sorcererstub.w3n 0 0
src/sorcerer/war3campaign.w3a 0 0
src/sorcerer/war3campaign.w3u 0 0
src/spellbook/spell01.j 241 0
File src/map1/main.j changed (mode: 100644) (index 4f53180..7deb572)
... ... function map1_init takes nothing returns nothing
144 144 if USER == GetLocalPlayer() then if USER == GetLocalPlayer() then
145 145 call SelectUnit(u, true) call SelectUnit(u, true)
146 146 endif endif
147 call UnitAddAbility(u, 'ACam')
148 147 call SetHeroLevel(u, 10, false) call SetHeroLevel(u, 10, false)
149 148
150 149 call quest_constraint_unit_survives_create(CONSTRAINT00, USER, u) call quest_constraint_unit_survives_create(CONSTRAINT00, USER, u)
File src/map1/map1.m4 changed (mode: 100644) (index 7a0d52c..b96cd6f)
1 1 define(globals, `divert(0)divert(1)')dnl # Cut and paste 'globals' blocks define(globals, `divert(0)divert(1)')dnl # Cut and paste 'globals' blocks
2 2 define(endglobals, `divert(0)divert(2)')dnl define(endglobals, `divert(0)divert(2)')dnl
3 3 divert(2)dnl divert(2)dnl
4 include(src/base/quest.j)dnl
5 include(src/spellbook/spell00.j)dnl
6 include(src/sorcerer/cmpgn.j)dnl
7 include(src/map1/main.j)dnl
4 dnl # WARNING: included files must never contain the tilde glyph,
5 dnl # otherwise this m4 script produces malformed *.j snippet
6 include(`src/base/quest.j')dnl
7 include(`src/spellbook/spell00.j')dnl
8 include(`src/spellbook/spell01.j')dnl
9 include(`src/sorcerer/cmpgn.j')dnl
10 include(`src/map1/main.j')dnl
8 11 divert(0)dnl divert(0)dnl
9 12 undefine(`globals')dnl # Leave 'globals' token as it is undefine(`globals')dnl # Leave 'globals' token as it is
10 13 undefine(`endglobals')dnl # Leave 'endglobals' token as it is undefine(`endglobals')dnl # Leave 'endglobals' token as it is
File src/sorcerer/cmpgn.j changed (mode: 100644) (index 0f85158..a1cedfe)
... ... endglobals
14 14
15 15 function cmpgn_spell_init takes nothing returns nothing function cmpgn_spell_init takes nothing returns nothing
16 16 call spell00_init() call spell00_init()
17 call spell01_init()
17 18 endfunction endfunction
18 19
19 20 function cmpgn_quest_init takes nothing returns nothing function cmpgn_quest_init takes nothing returns nothing
File src/sorcerer/sorcererstub.w3n changed (mode: 100644) (index 002b8f1..e8df16a)
File src/sorcerer/war3campaign.w3a changed (mode: 100644) (index b24608c..4cdf1df)
File src/sorcerer/war3campaign.w3u changed (mode: 100644) (index d6fdd86..ea6bfbf)
File src/spellbook/spell01.j added (mode: 100644) (index 0000000..15876d7)
1 // spell01.j
2
3 // FIXME There is a fatal runtime error hidden somewhere here
4 // TODO Spell descriptions contained in *.w3a may be globalized via *.wts and TRIGSTR_003 mechanism.
5
6 globals
7 // spell01 fields
8 constant integer SPELL01_ABILITY_ID = 'A001'
9 constant integer SPELL01_AID_IMMUNITY = 'ACm2'
10 constant string SPELL01_EFFECT_FILE = "Abilities\\Spells\\Undead\\AntiMagicShell\\AntiMagicShell.mdl"
11 constant string SPELL01_SPLAT_FILE = "ReplaceableTextures\\Splats\\TeleportTarget.blp"
12
13 constant real SPELL01_MAX_DURATION = 3600.0
14 constant real SPELL01_MAX_RADIUS = 512.0
15 constant real SPELL01_MIN_DURATION = 1.0
16 constant real SPELL01_MIN_RADIUS = 32.0
17
18 effect array spell01_effect
19 location array spell01_location
20 real array spell01_radius
21 real array spell01_timeout
22 unit array spell01_caster
23 image array spell01_splat
24
25 group spell01_group = null
26 integer spell01_head = 0
27 endglobals
28
29 // spell01 functions
30 function spell01_destroy takes integer i returns nothing
31 if i < 0 or i >= JASS_MAX_ARRAY_SIZE then
32 return
33 endif
34 set i = IMinBJ(IMaxBJ(0, i), JASS_MAX_ARRAY_SIZE)
35
36 if spell01_location[i] != null then
37 call RemoveLocation(spell01_location[i])
38 endif
39 if spell01_effect[i] != null then
40 call DestroyEffect(spell01_effect[i])
41 endif
42 if spell01_splat[i] != null then
43 call DestroyImage(spell01_splat[i])
44 endif
45 set spell01_caster[i] = null
46 set spell01_effect[i] = null
47 set spell01_location[i] = null
48 set spell01_radius[i] = 0.0
49 set spell01_splat[i] = null
50 set spell01_timeout[i] = 0.0
51 endfunction
52
53 function spell01_group_callback_apply takes nothing returns nothing
54 local unit u = GetEnumUnit()
55
56 if GetUnitAbilityLevel(u, SPELL01_AID_IMMUNITY) <= 0 then
57 call UnitAddAbility(u, SPELL01_AID_IMMUNITY)
58 endif
59 endfunction
60
61 function spell01_group_callback_clear takes nothing returns nothing
62 local unit u = GetEnumUnit()
63
64 if GetUnitAbilityLevel(u, SPELL01_AID_IMMUNITY) >= 1 then
65 call UnitRemoveAbility(u, SPELL01_AID_IMMUNITY)
66 endif
67 endfunction
68
69 function spell01_group_filter takes nothing returns boolean
70 local unit u = GetFilterUnit()
71 return GetUnitState(u, UNIT_STATE_LIFE) >= 1.0
72 endfunction
73
74 function spell01_timer_callback takes nothing returns nothing
75 local timer t = GetExpiredTimer()
76 local real dur = TimerGetElapsed(t)
77 local boolexpr filter
78
79 local group g = null
80 local location o = null
81 local real r = 0.0
82 local unit c = null
83 local real m = 0.0
84
85 local boolean caster_alive = true
86 local boolean caster_exists = true
87 local boolean lifetime_remains = true
88
89 local integer i = 0
90 local integer j = IMinBJ(IMaxBJ(0, spell01_head), JASS_MAX_ARRAY_SIZE)
91
92 set g = spell01_group
93 if null == g then
94 set g = CreateGroup()
95 endif
96
97 set filter = Condition(function spell01_group_filter)
98 call ForGroup(g, function spell01_group_callback_clear)
99 call GroupClear(g)
100 call DestroyGroup(g)
101 set g = CreateGroup()
102 loop
103 exitwhen(i >= j)
104
105 set m = spell01_timeout[i] - dur
106 set spell01_timeout[i] = m
107 set c = spell01_caster[i]
108 set o = spell01_location[i]
109 set r = spell01_radius[i]
110
111 set lifetime_remains = m > 0.0
112 set caster_exists = c != null
113 set caster_alive = false
114 if caster_exists then
115 set caster_alive = GetUnitState(c, UNIT_STATE_LIFE) >= 1.0
116 endif
117
118 if lifetime_remains and caster_alive and o != null and r >= SPELL01_MIN_RADIUS then
119 set r = RMinBJ(RMaxBJ(SPELL01_MIN_RADIUS, r), SPELL01_MAX_RADIUS)
120 call GroupEnumUnitsInRangeOfLoc(g, o, r, filter)
121 else
122 call spell01_destroy(i)
123 endif
124
125 set i = i + 1
126 endloop
127 call ForGroup(g, function spell01_group_callback_apply)
128 set spell01_group = g
129
130 call DestroyBoolExpr(filter)
131 endfunction
132
133 function spell01_cast takes integer ability_id, unit caster, location target_loc, real radius, real duration_sec returns boolean
134 local integer ubersplat_enum = 4
135 local image splat = null
136 local integer i = 0
137 local real x = 0.0
138 local real y = 0.0
139 local real z = 0.0
140 local real s = 0.0
141 local location l
142
143 if null == caster then
144 return false
145 endif
146
147 if null == target_loc then
148 return false
149 endif
150
151 if radius < SPELL01_MIN_RADIUS then
152 return false
153 endif
154
155 if radius > SPELL01_MAX_RADIUS then
156 return false
157 endif
158
159 if duration_sec < SPELL01_MIN_DURATION then
160 return false
161 endif
162
163 if duration_sec > SPELL01_MAX_DURATION then
164 return false
165 endif
166
167 set i = spell01_head
168 if i < 0 then
169 set i = 0
170 endif
171 if i >= JASS_MAX_ARRAY_SIZE then
172 set i = 0
173 endif
174 call spell01_destroy(i)
175
176 set x = GetLocationX(target_loc)
177 set y = GetLocationY(target_loc)
178 set z = 24.0
179 set target_loc = Location(x, y)
180 set spell01_location[i] = target_loc
181 set spell01_caster[i] = caster
182 // AddSpellEffectByIdLoc with EFFECT_TYPE_AREA_EFFECT
183 // will access Areaeffectart field from the Ability tab in object editor.
184 // Good working example is the Mass Teleport effect.
185 set spell01_effect[i] = AddSpellEffectByIdLoc(ability_id, EFFECT_TYPE_AREA_EFFECT, target_loc)
186 set radius = RMinBJ(RMaxBJ(SPELL01_MIN_RADIUS, radius), SPELL01_MAX_RADIUS)
187 set spell01_radius[i] = radius
188 set spell01_timeout[i] = RMinBJ(RMaxBJ(SPELL01_MIN_DURATION, duration_sec), SPELL01_MAX_DURATION)
189 set s = radius * 2.0
190 set splat = CreateImage(SPELL01_SPLAT_FILE, s, s, s, x - radius, y - radius, z, 0, 0, 0, ubersplat_enum)
191 // It is mandatory to call SetImageRenderAlways after CreateImage
192 call SetImageRenderAlways(splat, true)
193 call ShowImage(splat, true)
194 set spell01_splat[i] = splat
195
196 set i = i + 1
197
198 set spell01_head = i
199
200 return true
201 endfunction
202
203 function spell01_trigger_filter takes nothing returns boolean
204 return SPELL01_ABILITY_ID == GetSpellAbilityId()
205 endfunction
206
207 function spell01_trigger_action takes nothing returns nothing
208 local integer spell_id = 0
209 local integer spell_level = 0
210 local location target_point = GetSpellTargetLoc()
211 local real duration_sec = 30.0
212 local unit caster = GetSpellAbilityUnit()
213 local unit target_unit
214 if null == target_point then
215 return
216 endif
217
218 set spell_id = GetSpellAbilityId()
219 set spell_level = IMinBJ(IMaxBJ(0, GetUnitAbilityLevel(caster, spell_id)), 144)
220 if spell_level <= 0 then
221 set spell_level = 1
222 endif
223 set duration_sec = I2R(spell_level) * 10.0
224 call spell01_cast(spell_id, caster, target_point, 256.0, duration_sec)
225
226 call RemoveLocation(target_point)
227 endfunction
228
229 function spell01_init takes nothing returns nothing
230 local boolexpr filter
231 local timer t = CreateTimer()
232 local trigger trg = CreateTrigger()
233
234 set spell01_group = CreateGroup()
235 call TimerStart(t, 0.5, true, function spell01_timer_callback)
236
237 call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
238 set filter = Condition(function spell01_trigger_filter)
239 call TriggerAddCondition(trg, filter)
240 call TriggerAddAction(trg, function spell01_trigger_action)
241 endfunction
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/vrtc/sorcererw3n

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/vrtc/sorcererw3n

Clone this repository using git:
git clone git://git.rocketgit.com/user/vrtc/sorcererw3n

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main