Forum

> > CS2D > Scripts > Custom NPCs lagging extremely hard
Forums overviewCS2D overview Scripts overviewLog in to reply

English Custom NPCs lagging extremely hard

11 replies
To the start Previous 1 Next To the start

old Custom NPCs lagging extremely hard

NanuPlayer OP
User Off Offline

Quote
Hey, I have this custom NPCs script by Infinite Rain and LEGOMAN_2, and I'm not sure what's causing it to lag extremely hard when opening large maps like zAzz Jail or something, I've spent hours trying to figure out how to fix this... does anyone know if it's possible to fix?

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
-- Custom NPCs script by Crystal Rain
-- Version: 1.0
-- Release date: 30.08.2013

-- The script might be a little bit complicated. It's recommended not to change anything.
-- In order to add/modify NPCs you have to edit 'sys/lua/cnpc/list.lua' file.

-- Credits: EngiN33R -> Helped me to solve a problems with angels and grammar.

-- Setting up a global table
cnpc = {

	-- Table for spawned NPCs
	spawnedNPCs = {};
	
	-- Table for non-hooked functions
	funcs = {
		-- Table for NPC based functions
		npc = {
			-- Function to spawn an NPC
			-- type - Type id of an NPC
			-- x    - X spawn position on the map (in pixels)
			-- y    - Y spawn position on the map (in pixels)
			-- rot  - Rotation to spawn with
			spawn = function(type, x, y, rot, health)
				if type == nil then print('\169255000000Error in function \'cnpc.funcs.npc.spawn\': \'type\' paramater is not specified!') return end
				if x == nil then print('\169255000000Error in function \'cnpc.funcs.npc.spawn\': \'x\' paramater is not specified!') return end
				if y == nil then print('\169255000000Error in function \'cnpc.funcs.npc.spawn\': \'y\' paramater is not specified!') return end
				if rot == nil then print('\169255000000Error in function \'cnpc.funcs.npc.spawn\': \'rot\' paramater is not specified!') return end
				if not cnpc.list[type] then if id == nil then print('\169255000000Error in function \'cnpc.funcs.npc.spawn\': NPC with type '.. type ..' does not exist!') return end return end
				
				local npcData = cnpc.list[type]
				local npc = {
					x = x;
					y = y;
					rot = rot;
					type = type;
					image = image(npcData.imagePath, 0, 0, 1);
					
					name = npcData.name;
					speed = npcData.speed;
					rotSpeed = npcData.rotationSpeed;
					behaviorType = npcData.behaviorType;
					bulletNum = npcData.bulletNum;
					wander = npcData.wander;
					dropMoney = npcData.dropMoney;
					team = npcData.team;
					attackCoolDown = npcData.attackCoolDown;
					health = health or npcData.health;
					damage = npcData.damage;
					data = npcData;
					wanderRotateTo = 0;
					wanderSteps = 0;

					lastAttack = os.clock();
					closestPlayer = 0;
					closestPlayerLastSeen = false;
					timerParams = false;
				}
				imagepos(npc.image, x, y, rot)
				imagehitzone(npc.image, 3, -npcData.hitbox/2, -npcData.hitbox/2, npcData.hitbox, npcData.hitbox)
				table.insert(cnpc.spawnedNPCs, npc)
			end;
			
			-- Function that removes/despawns the NPC
			-- id - The ID of spawned NPC (from cnpc.spawnedNPCs table)
			remove = function(id)
				if id == nil then print('\169255000000Error in function \'cnpc.funcs.npc.remove\': \'id\' paramater is not specified!') return end
				if not cnpc.spawnedNPCs[id] then print('\169255000000Error in function \'cnpc.funcs.npc.remove\': NPC with ID '.. id ..' does not exist!') return end
				
				local npc = cnpc.spawnedNPCs[id]
				freeimage(npc.image)
				if npc.timerParams ~= false then freetimer(npc.timerParams[1], npc.timerParams[2]) npc.timerParams = false end
				cnpc.spawnedNPCs[id] = nil
			end;
			
			-- Function that forces NPC to attack
			-- id - The ID of spawned NPC (from cncp.spawnedNPCs table)
			-- NOTE: TO USE THIS FUNCTION, NPC HAS TO HAVE THE CLOSEST PLAYER!
			attack = function(id)
				if id == nil then print('\169255000000Error in function \'cnpc.funcs.npc.shoot\': \'id\' paramater is not specified!') return end
				if not cnpc.spawnedNPCs[id] then print('\169255000000Error in function \'cnpc.funcs.npc.shoot\': NPC with ID '.. id ..' does not exist!') return end
				
				local npc = cnpc.spawnedNPCs[id]
				npc.lastAttack = os.clock()
				if npc.behaviorType == 0 then
					local health, armor = cnpc.funcs.math.calculateDamage(player(npc.closestPlayer, 'health'), player(npc.closestPlayer, 'armor'), npc.damage)
					local slashImage = image('gfx/knifeslash.bmp', 0, 0, 1)
					
					tween_rotate(npc.image, 62.5, npc.rot+35)
					timer(62.5, 'parse', 'lua "tween_rotate('.. npc.image ..', '.. (npc.attackCoolDown-0.0625)*1000 ..', '.. npc.rot ..')"')
					
					imagepos(slashImage, npc.x, npc.y, npc.rot)
					imageblend(slashImage, 1)
					tween_alpha(slashImage, 250, 0)
					timer(250, 'freeimage', slashImage)
					
					if health <= 0 then
						parse('customkill 0 "'.. npc.name ..'" '.. npc.closestPlayer)
					else
						parse('sethealth '.. npc.closestPlayer ..' '.. health)
						parse('setarmor '.. npc.closestPlayer ..' '.. armor)
					end
					
					for _, pl in pairs(player(0, 'tableliving')) do
						local x, y = player(pl, 'x'), player(pl, 'y')
						if x >= npc.x - 320 and y >= npc.y - 240 and x <= npc.x + 320 and y <= npc.y + 240 then
							parse('sv_sound2 '.. pl ..' "'.. npc.data.slashSound ..'"')	
							parse('sv_sound2 '.. pl ..' "player/hit'.. math.random(1, 3) ..'.wav"')
						end
					end
				elseif npc.behaviorType == 1 then
				for i = 1, npc.bulletNum do
					local rot = npc.rot + math.random(-npc.data.bulletSpreading, npc.data.bulletSpreading)
					local startX, startY = cnpc.funcs.math.extendPosition(npc.x, npc.y, rot, npc.data.bulletOffset)
					local endX, endY = cnpc.funcs.math.extendPosition(startX, startY, rot, npc.data.bulletLength)
					local wallX, wallY = cnpc.funcs.math.wallOnLine(startX, startY, endX, endY)
					
					local bulletShot = {}
					local bulletFlash = image('gfx/sprites/flare3.bmp', startX, startY, 1)
					local distance
					if wallX == -1 then
						distance = cnpc.funcs.math.distance(startX, startY, endX, endY)
					else
						distance = cnpc.funcs.math.distance(startX, startY, wallX, wallY)
					end
					
					local x1, y1 = cnpc.funcs.math.extendPosition(startX, startY, rot, distance / 3)
					local x2, y2 = cnpc.funcs.math.extendPosition(startX, startY, rot, (distance / 3)*2)
					local x3, y3 = cnpc.funcs.math.extendPosition(startX, startY, rot, distance)
					bulletShot = {
						cnpc.funcs.draw.line(startX, startY, x1, y1);
						cnpc.funcs.draw.line(x1, y1, x2, y2);
						cnpc.funcs.draw.line(x2, y2, x3, y3);
					}
					
					for k, v in pairs(bulletShot) do
						imagealpha(v, (4-k)*0.22)
						imagecolor(v, 255, 255, 0)
						tween_alpha(v, 125, 0)
						timer(125, 'freeimage', v)
					end
					
					if i == 1 then
					imageblend(bulletFlash, 1)
					imagecolor(bulletFlash, 255, 255, 0)
					imagealpha(bulletFlash, 0.25)
					tween_alpha(bulletFlash, 125, 0)
					timer(125, 'freeimage', bulletFlash)
					else
					imageblend(bulletFlash, 1)
					imagecolor(bulletFlash, 255, 255, 0)
					imagealpha(bulletFlash, 0)
					tween_alpha(bulletFlash, 125, 0)
					timer(125, 'freeimage', bulletFlash)
					end
					
					for _, pl in pairs(player(0, 'tableliving')) do
						local x, y = player(pl, 'x'), player(pl, 'y')
						if x >= npc.x - 320 and y >= npc.y - 240 and x <= npc.x + 320 and y <= npc.y + 240 then
							if i == 1 then
								parse('sv_sound2 '.. pl ..' "'.. npc.data.bulletSound ..'"')
							end
						end
						
						local lx, ly = npc.x, npc.y
						local ex, ey = math.sin(math.rad(npc.rot)), -math.cos(math.rad(npc.rot))
						for i = 0, distance do
							lx, ly = lx + ex, ly + ey
							if x >= lx - 6 and y >= ly - 6 and x <= lx + 6 and y <= ly + 6 then
								local health, armor = cnpc.funcs.math.calculateDamage(player(pl, 'health'), player(pl, 'armor'), npc.damage)
								if health <= 0 then
									parse('customkill 0 "'.. npc.name ..'" '.. npc.closestPlayer)
								else
									parse('sethealth '.. npc.closestPlayer ..' '.. health)
									parse('setarmor '.. npc.closestPlayer ..' '.. armor)
								end
								break
							end
						end
					end
				end
				end
			end;
		};
		
		-- Table for math based function
		math = {
			-- Function that returns distance between two points (in pixels)
			-- x1 - X position of the first point
			-- y1 - Y position of the first point
			-- x2 - X position of the second point
			-- y2 - Y position of the second point
			distance = function(x1, y1, x2, y2)
				return math.sqrt((y1 - y2)^2 + (x1 - x2)^2)
			end;

			-- Function that returns an angle between first and second point
			-- x1 - X position of the first point
			-- y1 - Y position of the first point
			-- x2 - X position of the second point
			-- y2 - Y position of the second point
			getAngle = function(x1, y1, x2, y2)
				return -math.deg(math.atan2(x1 - x2, y1 - y2))
			end;

			-- Function that returns an extended position by dist pixels
			-- x    - X position
			-- x    - Y position
			-- dir  - Direction in which the position should be extended
			-- dist - Distance by which the position should be extended
			extendPosition = function(x, y, dir, dist)
				return x + math.sin(math.rad(dir)) * dist, y - math.cos(math.rad(dir)) * dist
			end;
			
			-- Function that returns a negatively extended position by dist pixels. Just called 'dextend' for fun :P
			-- x    - X position
			-- x    - Y position
			-- dir  - Direction in which the position should be dextended
			-- dist - Distance by which the position should be dextended
			dextendPosition = function(x, y, dir, dist)
				return x - math.sin(math.rad(dir)) * dist, y - math.cos(math.rad(dir)) * dist
			end;

			-- Function that returns calculated health and armor when player has got hit
			-- health - Player's health
			-- armor  - Player's armor
			-- damage - Damage by which the player got hit
			calculateDamage = function(health, armor, damage)
				damage = (damage < 1 and 1 or damage)
				local coveredDamage, uncoveredDamage
				local returnHealth, returnArmor
				
				if armor > 200 then
					if armor == 201 then
						return math.floor(health-(damage*(1-0.25))), armor
					elseif armor == 202 or armor == 204 then
						return math.floor(health-(damage*(1-0.5))), armor
					elseif armor == 203 then
						return math.floor(health-(damage*(1-0.75))), armor
					elseif armor == 205 then
						return math.floor(health-(damage*(1-0.95))), armor
					end
				end
				
				if damage >= armor then
					uncoveredDamage = damage - armor
					coveredDamage = armor
				else
					uncoveredDamage = 0
					coveredDamage = damage
				end
				returnHealth = health-(coveredDamage*(1.0-tonumber(game('mp_kevlar'))))-uncoveredDamage
				returnArmor = armor - coveredDamage

				return math.floor(returnHealth), returnArmor
			end;

			-- Function that returns the position of the closest wall from first point to second point
			-- Returns -1, -1 if there is no wall
			-- x1 - X position of the first point
			-- y1 - Y position of the first point
			-- x2 - X position of the second point
			-- y2 - Y position of the second point
			wallOnLine = function(x1, y1, x2, y2)
				local angle = cnpc.funcs.math.getAngle(x1, y1, x2, y2)
				local distance = math.floor(cnpc.funcs.math.distance(x1, y1, x2, y2))
				local increaseX, increaseY = cnpc.funcs.math.extendPosition(x1, y1, angle, 1)
				for i = 1, distance do
					x1, y1 = cnpc.funcs.math.extendPosition(x1, y1, angle, 1)
					if tile(math.floor(x1/32), math.floor(y1/32), 'wall') then
						return x1, y1
					end
				end
				return -1, -1
			end;

			-- Function that returns the position of the closest wall or obstacle from first point to second point
			-- Returns -1, -1 if there is no wall or obstacle
			-- x1 - X position of the first point
			-- y1 - Y position of the first point
			-- x2 - X position of the second point
			-- y2 - Y position of the second point
			obstructionOnLine = function(x1, y1, x2, y2)
				local angle = cnpc.funcs.math.getAngle(x1, y1, x2, y2)
				local distance = math.floor(cnpc.funcs.math.distance(x1, y1, x2, y2))
				local increaseX, increaseY = cnpc.funcs.math.extendPosition(x1, y1, angle, 1)
				for i = 1, distance do
					x1, y1 = cnpc.funcs.math.extendPosition(x1, y1, angle, 1)
					if tile(math.floor(x1/32), math.floor(y1/32), 'wall') then
						return x1, y1
					elseif tile(math.floor(x1/32), math.floor(y1/32), 'obstacle') then
						return x1, y1
					end
				end
				return -1, -1
			end;
		};

		draw = {
			-- Draws a line 
			-- x1 - Starting X position
			-- y1 - Starting Y position
			-- x2 - Ending X position
			-- y2 - Ending Y position
			line = function(x1, y1, x2, y2, mode)
				mode = mode or 1
				local line = image('gfx/cnpc/1x1.png', 0, 0, mode)
				local angle, distance = cnpc.funcs.math.getAngle(x1, y1, x2, y2), cnpc.funcs.math.distance(x1, y1, x2, y2)
				local x, y = cnpc.funcs.math.extendPosition(x1, y1, angle, distance/2)
				imagepos(line, x, y, angle)
				imagescale(line, 1, distance)
				return line
			end
		}
	};
	
	-- Table for hooked functions
	hooks = {
		-- Always hook
		always = function()
			-- Scripting NPC's behavior
			for npcID, npc in pairs(cnpc.spawnedNPCs) do
				local lastClosest = npc.closestPlayer
				npc.closestPlayer = 0
				local lastDist = 240
				for _, pl in pairs(player(0, 'tableliving')) do
					if npc.behaviorType == 0 then
						local wallX, wallY = cnpc.funcs.math.obstructionOnLine(npc.x, npc.y, player(pl, 'x'), player(pl, 'y'))
							if wallX == -1 then
								local distance = cnpc.funcs.math.distance(npc.x, npc.y, player(pl, 'x'), player(pl, 'y'))
								if distance < lastDist then
									if npc.team == 0 then
										npc.closestPlayer = pl
										lastDist = distance
									elseif npc.team == 1 then
										if player(pl, 'team') ~= 1 then
											npc.closestPlayer = pl
											lastDist = distance
										end
									elseif npc.team == 2 then
										if player(pl, 'team') == 1 then
											npc.closestPlayer = pl
											lastDist = distance
										end
									end
								end
							end
					elseif npc.behaviorType == 1 then
						local wallX, wallY = cnpc.funcs.math.wallOnLine(npc.x, npc.y, player(pl, 'x'), player(pl, 'y'))
						if wallX == -1 then
							local distance = cnpc.funcs.math.distance(npc.x, npc.y, player(pl, 'x'), player(pl, 'y'))
							if distance < lastDist then
								if npc.team == 0 then
									npc.closestPlayer = pl
									lastDist = distance
								elseif npc.team == 1 then
									if player(pl, 'team') ~= 1 then
										npc.closestPlayer = pl
										lastDist = distance
									end
								elseif npc.team == 2 then
									if player(pl, 'team') == 1 then
										npc.closestPlayer = pl
										lastDist = distance
									end
								end
							end
						end
					end
				end
				if npc.closestPlayer ~= 0 then
					if lastClosest ~= npc.closestPlayer then
						npc.closestPlayerLastSeen = {player(npc.closestPlayer, 'x'), player(npc.closestPlayer, 'y')}
					end
				end
			
				if npc.closestPlayer > 0 then
					local angle = math.floor(cnpc.funcs.math.getAngle(npc.x, npc.y, npc.closestPlayerLastSeen[1], npc.closestPlayerLastSeen[2]))
					if (npc.lastAttack + npc.attackCoolDown < os.clock() and npc.behaviorType == 0) or (npc.behaviorType > 0) then
						if not (npc.rot <= angle + npc.rotSpeed and npc.rot >= angle - npc.rotSpeed) then
							if npc.rot > angle then
								if math.abs(angle - npc.rot) % 360 > 180 then
									npc.rot = npc.rot + npc.rotSpeed
								else
									npc.rot = npc.rot - npc.rotSpeed
								end
							else
								if math.abs(angle - npc.rot) % 360 > 180 then
									npc.rot = npc.rot - npc.rotSpeed
								else
									npc.rot = npc.rot + npc.rotSpeed
								end
							end
							if angle == -180 and npc.rot == 180 then npc.rot = -180 end -- This will prevent NPCs from freezing
							if npc.rot > 180 then npc.rot = npc.rot - 360 end
							if npc.rot < -180 then npc.rot = npc.rot + 360 end
							imagepos(npc.image, npc.x, npc.y, npc.rot)
						else
							npc.rot = angle
							imagepos(npc.image, npc.x, npc.y, npc.rot)
							if npc.timerParams == false then
								npc.timerParams = {'parse', 'lua "cnpc.spawnedNPCs['.. npcID ..'].closestPlayerLastSeen = {'.. player(npc.closestPlayer, 'x') ..', '.. player(npc.closestPlayer, 'y') ..'} cnpc.spawnedNPCs['.. npcID ..'].timerParams = false"'}
								timer(62.5, 'parse', npc.timerParams[2])
							end
						end
					end
					
					if (npc.rot <= angle + npc.rotSpeed and npc.rot >= angle - npc.rotSpeed) then
						if not npc.rot == angle then
							npc.rot = angle
							imagepos(npc.image, npc.x, npc.y, npc.rot)
						end
						local distance = cnpc.funcs.math.distance(npc.x, npc.y, player(npc.closestPlayer, 'x'), player(npc.closestPlayer, 'y'))
						if npc.lastAttack + npc.attackCoolDown < os.clock() then
							if npc.behaviorType == 0 and not (distance < 20) then
								local x, y = cnpc.funcs.math.extendPosition(npc.x, npc.y, npc.rot, npc.speed)
								if tile(math.floor((x-6)/ 32), math.floor((y-6) / 32), 'walkable') and tile(math.floor((x+6)/ 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x-6) / 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x+6) / 32), math.floor((y-6) / 32), 'walkable') then
									npc.x, npc.y = cnpc.funcs.math.extendPosition(npc.x, npc.y, npc.rot, npc.speed)
									imagepos(npc.image, npc.x, npc.y, npc.rot)
								end
							elseif npc.behaviorType == 0 and (distance < 20) then
								cnpc.funcs.npc.attack(npcID)
							elseif npc.behaviorType == 1 and npc.closestPlayer > 0 then
								cnpc.funcs.npc.attack(npcID)
							end
							if npc.behaviorType == 1 and (distance < 20) and npc.closestPlayer > 0 then
								local x, y = cnpc.funcs.math.dextendPosition(npc.x, npc.y, npc.rot, npc.speed)
								if tile(math.floor((x-6)/ 32), math.floor((y-6) / 32), 'walkable') and tile(math.floor((x+6)/ 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x-6) / 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x+6) / 32), math.floor((y-6) / 32), 'walkable') then
									npc.x, npc.y = cnpc.funcs.math.dextendPosition(npc.x, npc.y, npc.rot, npc.speed)
									imagepos(npc.image, npc.x, npc.y, npc.rot)
								end
							end
						end
					end
				elseif npc.closestPlayer == 0 and lastClosest == 0 then
					if npc.closestPlayerLastSeen ~= false then
						if not (math.floor(npc.x/32) == math.floor(npc.closestPlayerLastSeen[1]/32) and math.floor(npc.y/32) == math.floor(npc.closestPlayerLastSeen[2]/32)) then	
							local angle = math.floor(cnpc.funcs.math.getAngle(npc.x, npc.y, npc.closestPlayerLastSeen[1], npc.closestPlayerLastSeen[2]))
							if not (npc.rot <= angle + npc.rotSpeed and npc.rot >= angle - npc.rotSpeed) then
								if npc.rot > angle then
									if math.abs(angle - npc.rot) % 360 > 180 then
										npc.rot = npc.rot + npc.rotSpeed
									else
										npc.rot = npc.rot - npc.rotSpeed
									end
								else
									if math.abs(angle - npc.rot) % 360 > 180 then
										npc.rot = npc.rot - npc.rotSpeed
									else
										npc.rot = npc.rot + npc.rotSpeed
									end
								end
								if angle == -180 and npc.rot == 180 then npc.rot = -180 end -- This will prevent NPCs from freezing
								if npc.rot > 180 then npc.rot = npc.rot - 360 end
								if npc.rot < -180 then npc.rot = npc.rot + 360 end
								imagepos(npc.image, npc.x, npc.y, npc.rot)
							else
								if not npc.rot == angle then
									npc.rot = angle
									imagepos(npc.image, npc.x, npc.y, npc.rot)
								end
								
								local x, y = cnpc.funcs.math.extendPosition(npc.x, npc.y, npc.rot, npc.speed)
								if tile(math.floor((x-6)/ 32), math.floor((y-6) / 32), 'walkable') and tile(math.floor((x+6)/ 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x-6) / 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x+6) / 32), math.floor((y-6) / 32), 'walkable') then
									npc.x, npc.y = cnpc.funcs.math.extendPosition(npc.x, npc.y, npc.rot, npc.speed)
									imagepos(npc.image, npc.x, npc.y, npc.rot)
								end
							end
						end
					end
					
					
					if math.random(1,300) == 1 then
						if npc.wanderSteps < 1 then
							if math.random(1,2) == 1 then
							npc.wanderRotateTo = npc.rot + math.random(0,90)
							else
							npc.wanderRotateTo = npc.rot - math.random(0,90)
							end
							if npc.wanderRotateTo > 359 then
							npc.wanderRotateTo = 359
							end
							if npc.wanderRotateTo < 0 then
							npc.wanderRotateTo = 0
							end
						npc.wanderSteps = math.random(500,1000) * npc.wander
						end
					end
					if npc.wanderSteps > 0 then
						npc.wanderSteps = npc.wanderSteps - 1
					if npc.rot > npc.wanderRotateTo then
						if math.abs(npc.wanderRotateTo - npc.rot) % 360 > 180 then
							npc.rot = npc.rot + npc.rotSpeed
						else
							npc.rot = npc.rot - npc.rotSpeed
						end
						else
						if math.abs(npc.wanderRotateTo - npc.rot) % 360 > 180 then
							npc.rot = npc.rot - npc.rotSpeed
						else
							npc.rot = npc.rot + npc.rotSpeed
						end
						if npc.wanderRotateTo == -180 and npc.rot == 180 then npc.rot = -180 end -- This will prevent NPCs from freezing
						if npc.rot > 180 then npc.rot = npc.rot - 360 end
						if npc.rot < -180 then npc.rot = npc.rot + 360 end
					end
					imagepos(npc.image, npc.x, npc.y, npc.rot)
						local x, y = cnpc.funcs.math.extendPosition(npc.x, npc.y, npc.rot, npc.speed)
						if tile(math.floor((x-6)/ 32), math.floor((y-6) / 32), 'walkable') and tile(math.floor((x+6)/ 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x-6) / 32), math.floor((y+6) / 32), 'walkable') and tile(math.floor((x+6) / 32), math.floor((y-6) / 32), 'walkable') then
							npc.x, npc.y = cnpc.funcs.math.extendPosition(npc.x, npc.y, npc.rot, npc.speed / 2)
							imagepos(npc.image, npc.x, npc.y, npc.rot)
						else
						if npc.wanderRotateTo > 179 then
							npc.wanderRotateTo = npc.wanderRotateTo + math.random(10,20)
							npc.rot = npc.wanderRotateTo
							imagepos(npc.image, npc.x, npc.y, npc.rot)
							else
							npc.wanderRotateTo = npc.wanderRotateTo - math.random(10,20)
							npc.rot = npc.wanderRotateTo
							imagepos(npc.image, npc.x, npc.y, npc.rot)
							if math.random(1,5) == 1 then
								npc.wanderSteps = npc.wanderSteps - 100
							end
						end
						end
					end
				end
			end
		end;
		
		-- Leave hook
		leave = function(id)
			-- Removing timer when closest player leaves the server
			for _, npc in pairs(cnpc.spawnedNPCs) do
				if npc.closestPlayer == id then
					if npc.timerParams ~= false then
						freetimer(npc.timeParams[1], npc.timeParams[2])
						npc.timerParams = false
					end
				end
			end
		end;
		
		-- Hitzone hook
		hitzone = function(imageid, playerid, objectid, weapon, impactx, impacty)
			-- NPC getting hit
			local npc, npcID
			for k, v in pairs(cnpc.spawnedNPCs) do
				if v.image == imageid then
					npc = v
					npcID = k
					break
				end
			end
			
			npc.health = npc.health - itemtype(player(playerid, 'weapontype'), 'dmg')
			if npc.health <= 0 then
				if npc.dropMoney == 1 then
				parse('spawnitem 66 '.. npc.x / 32 ..' '.. npc.y / 32 ..'')
				end
				if npc.dropMoney == 2 then
				parse('spawnitem 67 '.. npc.x / 32 ..' '.. npc.y / 32 ..'')
				end
				if npc.dropMoney == 3 then
				parse('spawnitem 68 '.. npc.x / 32 ..' '.. npc.y / 32 ..'')
				end
				cnpc.funcs.npc.remove(npcID)
				for _, pl in pairs(player(0, 'tableliving')) do
					local x, y = player(pl, 'x'), player(pl, 'y')
					if x >= npc.x - 320 and y >= npc.y - 240 and x <= npc.x + 320 and y <= npc.y + 240 then
						parse('sv_sound2 '.. pl ..' "'.. npc.data.deathSound ..'"')
					end
				end
			end
		end;
		
		-- Trigger hook
		trigger = function(trigger, source)
			-- Spawning NPCs on trigger
			for x = 0, map('xsize') do
				for y = 0, map('ysize') do
					if entity(x, y, 'name') == trigger and entity(x, y, 'typename') == 'Env_Item' then
						local wordTable = {}
						for word in string.gmatch(entity(x, y, 'trigger'), '[^%s]+') do
							table.insert(wordTable, word)
						end
						if wordTable[1] == 'cnpc' then
							local id, health, rot, spawn = tonumber(wordTable[2]), tonumber(wordTable[3]), tonumber(wordTable[4]), tonumber(wordTable[5])
							local error = false
							
							-- Checking entity for the errors
							local parameters = {'id', 'health', 'rot', 'spawn'}
							for k, v in pairs(parameters) do
								if not wordTable[k+1] then
									print(k+1)
									error = '\169255000000Unable to spawn NPC using entity on tile \''.. x ..'|'.. y ..'\': \''.. v ..'\' parameter is not specified!'
								end
							end
							if not error then
								if not cnpc.list[id] then
									error = '\169255000000Unable to spawn NPC using entity on tile \''.. x ..'|'.. y ..'\': NPC with type '.. id ..' does not exist!'
								end
							end
							
							if not error then
								if spawn == 1 then
									cnpc.funcs.npc.spawn(id, x*32+16, y*32+16, rot, health == 0 and nil or health)
								end
							else
								print(error)
							end
						end
					end
				end
			end
		end;
		
		-- Startround hook
		startround = function()
			-- Despawning all the NPCs from the last round and spawning new
			freetimer()
			cnpc.spawnedNPCs = {}
			
			for x = 0, map('xsize') do
				for y = 0, map('ysize') do
					if entity(x, y, 'typename') == 'Env_Item' then
						local wordTable = {}
						for word in string.gmatch(entity(x, y, 'trigger'), '[^%s]+') do
							table.insert(wordTable, word)
						end
						
						if wordTable[1] == 'cnpc' then
							local id, health, rot, spawn = tonumber(wordTable[2]), tonumber(wordTable[3]), tonumber(wordTable[4]), tonumber(wordTable[5])
							local error = false
							
							-- Checking entity for the errors
							local parameters = {'id', 'health', 'rot', 'spawn'}
							for k, v in pairs(parameters) do
								if not wordTable[k+1] then
									error = '\169255000000Unable to spawn NPC using entity on tile \''.. x ..'|'.. y ..'\': \''.. v ..'\' parameter is not specified!'
								end
							end
							if not error then
								if not cnpc.list[id] then
									error = '\169255000000Unable to spawn NPC using entity on tile \''.. x ..'|'.. y ..'\': NPC with type '.. id ..' does not exist!'
								end
							end
							
							if not error then
								if spawn == 1 then
									cnpc.funcs.npc.spawn(id, x*32+16, y*32+16, rot, health == 0 and nil or health)
								end
							else
								print(error)
							end
						end
					end
				end
			end
		end;
		
		-- Parse hook
		parse = function(cmd)
			-- Creating console commands
			local wordTable = {}
			for word in string.gmatch(cmd, '[^%s]+') do
				table.insert(wordTable, word)
			end
			
			local parameters, error, successFunction, identifier
			if wordTable[1] == 'cnpc_spawn' then
				parameters = {'type', 'x', 'y', 'rot'}
				identifier = 'type'
				successFunction = function()
					cnpc.funcs.npc.spawn(tonumber(wordTable[2]), tonumber(wordTable[3]), tonumber(wordTable[4]), tonumber(wordTable[5]))
				end
			elseif wordTable[1] == 'cnpc_damage' then
				parameters = {'id', 'damage'}
				identifier = 'id'
				successFunction = function()
					local npc = cnpc.spawnedNPCs[tonumber(wordTable[2])]
					npc.health = npc.health - tonumber(wordTable[3])
					if npc.health <= 0 then
						cnpc.funcs.npc.remove(tonumber(wordTable[2]))
						for _, pl in pairs(player(0, 'tableliving')) do
							local x, y = player(pl, 'x'), player(pl, 'y')
							if x >= npc.x - 320 and y >= npc.y - 240 and x <= npc.x + 320 and y <= npc.y + 240 then
								parse('sv_sound2 '.. pl ..' "'.. npc.data.deathSound ..'"')
							end
						end
					end
				end
			elseif wordTable[1] == 'cnpc_despawn' then
				parameters = {'id'}
				identifier = 'id'
				successFunction = function()
					cnpc.funcs.npc.remove(tonumber(wordTable[2]))
				end
			end
			
			if parameters then
				for k, v in pairs(parameters) do
					if not wordTable[k+1] then
						error = '\169255000000Unable to execute \''.. wordTable[1] ..'\' command: \''.. v ..'\' parameter is not specified!'
					end
				end
				if not error then
					local i = tonumber(wordTable[2])
					if identifier == 'type' then
						if not cnpc.list[i] then
							error = '\169255000000Unable to execute \''.. wordTable[1] ..'\' command: NPC with type '.. i ..' does not exist!'
						end
					else
						if not cnpc.spawnedNPCs[i] then
							error = '\169255000000Unable to execute \''.. wordTable[1] ..'\' command: NPC with id '.. i ..' does not exist!'
						end
					end
				end
				
				if not error then
					successFunction()
				else
					print(error)
				end
				return 2
			end
		end;
	};
}

-- Adding hooks
addhook('always', 'cnpc.hooks.always')
addhook('leave', 'cnpc.hooks.leave')
addhook('hitzone', 'cnpc.hooks.hitzone')
addhook('trigger', 'cnpc.hooks.trigger')
addhook('startround', 'cnpc.hooks.startround')
addhook('parse', 'cnpc.hooks.parse')

-- Loading up a custom NPC list
dofile('sys/lua/cnpc/list.lua')

-- Checking NPC list for the errors
print('\169255255255Checking NPC list for critical errors...')
local errors = 0
for npcType, npc in pairs(cnpc.list) do
	local error = false
	for _, value in pairs({'name', 'imagePath', 'deathSound', 'speed', 'rotationSpeed', 'behaviorType', 'attackCoolDown', 'health', 'damage', 'hitbox'}) do
		if npc[value] == nil then
			print('\169255000000Error in type '.. npcType ..': Value \''.. value ..'\' is not specified! Excluding the NPC from the list.')
			cnpc.list[npcType] = nil
			error = true
			errors = errors + 1
			break
		end
	end
	if not error then
		local parameters =  {[0] = {'slashSound'}, [1] = {'bulletLength', 'bulletSpreading', 'bulletOffset', 'bulletSound'}}
		for _, value in pairs(parameters[npc.behaviorType]) do
			if npc[value] == nil then
				print('\169255000000Error in checking type '.. npcType ..': Value \''.. value ..'\' is not specified! Excluding the NPC from the list.')
				cnpc.list[npcType] = nil
				error = true
				errors = errors + 1
				break
			end
		end
	end
end
print(errors == 0 and '\169255255255No errors was found in the NPC list.' or '\169255000000'.. errors ..' error(s) have occured during the check! See messages above for more information!')

-- Executing "startround" hook
cnpc.hooks.startround()

old Re: Custom NPCs lagging extremely hard

NanuPlayer OP
User Off Offline

Quote
It works fine in small maps, but large maps are extremely laggy

2gen NPCs works in both small and large maps but it is hard to edit so that's why I need this script

old Re: Custom NPCs lagging extremely hard

Infinite Rain
Reviewer Off Offline

Quote
Are you spawning any NPCs in those maps? If so, how many?

Edit: I think I know why the lag is happening. The trigger hook is executed every single time a trigger happens on the map, on huge maps there could be many triggers that are happening all the time, so that might be the source of lag. Looks like it searches for all the entities with a spawn npc trigger every single time. A simple way to optimize it would be to collect all the entities ONCE per map start. Then on trigger, simply iterate over the list of the collected entities to prevent the inefficient loop every single time.

old Re: Custom NPCs lagging extremely hard

NanuPlayer OP
User Off Offline

Quote
hey I improved your version and made it alot better though if you wanna check it out

here is what is new :

NPCs can now follow you [ Click E on them ]
NPCs can now attack each other
NPCs wander got a little improved

Cut features :

NPCs were able to throw grenades
NPCs had a reload animation [ Yes they had to reload their weapons lol ]
NPCs takes cover

Spoiler >

old Re: Custom NPCs lagging extremely hard

DC
Admin Off Offline

Quote
It lags when you open the map? What exactly does that mean?
Is it just slower to open the map or is the performance bad the whole time while being on the map?

Are you using any other scripts together with this script? If yes you should isolate and see if the problem still occurs when ONLY using this script.

Easiest way to find the hook causing the issues is probably to comment out the addhook lines one by one. (starting at lines 733 and following). You can do so by adding -- in front of the lines.
Comment out line 733 by putting -- in front of it, start the game, see if the performance is still bad. If yes try the next run (remove the -- again and put it in the next line, start game again)

If you only have performance issues at startup it might be the startround hook because it checks all tiles on the map one by one to find specific entities. The performance of this is directly affected by map size. This could be optimized a lot by simply using cs2d lua cmd entitylist instead of iterating all tiles.

old Re: Custom NPCs lagging extremely hard

NanuPlayer OP
User Off Offline

Quote
It was addhook('trigger', 'cnpc.hooks.trigger'), after I disabled it, it still lagged a little, then the lag vanished, so its playable now, script seems to be working normally, it might be broken I'm not sure yet

Thanks!

old Re: Custom NPCs lagging extremely hard

Infinite Rain
Reviewer Off Offline

Quote
This version should not have lag anymore, I have optimized the trigger and startround hooks:

More >
To the start Previous 1 Next To the start
Log in to reply Scripts overviewCS2D overviewForums overview