|
1 local MUTANT_VERSION = "v0.9.4" |
|
2 |
|
3 --[[ ___ ___ |
|
4 ( ) ( ) |
|
5 ___ .-. .-. ___ ___ | |_ .---. ___ .-. | |_ |
|
6 ( ) ' ( )( ( __) / .-, ( ) ( __) |
|
7 | .-. .-. | | | | | | (__) ; || .-. .| | |
|
8 | | | | | | | | | | | ___ .'` || | | || | ___ |
|
9 | | | | | | | | | | |( / .'| || | | || |( ) |
|
10 | | | | | | | | | | | | | / | || | | || | | | |
|
11 | | | | | | | ; ' | ' | ; | ; || | | || ' | | |
|
12 | | | | | ' `-' / ' `-' ' `-' || | | |' `-' ; |
|
13 (___)(___)(___'.__.' `.__.`.__.'_(___)(___)`.__. |
|
14 |
|
15 |
|
16 ---- IMPORTANT! |
|
17 ---- |
|
18 ---- You should save (press Ctrl+S) this script to: |
|
19 ---- Program Files\Hedgewars\share\hedgewars\Data\Scripts\Multiplayer\Mutant.lua |
|
20 ---- or (on Linux): |
|
21 ---- ~/.hedgewars/Data/Scripts/Multiplayer/Mutant.lua |
|
22 ---- |
|
23 ---- (or wherever scripts like Highlander.lua, Racer.lua are on your system) |
|
24 ---- |
|
25 ---- Also, if you didn't have Mutant script yet, you need to restart Hedgewars for it to find the script file. |
|
26 ---- |
|
27 |
|
28 |
|
29 ---- GAME RULES |
|
30 ---- |
|
31 ---- Recommended settings: |
|
32 ---- * one hedgehog per team |
|
33 ---- * 'Small' one-island map |
|
34 ---- |
|
35 ---- First one to kill anyone becomes Mutant. Mutant has super-weapons |
|
36 ---- and a lot of health, which however depletes if he doesn't frag fast. |
|
37 ---- Goal of Mutant is to use his weapons to hold his status for as long |
|
38 ---- as he can. |
|
39 ---- Goal of others is to hunt the Mutant down. The one who kills Mutant, |
|
40 ---- becomes Mutant himself. |
|
41 ---- The player with least points (or most deaths) is Bottom Feeder. He |
|
42 ---- can gain points by killing anyone. Other normal players only get points |
|
43 ---- for killing Mutant. |
|
44 ---- |
|
45 ---- Points: |
|
46 ---- +2 for becoming a Mutant |
|
47 ---- +1 to a Mutant for killing anyone |
|
48 ---- +1 to a Bottom Feeder for killing anyone |
|
49 ---- -1 to anyone for a suicide |
|
50 ---- other kills don't give you points. |
|
51 ---- |
|
52 |
|
53 --]] |
|
54 |
|
55 loadfile(GetDataPath() .. "Scripts/Locale.lua")() |
|
56 loadfile(GetDataPath() .. "Scripts/Tracker.lua")() |
|
57 |
|
58 --[[ |
|
59 MUTANT SCRIPT |
|
60 |
|
61 To Do: -Clean-up this fucking piece of code |
|
62 -Debug |
|
63 -Find a girlfriend |
|
64 -Fix Sheepluva's hat +[p] |
|
65 -Cookies |
|
66 |
|
67 -----------------------]] |
|
68 |
|
69 local hhs = {} |
|
70 local numhhs = 0 |
|
71 |
|
72 local gameOver=false |
|
73 |
|
74 local mutant = nil |
|
75 local mutant_base_health = 200 |
|
76 local mutant_base_disease = 25 |
|
77 local disease_timer = 2000 |
|
78 |
|
79 local kill_reward = nil |
|
80 local mt_hurt=false |
|
81 |
|
82 local killsCounter = 0 |
|
83 |
|
84 local team_fire_punishment = 3 |
|
85 local mutant_kill_reward = 2 |
|
86 |
|
87 local hh_weapons = { amBazooka, amGrenade, amShotgun, amMine} |
|
88 |
|
89 local mt_weapons = {amWatermelon, amHellishBomb, amBallgun, amRCPlane, amTeleport} |
|
90 |
|
91 local disease=0 |
|
92 local timer=0 |
|
93 |
|
94 local winScore = 15 |
|
95 local hogsLimit = 1 |
|
96 |
|
97 local teams = {} |
|
98 |
|
99 local circles = {} |
|
100 local circleFrame = -1 |
|
101 |
|
102 function onGameInit() |
|
103 TurnTime = 20000 |
|
104 WaterRise = 0 |
|
105 GameFlags = GameFlags + gfResetWeps + gfPerHogAmmo |
|
106 HealthCaseProb=0 |
|
107 HealthCaseAmount=0 |
|
108 MinesTime=1000 |
|
109 CaseFreq = 2 |
|
110 |
|
111 end |
|
112 |
|
113 |
|
114 function limitHogs(gear) |
|
115 cnthhs = cnthhs + 1 |
|
116 if cnthhs > 1 then |
|
117 hogLimitHit = true |
|
118 SetEffect(gear, heResurrectable, false) |
|
119 SetHealth(gear, 0) |
|
120 end |
|
121 end |
|
122 |
|
123 function onGameStart() |
|
124 trackTeams() |
|
125 teamScan() |
|
126 runOnHogs(saveStuff) |
|
127 --local str = "/say " .. MUTANT_VERSION |
|
128 --ParseCommand(str) |
|
129 |
|
130 hogLimitHit = false |
|
131 for i=0 , TeamsCount - 1 do |
|
132 cnthhs = 0 |
|
133 runOnHogsInTeam(limitHogs, teams[i]) |
|
134 end |
|
135 if hogLimitHit then |
|
136 AddCaption(loc("ONE HOG PER TEAM! KILLING EXCESS HEDGES")) |
|
137 end |
|
138 end |
|
139 |
|
140 |
|
141 |
|
142 function giveWeapons(gear) |
|
143 |
|
144 if gear == mutant then |
|
145 AddAmmo(gear, amRope) |
|
146 for i=1, #mt_weapons do |
|
147 AddAmmo(gear, mt_weapons[i]) |
|
148 end |
|
149 |
|
150 else |
|
151 for i=1, #hh_weapons do |
|
152 AddAmmo(gear,hh_weapons[i]) |
|
153 end |
|
154 end |
|
155 end |
|
156 |
|
157 function onAmmoStoreInit() |
|
158 |
|
159 SetAmmo(amSkip, 9, 0, 0, 0) |
|
160 SetAmmo(amRope,0,1,0,5) |
|
161 SetAmmo(amSnowball,0,1,0,1) |
|
162 |
|
163 for i=1, #hh_weapons do |
|
164 SetAmmo(hh_weapons[i], 0, 0, 0, 1) |
|
165 end |
|
166 |
|
167 for i=1, #mt_weapons do |
|
168 SetAmmo(mt_weapons[i], 0, 3, 0, 1) |
|
169 end |
|
170 |
|
171 end |
|
172 |
|
173 function drawCircles() |
|
174 for i = 0, #hhs do |
|
175 if circles[hhs[i]] ~= nil then |
|
176 DeleteVisualGear(circles[hhs[i]]) |
|
177 circles[hhs[i]] = nil |
|
178 end |
|
179 |
|
180 if hhs[i] ~= CurrentHedgehog then |
|
181 if mutant == nil then |
|
182 circles[hhs[i]] = AddVisualGear(0, 0, vgtCircle, 0, false) |
|
183 SetVisualGearValues(circles[hhs[i]], 0, 0, 0, 0, 0, 0, 0, 22, 5, 0xff000080) |
|
184 elseif CurrentHedgehog == mutant then |
|
185 circles[hhs[i]] = AddVisualGear(0, 0, vgtCircle, 0, false) |
|
186 SetVisualGearValues(circles[hhs[i]], 0, 0, 0, 0, 0, 0, 0, 22, 3, 0xaa000070) |
|
187 elseif getGearValue(CurrentHedgehog, "Feeder") and hhs[i] ~= mutant then |
|
188 circles[hhs[i]] = AddVisualGear(0, 0, vgtCircle, 0, false) |
|
189 SetVisualGearValues(circles[hhs[i]], 0, 0, 0, 0, 0, 0, 0, 22, 3, 0xaa000070) |
|
190 elseif hhs[i] == mutant then |
|
191 circles[hhs[i]] = AddVisualGear(0, 0, vgtCircle, 0, false) |
|
192 SetVisualGearValues(circles[hhs[i]], 0, 0, 0, 0, 0, 0, 0, 22, 5, 0xff000080) |
|
193 end |
|
194 end |
|
195 end |
|
196 circleFrame = 0 |
|
197 end |
|
198 |
|
199 function onNewTurn() |
|
200 |
|
201 trackTeams() |
|
202 killsCounter = 0 |
|
203 |
|
204 if mutant == nil then |
|
205 AddCaption( loc("FIRST BLOOD MUTATES") ) |
|
206 end |
|
207 |
|
208 checkScore() |
|
209 giveWeapons(CurrentHedgehog) |
|
210 drawCircles() |
|
211 kill_reward= numhhs*10 |
|
212 |
|
213 if CurrentHedgehog == mutant then |
|
214 mt_hurt=true |
|
215 disease= mutant_base_disease - numhhs |
|
216 else |
|
217 mt_hurt=false |
|
218 end |
|
219 |
|
220 setGearValue(CurrentHedgehog, "Alive", true) |
|
221 |
|
222 end |
|
223 |
|
224 function countBodies() |
|
225 if killsCounter == 2 then |
|
226 AddCaption(loc("DOUBLE KILL")) |
|
227 elseif killsCounter == 3 then |
|
228 AddCaption(loc("MEGA KILL")) |
|
229 PlaySound(sndRegret) |
|
230 elseif killsCounter == 4 then |
|
231 AddCaption(loc("ULTRA KILL")) |
|
232 elseif killsCounter == 5 then |
|
233 AddCaption(loc("MONSTER KILL")) |
|
234 PlaySound(sndIllGetYou) |
|
235 elseif killsCounter == 6 then |
|
236 AddCaption(loc("LUDICROUS KILL")) |
|
237 PlaySound(sndNutter) |
|
238 elseif killsCounter == 7 then |
|
239 AddCaption(loc("HOLY SHIT!!")) |
|
240 PlaySound(sndLaugh) |
|
241 elseif killsCounter >8 then |
|
242 AddCaption(loc("ZADROT")) |
|
243 end |
|
244 |
|
245 end |
|
246 |
|
247 function onGameTick() |
|
248 |
|
249 if circleFrame > -1 then |
|
250 for i = 0, #hhs do |
|
251 if circles[hhs[i]] ~= nil and hhs[i]~= nil then |
|
252 hhx, hhy = GetGearPosition(hhs[i]) |
|
253 X, Y, dX, dY, Angle, Frame, FrameTicks, State, Timer, Tint = GetVisualGearValues(circles[hhs[i]]) |
|
254 SetVisualGearValues(circles[hhs[i]], hhx + 1, hhy - 3, 0, 0, 0, 0, 0, 40 - (circleFrame % 25), Timer, Tint) |
|
255 end |
|
256 end |
|
257 |
|
258 circleFrame = circleFrame + 0.06 |
|
259 |
|
260 if circleFrame >= 25 then |
|
261 for i = 0, #hhs do |
|
262 if circles[hhs[i]] ~= nil then |
|
263 DeleteVisualGear(circles[hhs[i]]) |
|
264 circles[hhs[i]] = nil |
|
265 end |
|
266 end |
|
267 end |
|
268 end |
|
269 |
|
270 if TurnTimeLeft==0 and mt_hurt then |
|
271 mt_hurt = false |
|
272 end |
|
273 |
|
274 if mt_hurt and mutant~=nil then |
|
275 timer = timer + 1 |
|
276 if timer > disease_timer then |
|
277 timer = 0 |
|
278 SetHealth(mutant, GetHealth(mutant)-disease ) |
|
279 AddVisualGear(GetX(mutant), GetY(mutant)-5, vgtHealthTag, disease, true) |
|
280 if GetHealth(mutant)<=0 then |
|
281 SetHealth(mutant,0) |
|
282 mt_hurt= false |
|
283 setGearValue(mutant,"SelfDestruct",true) |
|
284 TurnTimeLeft = 0 |
|
285 end |
|
286 end |
|
287 end |
|
288 end |
|
289 |
|
290 function saveStuff(gear) |
|
291 setGearValue(gear,"Name",GetHogName(gear)) |
|
292 setGearValue(gear,"Hat",GetHogHat(gear)) |
|
293 end |
|
294 |
|
295 function armageddon(gear) |
|
296 SetState(gear, gstLoser) |
|
297 SetEffect(gear, heResurrectable, false) |
|
298 SetHealth(gear, 0) |
|
299 end |
|
300 |
|
301 function updateScore() |
|
302 local showScore = "" |
|
303 |
|
304 for i=0, TeamsCount-1 do |
|
305 if teams[i]~= nil then |
|
306 |
|
307 local curr_score = getTeamValue(teams[i], "Score") |
|
308 showScore = showScore .. teams[i] .. ": " .. curr_score .. " (deaths: " .. getTeamValue(teams[i], "DeadHogs") .. ") " .. "|" |
|
309 |
|
310 end |
|
311 end |
|
312 |
|
313 ShowMission(loc("Score"), |
|
314 "-------", |
|
315 showScore, 0, 200) |
|
316 |
|
317 HideMission() |
|
318 |
|
319 end |
|
320 |
|
321 function checkScore() |
|
322 local showScore = "" |
|
323 local lowest_score_team = nil |
|
324 local min_score=nil |
|
325 local winTeam = nil |
|
326 |
|
327 local only_low_score = true |
|
328 |
|
329 for i=0, TeamsCount-1 do |
|
330 if teams[i]~=nil then |
|
331 local curr_score = getTeamValue(teams[i], "Score") |
|
332 |
|
333 runOnHogsInTeam(removeFeeder, teams[i]) |
|
334 |
|
335 showScore = showScore .. teams[i] ..": " .. curr_score .. " (deaths: " .. getTeamValue(teams[i], "DeadHogs") .. ") " .. "|" |
|
336 |
|
337 if curr_score >= winScore then |
|
338 gameOver = true |
|
339 winTeam = teams[i] |
|
340 end |
|
341 |
|
342 if min_score==nil then |
|
343 min_score= curr_score |
|
344 lowest_score_team = teams[i] |
|
345 else |
|
346 if curr_score <= min_score then |
|
347 if curr_score == min_score then |
|
348 if getTeamValue(teams[i], "DeadHogs") == getTeamValue(lowest_score_team, "DeadHogs") then |
|
349 only_low_score = false |
|
350 else |
|
351 if getTeamValue(teams[i], "DeadHogs") > getTeamValue(lowest_score_team, "DeadHogs") then |
|
352 lowest_score_team = teams[i] |
|
353 end |
|
354 only_low_score = true |
|
355 end |
|
356 |
|
357 else |
|
358 min_score= curr_score |
|
359 lowest_score_team = teams[i] |
|
360 only_low_score = true |
|
361 end |
|
362 end |
|
363 end |
|
364 end |
|
365 end |
|
366 |
|
367 if gameOver then |
|
368 TurnTimeLeft = 0 |
|
369 for i=0, #teams do |
|
370 if teams[i]~=winTeam then |
|
371 runOnHogsInTeam(armageddon, teams[i]) |
|
372 end |
|
373 end |
|
374 |
|
375 ShowMission( loc("WINNER IS ") .. winTeam, |
|
376 "~~~~~~~~~~~~~~~~~~~~~~~~~", |
|
377 showScore, 0, 200) |
|
378 else |
|
379 |
|
380 if only_low_score then |
|
381 runOnHogsInTeam(setFeeder, lowest_score_team) |
|
382 end |
|
383 |
|
384 ShowMission( loc("Score"), |
|
385 loc("-------"), |
|
386 showScore, 0, 200) |
|
387 |
|
388 end |
|
389 end |
|
390 |
|
391 function backToNormal(gear) |
|
392 |
|
393 SetHogName(gear, getGearValue(gear,"Name")) |
|
394 SetHogHat(gear, 'NoHat') |
|
395 SetHogHat(gear, getGearValue(gear,"Hat")) |
|
396 setGearValue(mutant,"SelfDestruct",false) |
|
397 mt_hurt=false |
|
398 mutant=nil |
|
399 end |
|
400 |
|
401 function removeFeeder(gear) |
|
402 |
|
403 if gear~=nil then |
|
404 setGearValue(gear,"Feeder",false) |
|
405 if gear~= mutant then |
|
406 SetHogName(gear, getGearValue(gear,"Name") ) |
|
407 SetHogHat(gear, 'NoHat') |
|
408 SetHogHat(gear, getGearValue(gear,"Hat")) |
|
409 end |
|
410 end |
|
411 end |
|
412 |
|
413 function setFeeder(gear) |
|
414 |
|
415 if gear~= mutant and gear~= nil then |
|
416 SetHogName(gear,"BOTTOM FEEDER") |
|
417 SetHogHat(gear, 'Poke_Slowpoke') |
|
418 setGearValue(gear,"Feeder", true) |
|
419 end |
|
420 end |
|
421 |
|
422 function setMutantStuff(gear) |
|
423 mutant = gear |
|
424 |
|
425 SetHogName(gear,"MUTANT") |
|
426 SetHogHat(gear,'whysoserious') |
|
427 SetHealth(gear, ( mutant_base_health + numhhs*25) ) |
|
428 SetEffect(gear, hePoisoned, 1) |
|
429 setGearValue(mutant,"SelfDestruct",false) |
|
430 setGearValue(gear, "Feeder", false) |
|
431 |
|
432 AddCaption(getGearValue(gear, "Name") .. loc(" HAS MUTATED" )) |
|
433 |
|
434 TurnTimeLeft=0 |
|
435 |
|
436 AddVisualGear(GetX(gear), GetY(gear), vgtSmokeRing, 0, false) |
|
437 AddVisualGear(GetX(gear), GetY(gear), vgtSmokeRing, 0, false) |
|
438 AddVisualGear(GetX(gear), GetY(gear), vgtSmokeRing, 0, false) |
|
439 AddVisualGear(GetX(gear), GetY(gear), vgtSmokeRing, 0, false) |
|
440 AddVisualGear(GetX(gear), GetY(gear), vgtSmokeRing, 0, false) |
|
441 PlaySound(sndSuddenDeath) |
|
442 end |
|
443 |
|
444 function teamScan() |
|
445 |
|
446 for i=0, TeamsCount-1 do --nil filling |
|
447 teams[i]=nil |
|
448 end |
|
449 |
|
450 for i=0, #hhs do |
|
451 for j=0, TeamsCount-1 do |
|
452 if teams[j] ==nil and hhs[i]~=nil then |
|
453 teams[j] = GetHogTeamName(hhs[i]) |
|
454 setTeamValue(teams[j],"Score",0) |
|
455 setTeamValue(teams[j], "DeadHogs",0) |
|
456 break |
|
457 end |
|
458 |
|
459 if teams[j] == GetHogTeamName(hhs[i]) then |
|
460 break |
|
461 end |
|
462 end |
|
463 end |
|
464 |
|
465 ---***--- |
|
466 end |
|
467 |
|
468 |
|
469 function onGearDamage(gear, dmg) |
|
470 |
|
471 end |
|
472 |
|
473 function set_Mutant_and_Score(gear) |
|
474 |
|
475 local curr_team = GetHogTeamName(CurrentHedgehog) |
|
476 |
|
477 if gear == CurrentHedgehog then |
|
478 if CurrentHedgehog == mutant then |
|
479 PlaySound(sndHomerun) |
|
480 if getGearValue(gear, "SelfDestruct")==false then |
|
481 decreaseTeamValue(curr_team,"Score") |
|
482 end |
|
483 backToNormal(gear) |
|
484 else |
|
485 decreaseTeamValue(curr_team,"Score") |
|
486 end |
|
487 |
|
488 else |
|
489 if gear == mutant then |
|
490 backToNormal(mutant) |
|
491 if curr_team ~=GetHogTeamName(gear) then |
|
492 if getGearValue(CurrentHedgehog, "Alive") then |
|
493 setMutantStuff(CurrentHedgehog) |
|
494 setTeamValue(curr_team,"Score",(getTeamValue(curr_team,"Score") + mutant_kill_reward)) |
|
495 end |
|
496 else |
|
497 setTeamValue(curr_team,"Score",(getTeamValue(curr_team,"Score") - team_fire_punishment)) |
|
498 end |
|
499 else |
|
500 if mutant==nil then |
|
501 if curr_team ~=GetHogTeamName(gear) then |
|
502 if getGearValue(CurrentHedgehog, "Alive") then |
|
503 setMutantStuff(CurrentHedgehog) |
|
504 setTeamValue(curr_team,"Score",(getTeamValue(curr_team,"Score") + mutant_kill_reward)) |
|
505 else |
|
506 increaseTeamValue(curr_team,"Score") |
|
507 end |
|
508 else |
|
509 setTeamValue(curr_team,"Score",(getTeamValue(curr_team,"Score") - team_fire_punishment)) |
|
510 end |
|
511 else |
|
512 if curr_team ~=GetHogTeamName(gear) then |
|
513 if CurrentHedgehog==mutant and getGearValue(mutant,"SelfDestruct")==false then |
|
514 SetHealth(CurrentHedgehog, GetHealth(CurrentHedgehog)+kill_reward) |
|
515 AddCaption("+" .. kill_reward .. loc(" HP") ) |
|
516 increaseTeamValue(curr_team,"Score") |
|
517 end |
|
518 if getGearValue(CurrentHedgehog,"Feeder") then |
|
519 increaseTeamValue(curr_team,"Score") |
|
520 end |
|
521 else |
|
522 setTeamValue(curr_team,"Score",(getTeamValue(curr_team,"Score") - team_fire_punishment)) |
|
523 end |
|
524 end |
|
525 end |
|
526 end |
|
527 end |
|
528 |
|
529 function onGearResurrect(gear) |
|
530 if not gameOver then |
|
531 if GetGearType(gear) == gtHedgehog then |
|
532 |
|
533 increaseTeamValue(GetHogTeamName(gear), "DeadHogs") |
|
534 |
|
535 if gear==CurrentHedgehog then |
|
536 setGearValue(CurrentHedgehog, "Alive", false) |
|
537 end |
|
538 set_Mutant_and_Score(gear) |
|
539 if gear~=CurrentHedgehog then |
|
540 killsCounter = killsCounter + 1 |
|
541 countBodies() |
|
542 end |
|
543 AddVisualGear(GetX(gear), GetY(gear), vgtSmokeRing, 0, false) |
|
544 PlaySound(sndWhack) |
|
545 updateScore() |
|
546 end |
|
547 end |
|
548 end |
|
549 |
|
550 function onGearAdd(gear) |
|
551 |
|
552 -- Catch hedgehogs for the tracker |
|
553 if GetGearType(gear) == gtHedgehog then |
|
554 trackGear(gear) |
|
555 hhs[numhhs] = gear |
|
556 numhhs = numhhs + 1 |
|
557 SetEffect(gear, heResurrectable, 1) |
|
558 end |
|
559 end |
|
560 |
|
561 function checkEmptyTeam (teamName) |
|
562 for i=0 , #hhs do |
|
563 if hhs[i]~=nil then |
|
564 if teamName == GetHogTeamName(hhs[i]) then |
|
565 return false |
|
566 end |
|
567 end |
|
568 end |
|
569 return true |
|
570 end |
|
571 |
|
572 function onGearDelete(gear) |
|
573 -- Remove hogs that are gone |
|
574 if GetGearType(gear) == gtHedgehog then |
|
575 numhhs = numhhs - 1 |
|
576 |
|
577 local found |
|
578 for i=0, #hhs do |
|
579 if hhs[i] == gear then |
|
580 found = i |
|
581 break |
|
582 end |
|
583 end |
|
584 for i = found, #hhs - 1 do |
|
585 hhs[i] = hhs[i + 1] |
|
586 end |
|
587 hhs[#hhs] = nil |
|
588 |
|
589 local t_name = GetHogTeamName(gear) |
|
590 if checkEmptyTeam(t_name) then |
|
591 for i = 0, TeamsCount - 1 do |
|
592 if teams[i] == t_name then |
|
593 found = i |
|
594 break |
|
595 end |
|
596 end |
|
597 for i = found, TeamsCount - 2 do |
|
598 teams[i] = teams[i + 1] |
|
599 end |
|
600 teams[TeamsCount - 1] = nil |
|
601 TeamsCount = TeamsCount - 1 |
|
602 end |
|
603 AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false) |
|
604 trackDeletion(gear) |
|
605 end |
|
606 end |
|
607 |
|
608 --[[ |
|
609 S T A R R I N G |
|
610 |
|
611 prof - Coding, implementing and evangelism |
|
612 vos - Initial idea and script improvements |
|
613 --]] |