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: 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 08560a980369527f3fc962e3da384da1ef97ce6f - feat: Parametrize individual spell00 spellcasts
Allow spell00 damage and healing scale with the ability level of the
caster. Additionally, draw lightning effects to visualize the path that
the spell takes.
Author: Vladyslav Bondarenko
Author date (UTC): 2022-12-17 22:15
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2022-12-17 22:15
Parent(s): 2d95ed54414b150e45c391bc04b81fa6845c2797
Signer:
Signing key: EFF9624877D25D02
Signing status: E
Tree: 5503a47456dafe548cadf95a3c0d2d8e79ddebfc
File Lines added Lines deleted
src/map1/main.j 3 6
src/map1/map1.w3x 0 0
src/sorcerer/war3campaign.w3a 0 0
src/spellbook/spell00.j 103 54
File src/map1/main.j changed (mode: 100644) (index 2547c60..4f53180)
1 1 // map1/main.j // map1/main.j
2 2
3 // divert(1)
4 3 globals globals
5 4 constant player PLAYER_UNDEAD = Player(1) constant player PLAYER_UNDEAD = Player(1)
6 5 constant player PLAYER_TROLL = Player(2) constant player PLAYER_TROLL = Player(2)
7 6 constant integer CONSTRAINT00 = 0 constant integer CONSTRAINT00 = 0
8 7 constant integer CONSTRAINT01 = 1 constant integer CONSTRAINT01 = 1
9 8 endglobals endglobals
10 // divert
11 9
12 // divert(2)
13 10 //*************************************************************************** //***************************************************************************
14 11 //* //*
15 12 //* Players //* Players
 
... ... function map1_init takes nothing returns nothing
139 136 set n = CreateUnit(USER, 'hfoo', x + 128.0, y + 128.0, bj_UNIT_FACING) set n = CreateUnit(USER, 'hfoo', x + 128.0, y + 128.0, bj_UNIT_FACING)
140 137 call PauseUnit(n, true) call PauseUnit(n, true)
141 138
142 set n = CreateUnit(PLAYER_TROLL, 'edry', x + 128.0, y + 128.0, bj_UNIT_FACING)
139 set n = CreateUnit(PLAYER_TROLL, 'edry', x - 128.0, y - 128.0, bj_UNIT_FACING)
143 140 call PauseUnit(n, true) call PauseUnit(n, true)
144 set n = CreateUnit(PLAYER_TROLL, 'hfoo', x + 128.0, y + 128.0, bj_UNIT_FACING)
141 set n = CreateUnit(PLAYER_TROLL, 'hfoo', x - 128.0, y - 128.0, bj_UNIT_FACING)
145 142 call PauseUnit(n, true) call PauseUnit(n, true)
146 143
147 144 if USER == GetLocalPlayer() then if USER == GetLocalPlayer() then
148 145 call SelectUnit(u, true) call SelectUnit(u, true)
149 146 endif endif
150 147 call UnitAddAbility(u, 'ACam') call UnitAddAbility(u, 'ACam')
148 call SetHeroLevel(u, 10, false)
151 149
152 150 call quest_constraint_unit_survives_create(CONSTRAINT00, USER, u) call quest_constraint_unit_survives_create(CONSTRAINT00, USER, u)
153 151 call quest_constraint_unit_survives_create(CONSTRAINT01, PLAYER_TROLL, u1) call quest_constraint_unit_survives_create(CONSTRAINT01, PLAYER_TROLL, u1)
 
... ... function config takes nothing returns nothing
199 197 call InitCustomPlayerSlots( ) call InitCustomPlayerSlots( )
200 198 call InitCustomTeams( ) call InitCustomTeams( )
201 199 endfunction endfunction
202 // divert
File src/map1/map1.w3x changed (mode: 100644) (index d7c608d..a684545)
File src/sorcerer/war3campaign.w3a changed (mode: 100644) (index dd622c5..b24608c)
File src/spellbook/spell00.j changed (mode: 100644) (index 6b153d9..2038039)
1 1 // spell00.j // spell00.j
2 2
3 // divert(1)
4 3 globals globals
5 constant integer AID_SPELL00 = 'A000'
6 constant real SPELL00_RADIUS = 128.0
7
8 integer array spell00_stack
9 integer spell00_stack_size = 0
10 constant integer SPELL00_PRECISION = 12
4 // spell00 fields
5 constant integer SPELL00_ABILITY_ID = 'A000'
6 constant real SPELL00_DAMAGE_AMOUNT_PER_SPELL_LEVEL = 35.0
7 constant real SPELL00_DAMAGE_AMOUNT_BASE = 15.0
8 constant real SPELL00_HEALING_AMOUNT_BASE = 10.0
9 constant real SPELL00_HEALING_AMOUNT_PER_SPELL_LEVEL = 20.0
10 constant real SPELL00_RADIUS = 64.0
11 constant string SPELL00_EFFECT_FILE_DAMAGE = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
12 constant string SPELL00_EFFECT_FILE_HEAL = "Abilities\\Spells\\Items\\AIre\\AIreTarget.mdl"
13
14 unit spell00_current_caster = null
15 real spell00_current_damage = 0.0
16 real spell00_current_healing = 0.0
11 17 endglobals endglobals
12 // divert(0)
13
14 // divert(2)
15 function spell00_filter takes nothing returns boolean
16 call BJDebugMsg(":15 check")
17 if AID_SPELL00 == GetSpellAbilityId() then
18 call BJDebugMsg(":15 " + I2S(AID_SPELL00) + " == " + I2S(GetSpellAbilityId()))
19 return true
20 else
21 call BJDebugMsg(":15 " + I2S(AID_SPELL00) + " != " + I2S(GetSpellAbilityId()))
22 return false
23 endif
24 endfunction
25 18
19 // spell00 functions
26 20 function spell00_group_filter_friendly takes nothing returns boolean function spell00_group_filter_friendly takes nothing returns boolean
27 21 local unit f = GetFilterUnit() local unit f = GetFilterUnit()
28 22 return IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE) and GetUnitState(f, UNIT_STATE_LIFE) >= 1.0 return IsUnitType(f, UNIT_TYPE_MAGIC_IMMUNE) and GetUnitState(f, UNIT_STATE_LIFE) >= 1.0
 
... ... function spell00_group_filter_hostile takes nothing returns boolean
34 28 endfunction endfunction
35 29
36 30 function spell00_group_callback_friendly takes nothing returns nothing function spell00_group_callback_friendly takes nothing returns nothing
31 local unit caster = spell00_current_caster
37 32 local unit u = GetEnumUnit() local unit u = GetEnumUnit()
38 33 local real a = GetUnitState(u, UNIT_STATE_LIFE) local real a = GetUnitState(u, UNIT_STATE_LIFE)
39 local real d = 50.0
34 local real b
35 local real d = RMinBJ(RMaxBJ(0.0, spell00_current_healing), 65536.0)
40 36 local real x = GetUnitX(u) local real x = GetUnitX(u)
41 37 local real y = GetUnitY(u) local real y = GetUnitY(u)
38 if IsPlayerEnemy(GetOwningPlayer(u), GetOwningPlayer(caster)) then
39 set d = d * 2.0
40 endif
42 41 call SetUnitState(u, UNIT_STATE_LIFE, a + d) call SetUnitState(u, UNIT_STATE_LIFE, a + d)
43 call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Items\\AIre\\AIreTarget.mdl", x, y))
42 set b = GetUnitState(u, UNIT_STATE_LIFE)
43 if b > a then
44 call DestroyEffect(AddSpecialEffectTarget(SPELL00_EFFECT_FILE_HEAL, u, "origin"))
45 endif
44 46 endfunction endfunction
45 47
46 48 function spell00_group_callback_hostile takes nothing returns nothing function spell00_group_callback_hostile takes nothing returns nothing
47 local unit caster = GetEnumUnit() // FIXME
49 local boolean attack_flag = false
50 local boolean ranged_flag = false
51 local real a
52 local real b
53 local real d = RMinBJ(RMaxBJ(0.0, spell00_current_damage), 65536.0)
48 54 local unit u = GetEnumUnit() local unit u = GetEnumUnit()
49 local real d = 150.0
50 local boolean attack = false
51 local boolean ranged = false
52 local attacktype attackType = ATTACK_TYPE_MAGIC
53 local damagetype damageType = DAMAGE_TYPE_MAGIC
54 local weapontype weaponType = WEAPON_TYPE_WHOKNOWS
55 55 local real x = GetUnitX(u) local real x = GetUnitX(u)
56 56 local real y = GetUnitY(u) local real y = GetUnitY(u)
57 call UnitDamageTarget(caster, u, d, attack, ranged, attackType, damageType, weaponType)
57 local unit caster = spell00_current_caster
58 58
59 call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", x, y))
59 set a = GetUnitState(u, UNIT_STATE_LIFE)
60 call UnitDamageTarget(caster, u, d, attack_flag, ranged_flag, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
61 set b = GetUnitState(u, UNIT_STATE_LIFE)
62
63 if b < a then
64 call DestroyEffect(AddSpecialEffectTarget(SPELL00_EFFECT_FILE_DAMAGE, u, "origin"))
65 endif
60 66 endfunction endfunction
61 67
62 function spell00_cast takes unit caster, location o, real directionX, real directionY, real offset, real length, real damage_amount, real healing_amount returns nothing
68 function spell00_cast takes unit caster, location o, location destination, real offset, real distance, real damage_amount, real healing_amount returns nothing
63 69 local boolexpr filter_friendly local boolexpr filter_friendly
64 70 local boolexpr filter_hostile local boolexpr filter_hostile
65 71 local group g local group g
66 72 local integer i local integer i
67 73 local integer j local integer j
74 local real directionX
75 local real directionY
68 76 local real magnitude local real magnitude
69 77 local real ox local real ox
70 78 local real oy local real oy
79 local real midx
80 local real midy
81 local real endx
82 local real endy
83 local real r
71 84 local real x local real x
72 85 local real y local real y
73 local real r
86 local lightning bolt0
87 local lightning bolt1
88 local real rand
74 89
75 90 if null == caster then if null == caster then
76 call BJDebugMsg(":61 invalid argument")
77 91 return return
78 92 endif endif
79 93
80 94 if null == o then if null == o then
81 call BJDebugMsg(":65 invalid argument")
95 return
96 endif
97
98 if null == destination then
82 99 return return
83 100 endif endif
84 101
85 102 if offset < 0.0 then if offset < 0.0 then
86 call BJDebugMsg(":71 invalid argument")
87 103 return return
88 104 endif endif
89 105
90 if length < 32.0 then
91 call BJDebugMsg(":76 invalid argument")
106 if distance < 32.0 then
92 107 return return
93 108 endif endif
94 109
95 110 if damage_amount < 0.0 then if damage_amount < 0.0 then
96 call BJDebugMsg(":81 invalid argument")
97 111 return return
98 112 endif endif
99 113
100 114 if healing_amount < 0.0 then if healing_amount < 0.0 then
101 call BJDebugMsg(":86 invalid argument")
102 115 return return
103 116 endif endif
104 117
118 set directionX = GetLocationX(destination) - GetLocationX(o)
119 set directionY = GetLocationY(destination) - GetLocationY(o)
105 120 set magnitude = SquareRoot(directionX * directionX + directionY * directionY) set magnitude = SquareRoot(directionX * directionX + directionY * directionY)
106 if magnitude <= 1.1 then
107 call BJDebugMsg(":92 invalid argument")
121 if 0.0 >= magnitude then
108 122 return return
109 123 endif endif
110 124 set directionX = directionX / magnitude set directionX = directionX / magnitude
111 125 set directionY = directionY / magnitude set directionY = directionY / magnitude
112 126
113 call BJDebugMsg(":102 cast")
114
115 127 set ox = GetLocationX(o) + offset * directionX set ox = GetLocationX(o) + offset * directionX
116 128 set oy = GetLocationY(o) + offset * directionY set oy = GetLocationY(o) + offset * directionY
117 129
 
... ... function spell00_cast takes unit caster, location o, real directionX, real direc
119 131
120 132 set r = SPELL00_RADIUS set r = SPELL00_RADIUS
121 133
134 set spell00_current_caster = caster
135 set spell00_current_damage = damage_amount
136 set spell00_current_healing = healing_amount
137
122 138 set filter_friendly = Condition(function spell00_group_filter_friendly) set filter_friendly = Condition(function spell00_group_filter_friendly)
123 139 set filter_hostile = Condition(function spell00_group_filter_hostile) set filter_hostile = Condition(function spell00_group_filter_hostile)
124 140
125 set j = R2I(length / r) + 1
141 set j = R2I(distance / r) + 1
126 142 set i = 0 set i = 0
127 143 loop loop
128 144 exitwhen (i >= j) exitwhen (i >= j)
 
... ... function spell00_cast takes unit caster, location o, real directionX, real direc
140 156 set i = i + 1 set i = i + 1
141 157 endloop endloop
142 158
159 set spell00_current_caster = null
160 set spell00_current_damage = 0.0
161 set spell00_current_healing = 0.0
162
143 163 call DestroyBoolExpr(filter_friendly) call DestroyBoolExpr(filter_friendly)
144 164 call DestroyBoolExpr(filter_hostile) call DestroyBoolExpr(filter_hostile)
165
166 set rand = SPELL00_RADIUS * 0.8
167 set midx = ox + distance / 2.0 * directionX + GetRandomReal(-rand, rand)
168 set midy = oy + distance / 2.0 * directionY + GetRandomReal(-rand, rand)
169 set endx = ox + distance * directionX + GetRandomReal(-rand, rand)
170 set endy = oy + distance * directionY + GetRandomReal(-rand, rand)
171
172 call DestroyEffect(AddSpecialEffect(SPELL00_EFFECT_FILE_DAMAGE, ox, oy))
173 call DestroyEffect(AddSpecialEffect(SPELL00_EFFECT_FILE_DAMAGE, midx, midy))
174 call DestroyEffect(AddSpecialEffect(SPELL00_EFFECT_FILE_DAMAGE, ox + distance * directionX, oy + distance * directionY))
175
176 // TODO Create a custom special effect that does not rely on timely deletion,
177 // similar to how Carrion Swarm and Crushing Wave effects are.
178 set bolt0 = AddLightning("MFPB", true, ox, oy, midx, midy)
179 set bolt1 = AddLightning("MFPB", true, midx, midy, endx, endy)
180 call SetLightningColor(bolt0, 0.6, 0.3, 1, 1)
181 call SetLightningColor(bolt1, 0.6, 0.3, 1, 1)
182
183 // FIXME May give unexpected and unwanted results at runtime
184 call PolledWait(0.7)
185 call DestroyLightning(bolt0)
186 call DestroyLightning(bolt1)
187 endfunction
188
189 function spell00_trigger_filter takes nothing returns boolean
190 return SPELL00_ABILITY_ID == GetSpellAbilityId()
145 191 endfunction endfunction
146 192
147 function spell00_action takes nothing returns nothing
193 function spell00_trigger_action takes nothing returns nothing
148 194 local unit caster local unit caster
149 195 local unit target_unit local unit target_unit
150 196 local location target_point local location target_point
 
... ... function spell00_action takes nothing returns nothing
155 201 local location od local location od
156 202 local real directionX local real directionX
157 203 local real directionY local real directionY
204 local real damage_amount
205 local real healing_amount
206 local integer spell_id
207 local integer spell_level
158 208
159 call BJDebugMsg(":136 trigger")
160 209 set caster = GetSpellAbilityUnit() set caster = GetSpellAbilityUnit()
161 210 if null == caster then if null == caster then
162 211 return return
 
... ... function spell00_action takes nothing returns nothing
173 222 return return
174 223 endif endif
175 224
225 set spell_id = GetSpellAbilityId()
226 set spell_level = IMinBJ(IMaxBJ(0, GetUnitAbilityLevel(caster, spell_id)), 144)
176 227
177 228 set o = GetUnitLoc(caster) set o = GetUnitLoc(caster)
178 set directionX = GetLocationX(target_point) - GetLocationX(o)
179 set directionY = GetLocationY(target_point) - GetLocationY(o)
180 call spell00_cast(caster, o, directionX, directionY, 256.0, 1024.0, 144.0, 36.0)
229 set damage_amount = SPELL00_DAMAGE_AMOUNT_BASE + SPELL00_DAMAGE_AMOUNT_PER_SPELL_LEVEL * spell_level
230 set healing_amount = SPELL00_HEALING_AMOUNT_BASE + SPELL00_HEALING_AMOUNT_PER_SPELL_LEVEL * spell_level
231 call spell00_cast(caster, o, target_point, 72.0, 900.0, damage_amount, healing_amount)
181 232
182 233 call RemoveLocation(o) call RemoveLocation(o)
183 234 call RemoveLocation(target_point) call RemoveLocation(target_point)
184 call BJDebugMsg(":177 end")
185 235 endfunction endfunction
186 236
187 237 function spell00_init takes nothing returns nothing function spell00_init takes nothing returns nothing
 
... ... function spell00_init takes nothing returns nothing
191 241
192 242 set trg = CreateTrigger() set trg = CreateTrigger()
193 243 call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
194 set filter = Condition(function spell00_filter)
244 set filter = Condition(function spell00_trigger_filter)
195 245 call TriggerAddCondition(trg, filter) call TriggerAddCondition(trg, filter)
196 call TriggerAddAction(trg, function spell00_action)
246 call TriggerAddAction(trg, function spell00_trigger_action)
197 247 endfunction endfunction
198 // divert(0)
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