-
Notifications
You must be signed in to change notification settings - Fork 4
/
pmp.lua
1412 lines (1171 loc) · 48.8 KB
/
pmp.lua
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
print ('[PMP] pmp.lua' )
PMPVERSION = "0.50"
DISABLE_FOG_OF_WAR_ENTIRELY = false
CAMERA_DISTANCE_OVERRIDE = 1600
GOLD_PER_TICK = 5
LUMBER_PER_TICK = 5
GOLD_TICK_TIME = 5
LUMBER_TICK_TIME = 5
UNSEEN_FOG_ENABLED = false
DEBUG_SPEW = 1
UNIT_FORMATION_DISTANCE = 120
INITIAL_PEONS = 8
INITIAL_GOLD = 50
INITIAL_LUMBER = 50
INITIAL_FOOD_LIMIT = 10
XP_PER_PEON = 20
TEAM_COLORS = {}
TEAM_COLORS[DOTA_TEAM_GOODGUYS] = { 52, 85, 255 } -- Blue
TEAM_COLORS[DOTA_TEAM_BADGUYS] = { 255, 52, 85 } -- Red
TEAM_COLORS[DOTA_TEAM_CUSTOM_1] = { 61, 210, 150 } -- Teal
TEAM_COLORS[DOTA_TEAM_CUSTOM_2] = { 140, 42, 244 } -- Purple
TEAM_COLORS[DOTA_TEAM_CUSTOM_3] = { 243, 201, 9 } -- Yellow
TEAM_COLORS[DOTA_TEAM_CUSTOM_4] = { 255, 108, 0 } -- Orange
TEAM_COLORS[DOTA_TEAM_CUSTOM_5] = { 101, 212, 19 } -- Green
TEAM_COLORS[DOTA_TEAM_CUSTOM_6] = { 197, 77, 168 } -- Pink
TEAM_COLORS[DOTA_TEAM_CUSTOM_7] = { 129, 83, 54 } -- Brown
TEAM_COLORS[DOTA_TEAM_CUSTOM_8] = { 199, 228, 13 } -- Olive
-- For announcer
TEAM_NUMER_TO_COLOR = {
[DOTA_TEAM_GOODGUYS] = "blue",
[DOTA_TEAM_BADGUYS] = "red",
[DOTA_TEAM_CUSTOM_1] = "teal",
[DOTA_TEAM_CUSTOM_2] = "purple",
[DOTA_TEAM_CUSTOM_3] = "yellow",
[DOTA_TEAM_CUSTOM_4] = "orange",
[DOTA_TEAM_CUSTOM_5] = "green",
[DOTA_TEAM_CUSTOM_6] = "pink",
[DOTA_TEAM_CUSTOM_7] = "brown",
[DOTA_TEAM_CUSTOM_8] = "olive"
}
PLAYER_COLORS = {}
PLAYER_COLORS[0] = { 52, 85, 255 } -- Blue
PLAYER_COLORS[1] = { 255, 52, 85 } -- Red
PLAYER_COLORS[2] = { 61, 210, 150 } -- Teal
PLAYER_COLORS[3] = { 140, 42, 244 } -- Purple
PLAYER_COLORS[4] = { 243, 201, 9 } -- Yellow
PLAYER_COLORS[5] = { 255, 108, 0 } -- Orange
PLAYER_COLORS[6] = { 101, 212, 19 } -- Green
PLAYER_COLORS[7] = { 197, 77, 168 } -- Pink
PLAYER_COLORS[8] = { 129, 83, 54 } -- Brown
PLAYER_COLORS[9] = { 199, 228, 13 } -- Olive
PLAYER_COLORS[10] = { 105, 105, 255 } -- Light Blue
PLAYER_COLORS[11] = { 128, 128, 128 } -- Gray
XP_PER_LEVEL_TABLE = {
0, 200, 500, 900, 1400, 2000, 2700, 3500, 4400, 5400,
6000, 6600, 7200, 7800, 8400, 9000, 9600, 10200, 10800, 11400,
12000, 12600, 13200, 13800, 14400, 15000, 15600, 16200, 16800,
17400, 18000, 18600, 19200, 19800, 20400, 21000, 21600, 22200,
22800, 23400, 24000, 24600, 25200, 25800, 26400, 27000, 27600,
28200, 28800, 29400, 30000, 30600, 31200, 31800, 32400, 33000,
33600, 34200, 34800, 35400, 36000, 36600, 37200, 37800, 38400,
39000, 39600, 40200, 40800, 41400, 42000, 42600, 43200, 43800
}
RACES = {"npc_dota_hero_axe","npc_dota_hero_undying","npc_dota_hero_skeleton_king","npc_dota_hero_meepo","npc_dota_hero_dragon_knight",
"npc_dota_hero_silencer","npc_dota_hero_treant", "npc_dota_hero_drow_ranger","npc_dota_hero_warlock"}
PMP_ENABLED_HERO_COUNT = 9
--------------
-- This function initializes the game mode and is called before anyone loads into the game
-- It can be used to pre-initialize any values/tables that will be needed later
function PMP:InitGameMode()
print('[PMP] Starting to load gamemode...')
-- Setup rules
GameRules:SetHeroRespawnEnabled( false )
GameRules:SetUseUniversalShopMode( false )
GameRules:SetSameHeroSelectionEnabled( true )
GameRules:SetHeroSelectionTime( 30 )
GameRules:SetPreGameTime( 0 )
GameRules:SetPostGameTime( 100 )
GameRules:SetTreeRegrowTime( 10000.0 )
GameRules:SetUseCustomHeroXPValues ( true )
GameRules:SetUseBaseGoldBountyOnHeroes( false ) -- Need to check legacy values
GameRules:SetHeroMinimapIconScale( 1 )
GameRules:SetCreepMinimapIconScale( 1 )
GameRules:SetRuneMinimapIconScale( 1 )
GameRules:SetFirstBloodActive( false )
GameRules:SetHideKillMessageHeaders( true )
GameRules:SetGoldPerTick(GOLD_PER_TICK)
GameRules:SetGoldTickTime(GOLD_TICK_TIME)
GameRules:SetCustomVictoryMessageDuration( 600 )
-- Set game mode rules
GameMode = GameRules:GetGameModeEntity()
GameMode:SetRecommendedItemsDisabled( true )
GameMode:SetBuybackEnabled( false )
GameMode:SetTopBarTeamValuesOverride( true )
GameMode:SetTopBarTeamValuesVisible( false )
GameMode:SetUseCustomHeroLevels ( true )
GameMode:SetUnseenFogOfWarEnabled( UNSEEN_FOG_ENABLED )
GameMode:SetTowerBackdoorProtectionEnabled( false )
GameMode:SetGoldSoundDisabled( false )
GameMode:SetRemoveIllusionsOnDeath( true )
GameMode:SetAnnouncerDisabled( true )
GameMode:SetLoseGoldOnDeath( false )
GameMode:SetCameraDistanceOverride( CAMERA_DISTANCE_OVERRIDE )
GameMode:SetFogOfWarDisabled( DISABLE_FOG_OF_WAR_ENTIRELY )
GameMode:SetCustomXPRequiredToReachNextLevel( XP_PER_LEVEL_TABLE )
GameMode:SetCustomHeroMaxLevel ( 80 )
-- Team Colors
for team,color in pairs(TEAM_COLORS) do
SetTeamCustomHealthbarColor(team, color[1], color[2], color[3])
end
-- DebugPrint
--Convars:RegisterConvar('debug_spew', tostring(DEBUG_SPEW), 'Set to 1 to start spewing debug info. Set to 0 to disable.', 0)
if GetMapName() == "free_for_all" then
-- 9 teams of 1 player
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_GOODGUYS, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_BADGUYS, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_1, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_2, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_3, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_4, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_5, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_6, 1 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_7, 1 )
VALID_TEAMS = {DOTA_TEAM_GOODGUYS,
DOTA_TEAM_BADGUYS,DOTA_TEAM_CUSTOM_1,
DOTA_TEAM_CUSTOM_2,DOTA_TEAM_CUSTOM_3,
DOTA_TEAM_CUSTOM_4,DOTA_TEAM_CUSTOM_5,
DOTA_TEAM_CUSTOM_6,DOTA_TEAM_CUSTOM_7,}
PMP_MAX_PLAYERS = 9
GameRules.PlayersPerTeam = 1
GameRules.Positions = false
else
-- Default to 3v3v3v3
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_GOODGUYS, 3 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_BADGUYS, 3 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_2, 3 )
GameRules:SetCustomGameTeamMaxPlayers( DOTA_TEAM_CUSTOM_3, 3 )
VALID_TEAMS = {DOTA_TEAM_GOODGUYS,DOTA_TEAM_BADGUYS,DOTA_TEAM_CUSTOM_1,DOTA_TEAM_CUSTOM_2,}
PMP_MAX_PLAYERS = 12
GameRules:EnableCustomGameSetupAutoLaunch(false)
GameRules.PlayersPerTeam = 3
GameRules.Positions = true
statCollection:setFlags({fixed_positions = GameRules.Positions})
end
FFA_MAP = GetMapName() == "free_for_all"
GameRules.BossRoam = false
GameRules.BotDifficulty = "normal"
GameRules.BotNames = {"Noya","Baumi","Icefrog","Dendi","Fear","Bulldong","Arteezy","Pyrion Flax","ODPixel","KotLGuy","Zyori","Loda","Puppey"}
statCollection:setFlags({team_setting = GameRules.PlayersPerTeam})
statCollection:setFlags({BossRoam = GameRules.BossRoam})
-- Event Hooks
ListenToGameEvent('entity_killed', Dynamic_Wrap(PMP, 'OnEntityKilled'), self)
ListenToGameEvent('npc_spawned', Dynamic_Wrap(PMP, 'OnNPCSpawned'), self)
ListenToGameEvent('dota_player_pick_hero', Dynamic_Wrap(PMP, 'OnPlayerPickHero'), self)
ListenToGameEvent('game_rules_state_change', Dynamic_Wrap(PMP, 'OnGameRulesStateChange'), self)
ListenToGameEvent('player_connect_full', Dynamic_Wrap(PMP, 'OnConnectFull'), self)
ListenToGameEvent('player_connect', Dynamic_Wrap(PMP, 'PlayerConnect'), self)
ListenToGameEvent('player_disconnect', Dynamic_Wrap(PMP, 'OnDisconnect'), self)
ListenToGameEvent('player_chat', Dynamic_Wrap(PMP, 'OnPlayerChat'), self)
ListenToGameEvent('dota_player_gained_level', Dynamic_Wrap(PMP, 'OnPlayerLevelUp'), self)
ListenToGameEvent('entity_hurt', Dynamic_Wrap(PMP, 'OnEntityHurt'), self)
-- Filters
GameMode:SetExecuteOrderFilter( Dynamic_Wrap( PMP, "FilterExecuteOrder" ), self )
GameMode:SetDamageFilter( Dynamic_Wrap( PMP, "FilterDamage" ), self )
-- Register Listener
CustomGameEventManager:RegisterListener( "reposition_player_camera", Dynamic_Wrap(PMP, "RepositionPlayerCamera"))
CustomGameEventManager:RegisterListener( "update_selected_entities", Dynamic_Wrap(PMP, 'OnPlayerSelectedEntities'))
CustomGameEventManager:RegisterListener( "building_rally_order", Dynamic_Wrap(PMP, "OnBuildingRallyOrder")) --Right click through panorama
CustomGameEventManager:RegisterListener( "trade_order", Dynamic_Wrap(PMP, "OnTradeOrder")) --Trader
CustomGameEventManager:RegisterListener( "set_setting", Dynamic_Wrap(PMP, "SetSetting")) --Team Options
-- Lua Modifiers
LinkLuaModifier("modifier_movespeed_cap", "libraries/modifiers/modifier_movespeed_cap", LUA_MODIFIER_MOTION_NONE)
LinkLuaModifier("modifier_hex", "libraries/modifiers/modifier_hex", LUA_MODIFIER_MOTION_NONE)
-- Allow cosmetic swapping
SendToServerConsole( "dota_combine_models 0" )
-- Don't end the game if everyone is unassigned
SendToServerConsole("dota_surrender_on_disconnect 0")
-- Increase time to load
SendToServerConsole("dota_wait_for_players_to_load_timeout 240")
-- Change random seed
local timeTxt = string.gsub(string.gsub(GetSystemTime(), ':', ''), '0','')
math.randomseed(tonumber(timeTxt))
self.vUserIds = {}
playerIDs = {}
-- Win Condition
GameRules.StillInGame = {}
-- Music play
GameRules.PlayingMusic = {}
-- KV Files
GameRules.AbilityKV = LoadKeyValues("scripts/npc/npc_abilities_custom.txt")
GameRules.UnitKV = LoadKeyValues("scripts/npc/npc_units_custom.txt")
GameRules.HeroKV = LoadKeyValues("scripts/npc/npc_heroes_custom.txt")
GameRules.ItemKV = LoadKeyValues("scripts/npc/npc_items_custom.txt")
GameRules.APPLIER = CreateItem("item_apply_modifiers", nil, nil)
GameRules.UPGRADER = CreateItem("item_upgrade_modifiers", nil, nil)
GameRules.ADDON_ENGLISH = LoadKeyValues("resource/addon_english.txt")
-- Lumber AbilityValue, credits to zed https://github.com/zedor/AbilityValues
Convars:RegisterCommand( "ability_values_entity", function(name, entityIndex)
local cmdPlayer = Convars:GetCommandClient()
local pID = cmdPlayer:GetPlayerID()
if cmdPlayer then
local unit = EntIndexToHScript(tonumber(entityIndex))
if not IsValidEntity(unit) then
return
end
if unit then
local abilityValues = {}
local itemValues = {}
-- Iterate over the abilities
for i=0,15 do
local ability = unit:GetAbilityByIndex(i)
-- If there's an ability in this slot and its not hidden, define the number to show
if ability and not ability:IsHidden() then
local lumberCost = ability:GetLevelSpecialValueFor("lumber_cost", ability:GetLevel() - 1)
if lumberCost then
table.insert(abilityValues,lumberCost)
else
table.insert(abilityValues,0)
end
end
end
FireGameEvent( 'ability_values_send', { player_ID = pID,
hue_1 = -10, val_1 = abilityValues[1],
hue_2 = -10, val_2 = abilityValues[2],
hue_3 = -10, val_3 = abilityValues[3],
hue_4 = -10, val_4 = abilityValues[4],
hue_5 = -10, val_5 = abilityValues[5],
hue_6 = -10, val_6 = abilityValues[6] } )
-- Iterate over the items
for i=0,5 do
local item = unit:GetItemInSlot(i)
-- If there's an item in this slot, define the number to show
if item then
local cost = item:GetLevelSpecialValueFor("gold_cost", item:GetLevel() - 1)
if cost then
table.insert(itemValues,cost)
else
table.insert(itemValues,0)
end
else
table.insert(itemValues,0)
end
end
FireGameEvent( 'ability_values_send_items', { player_ID = pID,
hue_1 = -60, bri_1 = -50, val_1 = itemValues[1],
hue_2 = -60, bri_2 = -50, val_2 = itemValues[2],
hue_3 = -60, bri_3 = -50, val_3 = itemValues[3],
hue_4 = -60, bri_4 = -50, val_4 = itemValues[4],
hue_5 = -60, bri_5 = -50, val_5 = itemValues[5],
hue_6 = -60, bri_6 = -50, val_6 = itemValues[6] } )
else
-- Hide all the values if the unit is not supposed to show any.
FireGameEvent( 'ability_values_send', { player_ID = pID, val_1 = 0, val_2 = 0, val_3 = 0, val_4 = 0, val_5 = 0, val_6 = 0 } )
FireGameEvent( 'ability_values_send_items', { player_ID = pID, val_1 = 0, val_2 = 0, val_3 = 0, val_4 = 0, val_5 = 0, val_6 = 0 } )
end
end
end, "Change AbilityValues", 0 )
-- Store and update selected units of each pID
GameRules.SELECTED_UNITS = {}
-- Keeps the blighted gridnav positions
GameRules.Blight = {}
-- Starting positions
GameRules.StartingPositions = {}
GameRules.OrderedPositionEntities = Entities:FindAllByName( "*starting_position*" ) --Inside player_start.vmap prefab
GameRules.triggerNorth = Entities:FindByName( nil, "*triggerNorth*" )
GameRules.triggerSouth = Entities:FindByName( nil, "*triggerSouth*" )
GameRules.triggerEast = Entities:FindByName( nil, "*triggerEast*" )
GameRules.triggerWest = Entities:FindByName( nil, "*triggerWest*" )
GameRules.ShuffledPositionEntities = ShuffledList(GameRules.OrderedPositionEntities)
--[[print("Ordered:")
for k,v in pairs(GameRules.OrderedPositionEntities) do
print(k,v:GetName())
end
print("Shuffled:")
for k,v in pairs(GameRules.ShuffledPositionEntities) do
print(k,v:GetName())
end]]
for k,v in pairs(GameRules.ShuffledPositionEntities) do
local pos_table = {}
pos_table.position = v:GetAbsOrigin()
pos_table.playerID = -1 --Unassigned position
GameRules.StartingPositions[k-1] = pos_table
end
-- Less expensive pathing?
LimitPathingSearchDepth(0.5)
-- Version Label
CustomNetTables:SetTableValue("gameinfo", "version", {value=PMPVERSION})
print('[PMP] Done loading gamemode!')
end
-- This function is called 1 to 2 times as the player connects initially but before they
-- have completely connected
function PMP:PlayerConnect(keys)
--print('[PMP] PlayerConnect')
--DeepPrintTable(keys)
end
-- This function is called once when the player fully connects and becomes "Ready" during Loading
function PMP:OnConnectFull(keys)
print ('[PMP] OnConnectFull')
--DeepPrintTable(keys)
local entIndex = keys.index+1
-- The Player entity of the joining user
local ply = EntIndexToHScript(entIndex)
Timers:CreateTimer(0.03, function() -- To prevent it from being -1 when the player is created
if not ply then return end -- Something went wrong
local playerID = ply:GetPlayerID()
if playerID and playerID ~= -1 then
if not tableContains(playerIDs, playerID) then
table.insert(playerIDs, playerID)
else
PMP:OnReconnect(playerID)
end
-- Update the user ID table with this user
self.vUserIds[keys.userid] = ply
end
end)
end
function PMP:OnReconnect(playerID)
local hero = PlayerResource:GetSelectedHeroEntity(playerID)
if hero then
Setup_Hero_Panel(hero)
end
end
function PMP:PostLoadPrecache()
print("[PMP] Performing Post-Load precache")
end
function PMP:OnFirstPlayerLoaded()
print("[PMP] First Player has loaded")
end
function PMP:OnAllPlayersLoaded()
print("[PMP] All Players have loaded into the game")
if GameRules.Positions then
PMP:SetPlayersStartingPositions()
end
end
-- A player picked a hero
function PMP:OnPlayerPickHero(keys)
local heroName = keys.hero
local hero = EntIndexToHScript(keys.heroindex)
local player = EntIndexToHScript(keys.player)
local playerID = player:GetPlayerID()
local teamNumber = hero:GetTeamNumber()
--print('[PMP] OnPlayerPickHero', playerID, heroName, FORCED_RANDOM[playerID])
-- Creating heroes during showcase causes the hero to be created again on game in progress, so skip the double creation
if FORCED_RANDOM[playerID] then
UTIL_Remove(hero)
FORCED_RANDOM[playerID] = nil
return
end
-- Setup bots team in FFA
if PlayerResource:IsFakeClient(playerID) and PlayerResource:GetPlayerCountForTeam(teamNumber) > 1 then
for k,teamID in pairs(VALID_TEAMS) do
local teamPlayerCount = PlayerResource:GetPlayerCountForTeam(teamID)
if teamPlayerCount == 0 then
print("FakePlayer "..playerID.." reassigned to team "..teamID)
PlayerResource:SetCustomTeamAssignment(playerID, teamID)
teamNumber = teamID
break
end
end
hero:SetTeam(teamNumber)
hero:SetPlayerID(playerID)
hero:SetOwner(PlayerResource:GetPlayer(playerID))
end
local race = GameRules.HeroKV[heroName]["Race"]
local playerName = GetPlayerName(playerID)
-- Color
local color = PMP:ColorForTeam( teamNumber )
PlayerResource:SetCustomPlayerColor( playerID, color[1], color[2], color[3] )
-- Main building
if not GameRules.StartingPositions[playerID] then
print("No slot for player "..playerID.."!")
return
end
-- Add to playing list
table.insert(GameRules.StillInGame, hero)
local center_position = GameRules.StartingPositions[playerID].position
hero.garage = CreateUnitByName(race.."_garage", center_position, false, hero, hero, teamNumber)
hero.garage:SetOwner(hero)
hero.garage:SetControllableByPlayer(playerID, true)
hero.garage.rally_point = center_position
Timers:CreateTimer(0.1, function()
ApplyModifier(hero.garage, "modifier_show_health_bar")
hero.garage:SetCustomHealthLabel( playerName, color[1], color[2], color[3]) -- Add a label on the base
end)
-- Undead effects
if race == "undead" then
CreateBlight(center_position)
end
Timers:CreateTimer(1/30, function()
NewSelection(hero.garage)
PlayerResource:SetCameraTarget(playerID, hero.garage)
Timers:CreateTimer(2/30, function() PlayerResource:SetCameraTarget(playerID, nil) end)
end)
-- Apply 4 layers of invulnerability from towers
ApplyModifier(hero.garage, "modifier_invulnerability_layer")
hero.garage:SetModifierStackCount("modifier_invulnerability_layer", GameRules.APPLIER, 4)
hero.invulnCount = 4
-- The main hero is an invulnerable fake just used to get global upgrades
center_position.z = -128
Timers:CreateTimer(1, function()
if hero:IsAlive() and (hero:GetAbsOrigin() - center_position):Length2D() > 200 then
hero:SetAbsOrigin(center_position)
hero:AddNoDraw()
end
return 1 --repeat until its set
end)
-- Upgrade Shop
local shopEnt = Entities:FindByNameWithin(nil, "*shop_position", center_position, 1000)
hero.pimpery = CreateUnitByName(race.."_pimpery", shopEnt:GetAbsOrigin(), false, hero, hero, teamNumber)
hero.pimpery:SetOwner(hero)
hero.pimpery:SetControllableByPlayer(playerID, true)
hero.pimpery:SetAngles(0, -90, 0)
hero.pimpery:AddNewModifier(unit, nil, "modifier_invulnerable", {})
PMP:GiveItemUpgrades(hero.pimpery, race)
-- Towers - 1 on each corner
hero.towers = {}
local towerEnts = Entities:FindAllByNameWithin("*tower_position", center_position, 1200)
for k,tower in pairs(towerEnts) do
local tower = CreateUnitByName(race.."_tower", tower:GetAbsOrigin(), false, hero, hero, teamNumber)
tower:SetOwner(hero)
tower:SetControllableByPlayer(playerID, true)
table.insert(hero.towers, tower)
if race == "demon" then
tower:StartGesture(ACT_DOTA_IDLE)
end
end
-- Barricades - 3 on each side
--print("[PMP] Setting Up Barricades")
hero.barricade_positions = {}
hero.barricades = {}
-- Tracking
hero.lumber = 0
hero.food_used = 0
hero.food_limit = 0
hero.spawn_rate = 2
hero.super_peons_used = 0
hero.barricades_used = 0
hero.repairs_used = 0
hero.gold_earned = 0
hero.lumber_earned = 0
hero.outposts = {}
hero.Upgrades = {}
hero.Upgrades["weapon"] = 0
hero.Upgrades["helm"] = 0
hero.Upgrades["shield"] = 0
hero.Upgrades["wings"] = 0
hero.Upgrades["bow"] = 0
hero.Upgrades["quiver"] = 0
hero.Upgrades["health"] = 0
hero.Upgrades["critical_strike"] = 0
hero.Upgrades["stun_hit"] = 0
hero.Upgrades["poisoned_weapons"] = 0
hero.Upgrades["dodge"] = 0
hero.Upgrades["spiked_armor"] = 0
hero.Upgrades["racial"] = 1
hero.Upgrades["pimp_damage"] = 0
hero.Upgrades["pimp_armor"] = 0
hero.Upgrades["pimp_speed"] = 0
hero.Upgrades["pimp_regen"] = 0
--PMP:PrintUpgrades(playerID)
-- Set Resources
Timers:CreateTimer(function()
if not PlayerResource:GetSelectedHeroEntity(playerID) then
return 0.5
end
local gold = INITIAL_GOLD
local lumber = INITIAL_LUMBER
if PlayerResource:HasRandomed(playerID) then
gold = gold + 10
lumber = lumber + 10
end
SetGold(playerID, gold)
SetLumber(playerID, lumber)
SetFoodUsed(playerID, 0)
SetFoodLimit(playerID, INITIAL_FOOD_LIMIT)
-- Update the UI in case it hasn't been built yet
Timers:CreateTimer(1, function()
SetGold(playerID, GetGold(playerID))
SetLumber(playerID, GetLumber(playerID))
SetFoodUsed(playerID, GetFoodUsed(playerID))
SetFoodLimit(playerID, GetFoodLimit(playerID))
end)
if Convars:GetBool('developer') then
local steamID = PlayerResource:GetSteamAccountID(playerID)
--[[if steamID == 86718505 then
SetGold(playerID, 50000)
SetLumber(playerID, 50000)
--SetFoodLimit(playerID, 100)
end]]
end
-- Set initial units
hero.units = {}
for i=1,INITIAL_PEONS do
local unit = CreateUnitByName(race, center_position, true, hero, hero, teamNumber)
unit:SetOwner(hero)
unit:SetIdleAcquire(true)
unit:SetControllableByPlayer(playerID, true)
FindClearSpaceForUnit(unit, center_position, true)
ModifyFoodUsed(playerID, 1)
table.insert(hero.units, unit)
unit.pmp = true
end
end)
end
ITEM_UPGRADE_LIST =
{
"item_upgrade_critical_strike1",
"item_upgrade_stun_hit1",
"item_upgrade_poisoned_weapons1",
"item_upgrade_dodge1",
"item_upgrade_spiked_armor1"
}
function PMP:GiveItemUpgrades( unit , race)
for i=1,5 do
unit:AddItem(CreateItem(ITEM_UPGRADE_LIST[i], unit, unit))
end
unit:AddItem(CreateItem("item_upgrade_"..race.."_racial1", unit, unit))
end
-- An NPC has spawned somewhere in game. This includes heroes
function PMP:OnNPCSpawned(keys)
--print("[PMP] NPC Spawned")
--DeepPrintTable(keys)
local npc = EntIndexToHScript(keys.entindex)
if npc:IsRealHero() and npc.bFirstSpawned == nil then
npc.bFirstSpawned = true
PMP:OnHeroInGame(npc)
end
if GameRules['Positions'] and Convars:GetBool('developer') then
PMP:SetPlayersStartingPositions()
end
--ApplyModifier(npc, "modifier_attackable")
-- Ignore default gold bounty
npc:SetMaximumGoldBounty(0)
npc:SetMinimumGoldBounty(0)
npc:AddNewModifier(npc, nil, "modifier_movespeed_cap", {})
end
function PMP:OnHeroInGame(hero)
local hero_name = hero:GetUnitName()
--print("[PMP] OnHeroInGame "..hero_name)
if hero_name == "nian_boss" then return end
ClearAbilities(hero)
TeachAbility(hero, "hide_hero", 1)
if IsValidEntity(hero) and hero:GetPlayerID() and FORCED_RANDOM[hero:GetPlayerID()] then return end
Timers:CreateTimer(1, function()
if not IsValidEntity(hero) then return end
Setup_Hero_Panel(hero)
if hero:HasModifier("modifier_silencer_int_steal") then
hero:RemoveModifierByName("modifier_silencer_int_steal")
end
end)
-- Global upgrade abilities
hero:AddAbility("pimp_damage")
hero:AddAbility("pimp_armor")
hero:AddAbility("pimp_speed")
hero:AddAbility("pimp_regen")
hero:AddNoDraw()
-- Check for excess resources periodically
local playerID = hero:GetPlayerID()
local excessInterval = 30
if not PlayerResource:IsFakeClient(playerID) then
Timers:CreateTimer(excessInterval + RandomInt(2, 4), function()
if not hero.lost then
if CanAffordAllGoldUpgrades(playerID) then
Sounds:EmitSoundOnClient(playerID, "Announcer.Resource.ExcessGold")
end
return excessInterval
else
return
end
end)
Timers:CreateTimer(45 + RandomInt(2, 4), function()
if not hero.lost then
if CanAffordAllLumberUpgrades(playerID) then
Sounds:EmitSoundOnClient(playerID, "Announcer.Resource.ExcessLumber")
end
return excessInterval
else
return
end
end)
end
end
-- An entity somewhere has been hurt.
function PMP:OnEntityHurt(keys)
if keys.entindex_killed ~= nil then
local victim = EntIndexToHScript(keys.entindex_killed)
Sounds:ResolveAttackedSounds(victim)
end
end
-- Notify Panorama that the player acquired a new hero
function Setup_Hero_Panel(hero)
local playerid = hero:GetPlayerOwnerID()
local heroid = PlayerResource:GetSelectedHeroID(playerid)
local heroname = hero:GetUnitName()
-- get hero entindex and create hero panel
local HeroIndex = hero:GetEntityIndex()
Create_Hero_Panel(playerid, heroid, heroname, "landscape", HeroIndex)
end
function Create_Hero_Panel(playerid, heroID, heroname, imagestyle, HeroIndex)
local player = PlayerResource:GetPlayer(playerid)
CustomGameEventManager:Send_ServerToPlayer(player, "create_hero", {heroid=heroID, heroname=heroname, imagestyle=imagestyle, playerid = playerid, hero=HeroIndex})
end
-- Tries to random for players with unpicked heroes
FORCED_RANDOM = {}
function PMP:ForceRandom()
for playerID = 0, DOTA_MAX_TEAM_PLAYERS do
if PlayerResource:IsValidPlayerID(playerID) then
local hero = PlayerResource:GetSelectedHeroEntity(playerID)
if not hero then
local player = PlayerResource:GetPlayer(playerID)
if player then
FORCED_RANDOM[playerID] = true
CreateHeroForPlayer(RACES[RandomInt(1,#RACES)], player)
end
end
end
end
end
function PMP:OnGameInProgress()
print("[PMP] The game has officially begun")
GameRules:SendCustomMessage("Welcome to <font color='#FF0000'>Pimp My Peon</font>!", 0, 0)
--GameRules:SendCustomMessage("Version: <font color='#FF0000'>"..PMPVERSION.."</font>", 0, 0)
PMP:SpawnBoss()
-- Lumber Tick for players
for playerID = 0, DOTA_MAX_TEAM_PLAYERS do
Timers:CreateTimer(function()
if PlayerResource:IsValidPlayerID(playerID) then
local hero = PlayerResource:GetSelectedHeroEntity(playerID)
if hero and not hero.lost then
ModifyLumber(playerID, LUMBER_PER_TICK)
end
return LUMBER_TICK_TIME
end
end)
end
Timers:CreateTimer(1, function()
if not GameRules.Winner then
PMP:CheckWinCondition()
return 1
end
end)
end
gamestates =
{
[0] = "DOTA_GAMERULES_STATE_INIT",
[1] = "DOTA_GAMERULES_STATE_WAIT_FOR_PLAYERS_TO_LOAD",
[2] = "DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP",
[3] = "DOTA_GAMERULES_STATE_HERO_SELECTION",
[4] = "DOTA_GAMERULES_STATE_STRATEGY_TIME",
[5] = "DOTA_GAMERULES_STATE_TEAM_SHOWCASE",
[6] = "DOTA_GAMERULES_STATE_PRE_GAME",
[7] = "DOTA_GAMERULES_STATE_GAME_IN_PROGRESS",
[8] = "DOTA_GAMERULES_STATE_POST_GAME",
[9] = "DOTA_GAMERULES_STATE_DISCONNECT"
}
-- The overall game state has changed
function PMP:OnGameRulesStateChange(keys)
local newState = GameRules:State_Get()
print("[PMP] GameRules State Changed: ",gamestates[newState])
if newState == DOTA_GAMERULES_STATE_HERO_SELECTION then
PMP:PostLoadPrecache()
PMP:OnAllPlayersLoaded()
if GameRules.FillWithBots then
Timers:CreateTimer(0.1, function() AI:SpawnBots() end)
end
elseif newState == DOTA_GAMERULES_STATE_TEAM_SHOWCASE then
PMP:ForceRandom()
elseif newState == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then
PMP:OnGameInProgress()
elseif newState == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
GameRules.FillWithBots = FFA_MAP and PlayerResource:GetPlayerCount() == 1
if FFA_MAP then
statCollection:setFlags({BotDifficulty = "normal"})
statCollection:setFlags({FillWithBots = GameRules.FillWithBots})
end
end
end
-- An entity died
function PMP:OnEntityKilled( event )
--print( '[PMP] OnEntityKilled' )
local killed = EntIndexToHScript(event.entindex_killed)
local attacker
if event.entindex_attacker then
attacker = EntIndexToHScript(event.entindex_attacker)
end
if killed:IsHero() then
local origin = killed:GetAbsOrigin()
killed:SetAbsOrigin(Vector(origin.x, origin.y, origin.z+10000))
return
end
-- Safeguard
if killed.reincarnating then return end
RemoveUnitFromSelection(killed)
-- Killed credentials
local killed_player = killed:GetPlayerOwner()
local killed_playerID = killed:GetPlayerOwnerID()
local killed_teamNumber = killed:GetTeamNumber()
local killed_hero = PlayerResource:GetSelectedHeroEntity(killed_playerID)
-- Attacker credentials
local attacker_player = attacker and attacker:GetPlayerOwner()
local attacker_playerID = attacker and attacker:GetPlayerOwnerID()
local attacker_teamNumber = attacker and attacker:GetTeamNumber()
local attacker_hero = attacker_playerID and PlayerResource:GetSelectedHeroEntity(attacker_playerID)
-- Boss killed
if IsBoss(killed) then
SendBossSlain(attacker_playerID)
end
if killed:GetUnitName() == "outpost" then
Sounds:EmitSoundOnClient(killed_playerID, "Announcer.Destroyed.Outpost")
end
-- Garage killed
if IsCityCenter(killed) then
killed:AddNoDraw()
print("Garage Down for player",killed_playerID)
-- Make an Outpost for the attacker on the killed garage position
CreateOutpost(attacker_playerID, killed:GetAbsOrigin())
--[[local attacker_garage = GetPlayerCityCenter(attacker_playerID)
if attacker_garage then
local charges = attacker_garage:GetModifierStackCount("modifier_super_unit_charges", attacker_garage) + 1
attacker_garage:SetModifierStackCount("modifier_super_unit_charges", attacker_garage, charges)
-- Find super peon ability and skill it back to 1
local super_unit_ability = GetSuperUnitAbility(attacker_garage)
if super_unit_ability then
super_unit_ability:SetLevel(1)
end
end]]
SendDefeatedMessage(attacker_playerID,killed_playerID)
PMP:MakePlayerLose(killed_playerID)
-- Tower killed
elseif IsCustomTower(killed) then
print("Tower Killed for playerID ",killed_playerID)
-- Table cleanup
local tower_table = GetPlayerTowers(killed_playerID)
local unit_index = getIndexTable(tower_table, killed)
if unit_index then
print("Tower Removed from Table")
table.remove(tower_table, unit_index)
end
ParticleManager:CreateParticle("particles/newplayer_fx/npx_wood_break.vpcf", PATTACH_ABSORIGIN, killed)
killed:AddNoDraw()
local bInvuln = ReduceInvulnerabilityCount(killed_hero)
if bInvuln == 0 then
Sounds:EmitSoundOnClient(killed_playerID, "Announcer.Attacked.Garage")
else
Sounds:EmitSoundOnClient(killed_playerID, "Announcer.Destroyed.Tower")
end
print("Reduced Invulnerability count of playerID ",killed_playerID)
-- Pimp Creature killed
elseif IsPimpUnit(killed) then
-- Remove prop wearables
Timers:CreateTimer(4, function()
ClearPropWearables(killed)
end)
-- Table cleanup
local unit_table = GetPlayerUnits(killed_playerID)
local unit_index = getIndexTable(unit_table, killed)
if unit_index then
table.remove(unit_table, unit_index)
-- Substract the Food Used
if not killed.summoned then
local food = GetFoodCost(killed:GetUnitName())
ModifyFoodUsed(killed_playerID, -food)
end
end
if not killed.mortal_striked then
PhysicsFlail(killed, attacker)
end
-- If not denied
if killed_teamNumber ~= attacker_teamNumber then
-- Give experience globally to the attacker hero
if attacker_hero then
attacker_hero:AddExperience(XP_PER_PEON, true, true)
attacker_hero:IncrementKills(1)
end
killed_hero:IncrementDeaths(1)
--Add Score
end
Sounds:PlaySoundSet( killed_playerID, killed, "DIE" )
end
-- Give bounty to the attacker (unless denied)
if attacker_playerID and attacker_playerID ~= -1 and killed_teamNumber ~= attacker_teamNumber then
local lumber_bounty = GetLumberBounty(killed)
local gold_bounty = GetGoldBounty(killed)
-- Goblin Racial
if attacker and attacker:HasAbility("goblin_racial") then
local gg = attacker:FindAbilityByName("goblin_racial")
local bonus = gg:GetLevelSpecialValueFor("extra_bounty", gg:GetLevel()-1)
-- +1/-1
if RollPercentage(50) then
bonus = math.ceil(bonus)
else
bonus = math.floor(bonus)
end
lumber_bounty = lumber_bounty + bonus
gold_bounty = gold_bounty + bonus
end
-- Anti-farm mechanism
--[[local attacker_kills = attacker_hero:GetKills()
local killed_kills = killed_hero:GetKills()
local kill_difference = attacker_kills - killed_kills
local kill_diff_factor = killed_kills / attacker_kills
if kill_difference > 100 then
if kill_diff_factor <= 0.5 then
gold_bounty = 1
lumber_bounty = 1
elseif kill_diff_factor <= 0.75 then
gold_bounty = 2
lumber_bounty = 2
end
end]]
ModifyGold(attacker_playerID, gold_bounty)
PopupGoldGain(killed, gold_bounty, attacker_teamNumber)
ModifyLumber(attacker_playerID, lumber_bounty)
PopupLumber(killed, lumber_bounty, attacker_teamNumber)
EmitSoundOnClient("General.Coins", PlayerResource:GetPlayer(attacker_playerID))
attacker_hero.lumber_earned = attacker_hero.lumber_earned + lumber_bounty
end
end
function PMP:MakePlayerLose( playerID )
local hero = PlayerResource:GetSelectedHeroEntity(playerID)
local playerGarage = GetPlayerCityCenter(playerID)