DEADSOFTWARE

game: disable gfx for server
[d2df-sdl.git] / src / game / g_items.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_items;
18 interface
20 uses
21 SysUtils, Classes,
22 MAPDEF, g_textures, g_phys, g_saveload;
24 Type
25 PItem = ^TItem;
26 TItem = record
27 private
28 //treeNode: Integer;
29 slotIsUsed: Boolean;
30 arrIdx: Integer; // in ggItems
32 public
33 ItemType: Byte;
34 Respawnable: Boolean;
35 InitX, InitY: Integer;
36 RespawnTime: Word;
37 alive: Boolean;
38 Fall: Boolean;
39 QuietRespawn: Boolean;
40 SpawnTrigger: Integer;
41 Obj: TObj;
42 Animation: TAnimationState;
43 dropped: Boolean; // dropped from the monster? drops should be rendered after corpses, so zombie corpse will not obscure ammo container, for example
45 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
47 property used: Boolean read slotIsUsed;
48 property myid: Integer read arrIdx;
49 end;
51 procedure g_Items_LoadData();
52 procedure g_Items_FreeData();
53 procedure g_Items_Init();
54 procedure g_Items_Free();
55 function g_Items_Create(X, Y: Integer; ItemType: Byte;
56 Fall, Respawnable: Boolean; AdjCoord: Boolean = False; ForcedID: Integer = -1): DWORD;
57 procedure g_Items_SetDrop (ID: DWORD);
58 procedure g_Items_PreUpdate();
59 procedure g_Items_Update();
60 procedure g_Items_Pick(ID: DWORD);
61 procedure g_Items_Remove(ID: DWORD);
62 procedure g_Items_SaveState (st: TStream);
63 procedure g_Items_LoadState (st: TStream);
65 procedure g_Items_RestartRound ();
67 function g_Items_ValidId (idx: Integer): Boolean; inline;
68 function g_Items_ByIdx (idx: Integer): PItem;
69 function g_Items_ObjByIdx (idx: Integer): PObj;
71 procedure g_Items_EmitPickupSound (idx: Integer); // at item position
72 procedure g_Items_EmitPickupSoundAt (idx, x, y: Integer);
74 procedure g_Items_AddDynLights();
77 type
78 TItemEachAliveCB = function (it: PItem): Boolean is nested; // return `true` to stop
80 function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false): Boolean;
83 var
84 gMaxDist: Integer = 1; // for sounds
86 var (* private state *)
87 ggItems: Array of TItem = nil;
89 implementation
91 uses
92 {$IFDEF ENABLE_GFX}
93 g_gfx,
94 {$ENDIF}
95 Math,
96 g_basic, g_sound, g_map,
97 g_game, g_triggers, g_console, g_player, g_net, g_netmsg,
98 e_log, g_options,
99 g_grid, binheap, idpool, utils, xstreams
102 // ////////////////////////////////////////////////////////////////////////// //
103 var
104 freeIds: TIdPool = nil;
107 // ////////////////////////////////////////////////////////////////////////// //
108 function g_Items_ValidId (idx: Integer): Boolean; inline;
109 begin
110 result := false;
111 if (idx < 0) or (idx > High(ggItems)) then exit;
112 if not ggItems[idx].slotIsUsed then exit;
113 result := true;
114 end;
117 function g_Items_ByIdx (idx: Integer): PItem;
118 begin
119 if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('g_ItemObjByIdx: invalid index');
120 result := @ggItems[idx];
121 if not result.slotIsUsed then raise Exception.Create('g_ItemObjByIdx: requested inexistent item');
122 end;
125 function g_Items_ObjByIdx (idx: Integer): PObj;
126 begin
127 if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('g_ItemObjByIdx: invalid index');
128 if not ggItems[idx].slotIsUsed then raise Exception.Create('g_ItemObjByIdx: requested inexistent item');
129 result := @ggItems[idx].Obj;
130 end;
133 // ////////////////////////////////////////////////////////////////////////// //
134 procedure TItem.positionChanged ();
135 begin
136 end;
139 // ////////////////////////////////////////////////////////////////////////// //
140 const
141 ITEM_SIGNATURE = $4D455449; // 'ITEM'
143 ITEMSIZE: Array [ITEM_MEDKIT_SMALL..ITEM_MAX] of Array [0..1] of Byte =
144 (((14), (15)), // MEDKIT_SMALL
145 ((28), (19)), // MEDKIT_LARGE
146 ((28), (19)), // MEDKIT_BLACK
147 ((31), (16)), // ARMOR_GREEN
148 ((31), (16)), // ARMOR_BLUE
149 ((25), (25)), // SPHERE_BLUE
150 ((25), (25)), // SPHERE_WHITE
151 ((24), (47)), // SUIT
152 ((14), (27)), // OXYGEN
153 ((25), (25)), // INVUL
154 ((62), (24)), // WEAPON_SAW
155 ((63), (12)), // WEAPON_SHOTGUN1
156 ((54), (13)), // WEAPON_SHOTGUN2
157 ((54), (16)), // WEAPON_CHAINGUN
158 ((62), (16)), // WEAPON_ROCKETLAUNCHER
159 ((54), (16)), // WEAPON_PLASMA
160 ((61), (36)), // WEAPON_BFG
161 ((54), (16)), // WEAPON_SUPERPULEMET
162 (( 9), (11)), // AMMO_BULLETS
163 ((28), (16)), // AMMO_BULLETS_BOX
164 ((15), ( 7)), // AMMO_SHELLS
165 ((32), (12)), // AMMO_SHELLS_BOX
166 ((12), (27)), // AMMO_ROCKET
167 ((54), (21)), // AMMO_ROCKET_BOX
168 ((15), (12)), // AMMO_CELL
169 ((32), (21)), // AMMO_CELL_BIG
170 ((22), (29)), // AMMO_BACKPACK
171 ((16), (16)), // KEY_RED
172 ((16), (16)), // KEY_GREEN
173 ((16), (16)), // KEY_BLUE
174 (( 1), ( 1)), // WEAPON_KASTET
175 ((43), (16)), // WEAPON_PISTOL
176 ((14), (18)), // BOTTLE
177 ((16), (15)), // HELMET
178 ((32), (24)), // JETPACK
179 ((25), (25)), // INVIS
180 ((53), (20)), // WEAPON_FLAMETHROWER
181 ((13), (20))); // AMMO_FUELCAN
183 procedure g_Items_LoadData();
184 begin
185 e_WriteLog('Loading items data...', TMsgType.Notify);
187 g_Sound_CreateWADEx('SOUND_ITEM_RESPAWNITEM', GameWAD+':SOUNDS\RESPAWNITEM');
188 g_Sound_CreateWADEx('SOUND_ITEM_GETRULEZ', GameWAD+':SOUNDS\GETRULEZ');
189 g_Sound_CreateWADEx('SOUND_ITEM_GETWEAPON', GameWAD+':SOUNDS\GETWEAPON');
190 g_Sound_CreateWADEx('SOUND_ITEM_GETITEM', GameWAD+':SOUNDS\GETITEM');
192 freeIds := TIdPool.Create();
193 end;
196 procedure g_Items_FreeData();
197 begin
198 e_WriteLog('Releasing items data...', TMsgType.Notify);
200 g_Sound_Delete('SOUND_ITEM_RESPAWNITEM');
201 g_Sound_Delete('SOUND_ITEM_GETRULEZ');
202 g_Sound_Delete('SOUND_ITEM_GETWEAPON');
203 g_Sound_Delete('SOUND_ITEM_GETITEM');
205 freeIds.Free();
206 freeIds := nil;
207 end;
210 procedure releaseItem (idx: Integer);
211 var
212 it: PItem;
213 begin
214 if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('releaseItem: invalid item id');
215 if not freeIds.hasAlloced[LongWord(idx)] then raise Exception.Create('releaseItem: trying to release unallocated item (0)');
216 it := @ggItems[idx];
217 if not it.slotIsUsed then raise Exception.Create('releaseItem: trying to release unallocated item (1)');
218 if (it.arrIdx <> idx) then raise Exception.Create('releaseItem: arrIdx inconsistency');
219 it.slotIsUsed := false;
220 if (it.Animation <> nil) then
221 begin
222 it.Animation.Free();
223 it.Animation := nil;
224 end;
225 it.alive := False;
226 it.SpawnTrigger := -1;
227 it.ItemType := ITEM_NONE;
228 freeIds.release(LongWord(idx));
229 end;
232 procedure growItemArrayTo (newsz: Integer);
233 var
234 i, olen: Integer;
235 it: PItem;
236 begin
237 if (newsz < Length(ggItems)) then exit;
238 // no free slots
239 olen := Length(ggItems);
240 SetLength(ggItems, newsz);
241 for i := olen to High(ggItems) do
242 begin
243 it := @ggItems[i];
244 it.slotIsUsed := false;
245 it.arrIdx := i;
246 it.ItemType := ITEM_NONE;
247 it.Animation := nil;
248 it.alive := false;
249 it.SpawnTrigger := -1;
250 it.Respawnable := false;
251 //if not freeIds.hasFree[LongWord(i)] then raise Exception.Create('internal error in item idx manager');
252 end;
253 end;
256 function allocItem (): DWORD;
257 begin
258 result := freeIds.alloc();
259 if (result >= Length(ggItems)) then growItemArrayTo(Integer(result)+64);
260 if (Integer(result) > High(ggItems)) then raise Exception.Create('allocItem: freeid list corrupted');
261 if (ggItems[result].arrIdx <> Integer(result)) then raise Exception.Create('allocItem: arrIdx inconsistency');
262 end;
265 // it will be slow if the slot is free (we have to rebuild the heap)
266 function wantItemSlot (slot: Integer): Integer;
267 var
268 olen: Integer;
269 it: PItem;
270 begin
271 if (slot < 0) or (slot > $0fffffff) then raise Exception.Create('wantItemSlot: bad item slot request');
272 // do we need to grow item storate?
273 olen := Length(ggItems);
274 if (slot >= olen) then growItemArrayTo(slot+64);
276 it := @ggItems[slot];
277 if not it.slotIsUsed then
278 begin
279 freeIds.alloc(LongWord(slot));
280 end
281 else
282 begin
283 if not freeIds.hasAlloced[slot] then raise Exception.Create('wantItemSlot: internal error in item idx manager');
284 end;
285 it.slotIsUsed := false;
287 result := slot;
288 end;
291 // ////////////////////////////////////////////////////////////////////////// //
292 procedure g_Items_Init ();
293 var
294 a, b: Integer;
295 begin
296 if gMapInfo.Height > gPlayerScreenSize.Y then a := gMapInfo.Height-gPlayerScreenSize.Y else a := gMapInfo.Height;
297 if gMapInfo.Width > gPlayerScreenSize.X then b := gMapInfo.Width-gPlayerScreenSize.X else b := gMapInfo.Width;
298 gMaxDist := Trunc(Hypot(a, b));
299 end;
302 procedure g_Items_Free ();
303 var
304 i: Integer;
305 begin
306 if (ggItems <> nil) then
307 begin
308 for i := 0 to High(ggItems) do ggItems[i].Animation.Free();
309 ggItems := nil;
310 end;
311 freeIds.clear();
312 end;
315 function g_Items_Create (X, Y: Integer; ItemType: Byte;
316 Fall, Respawnable: Boolean; AdjCoord: Boolean = False; ForcedID: Integer = -1): DWORD;
317 var
318 find_id: DWORD;
319 it: PItem;
320 begin
321 if ForcedID < 0 then find_id := allocItem() else find_id := wantItemSlot(ForcedID);
323 //{$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('allocated item #%d', [Integer(find_id)]), MSG_NOTIFY);{$ENDIF}
325 it := @ggItems[find_id];
327 if (it.arrIdx <> Integer(find_id)) then raise Exception.Create('g_Items_Create: arrIdx inconsistency');
328 //it.arrIdx := find_id;
329 it.slotIsUsed := true;
331 it.ItemType := ItemType;
332 it.Respawnable := Respawnable;
333 it.InitX := X;
334 it.InitY := Y;
335 it.RespawnTime := 0;
336 it.Fall := Fall;
337 it.alive := True;
338 it.QuietRespawn := False;
339 it.dropped := false;
341 g_Obj_Init(@it.Obj);
342 it.Obj.X := X;
343 it.Obj.Y := Y;
344 it.Obj.Rect.Width := ITEMSIZE[ItemType][0];
345 it.Obj.Rect.Height := ITEMSIZE[ItemType][1];
347 it.Animation := nil;
348 it.SpawnTrigger := -1;
350 // Êîîðäèíàòû îòíîñèòåëüíî öåíòðà íèæíåãî ðåáðà
351 if AdjCoord then
352 begin
353 with it^ do
354 begin
355 Obj.X := X - (Obj.Rect.Width div 2);
356 Obj.Y := Y - Obj.Rect.Height;
357 InitX := Obj.X;
358 InitY := Obj.Y;
359 end;
360 end;
362 it.Obj.oldX := it.Obj.X;
363 it.Obj.oldY := it.Obj.Y;
365 // Óñòàíîâêà àíèìàöèè
366 case it.ItemType of
367 ITEM_ARMOR_GREEN: it.Animation := TAnimationState.Create(True, 20, 3);
368 ITEM_ARMOR_BLUE: it.Animation := TAnimationState.Create(True, 20, 3);
369 ITEM_JETPACK: it.Animation := TAnimationState.Create(True, 15, 3);
370 ITEM_SPHERE_BLUE: it.Animation := TAnimationState.Create(True, 15, 4);
371 ITEM_SPHERE_WHITE: it.Animation := TAnimationState.Create(True, 20, 4);
372 ITEM_INVUL: it.Animation := TAnimationState.Create(True, 20, 4);
373 ITEM_INVIS: it.Animation := TAnimationState.Create(True, 20, 4);
374 ITEM_BOTTLE: it.Animation := TAnimationState.Create(True, 20, 4);
375 ITEM_HELMET: it.Animation := TAnimationState.Create(True, 20, 4);
376 end;
378 it.positionChanged();
380 result := find_id;
381 end;
383 procedure g_Items_PreUpdate ();
384 var
385 i: Integer;
386 begin
387 if (ggItems = nil) then Exit;
388 for i := 0 to High(ggItems) do
389 if (ggItems[i].ItemType <> ITEM_NONE) and ggItems[i].slotIsUsed then
390 begin
391 ggItems[i].Obj.oldX := ggItems[i].Obj.X;
392 ggItems[i].Obj.oldY := ggItems[i].Obj.Y;
393 end;
394 end;
396 procedure g_Items_Update ();
397 var
398 i, j, k: Integer;
399 m, ItemRespawnTime: Word;
400 r, nxt: Boolean;
401 begin
402 if (ggItems = nil) then exit;
404 // respawn items in 15 seconds regardless of settings during warmup
405 ItemRespawnTime := IfThen(gLMSRespawn = LMS_RESPAWN_NONE, gGameSettings.ItemRespawnTime, 15);
407 for i := 0 to High(ggItems) do
408 begin
409 if (ggItems[i].ItemType = ITEM_NONE) then continue;
410 if not ggItems[i].slotIsUsed then continue; // just in case
412 with ggItems[i] do
413 begin
414 nxt := False;
416 if alive then
417 begin
418 if Fall then
419 begin
420 m := g_Obj_Move(@Obj, True, True);
421 positionChanged(); // this updates spatial accelerators
423 // Ñîïðîòèâëåíèå âîçäóõà
424 if gTime mod (GAME_TICK*2) = 0 then Obj.Vel.X := z_dec(Obj.Vel.X, 1);
426 // Åñëè âûïàë çà êàðòó
427 if WordBool(m and MOVE_FALLOUT) then
428 begin
429 if SpawnTrigger = -1 then
430 begin
431 g_Items_Pick(i);
432 end
433 else
434 begin
435 g_Items_Remove(i);
436 if g_Game_IsServer and g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
437 end;
438 continue;
439 end;
440 end;
442 // Åñëè èãðîêè ïîáëèçîñòè
443 if (gPlayers <> nil) then
444 begin
445 j := Random(Length(gPlayers))-1;
447 for k := 0 to High(gPlayers) do
448 begin
449 Inc(j);
450 if j > High(gPlayers) then j := 0;
452 if (gPlayers[j] <> nil) and gPlayers[j].alive and g_Obj_Collide(@gPlayers[j].Obj, @Obj) then
453 begin
454 if g_Game_IsClient then continue;
456 if not gPlayers[j].PickItem(ItemType, Respawnable, r) then continue;
458 if g_Game_IsNet then MH_SEND_PlayerStats(gPlayers[j].UID);
461 Doom 2D: Original:
462 1. I_NONE,I_CLIP,I_SHEL,I_ROCKET,I_CELL,I_AMMO,I_SBOX,I_RBOX,I_CELP,I_BPACK,I_CSAW,I_SGUN,I_SGUN2,I_MGUN,I_LAUN,I_PLAS,I_BFG,I_GUN2
463 +2. I_MEGA,I_INVL,I_SUPER
464 3. I_STIM,I_MEDI,I_ARM1,I_ARM2,I_AQUA,I_KEYR,I_KEYG,I_KEYB,I_SUIT,I_RTORCH,I_GTORCH,I_BTORCH,I_GOR1,I_FCAN
466 g_Items_EmitPickupSoundAt(i, gPlayers[j].Obj.X, gPlayers[j].Obj.Y);
468 // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòüñÿ ñ äðóãèì èãðîêîì
469 if r then
470 begin
471 if not (Respawnable and (ItemRespawnTime > 0)) then
472 g_Items_Remove(i)
473 else
474 g_Items_Pick(i);
475 if g_Game_IsNet then MH_SEND_ItemDestroy(False, i);
476 nxt := True;
477 break;
478 end;
479 end;
480 end;
481 end;
483 if nxt then continue;
484 end;
486 if Respawnable and g_Game_IsServer then
487 begin
488 DecMin(RespawnTime, 0);
489 if (RespawnTime = 0) and (not alive) then
490 begin
491 if not QuietRespawn then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', InitX, InitY);
492 {$IFDEF ENABLE_GFX}
493 g_GFX_QueueEffect(
494 R_GFX_ITEM_RESPAWN,
495 InitX + (Obj.Rect.Width div 2) - 16,
496 InitY + (Obj.Rect.Height div 2) - 16
497 );
498 {$ENDIF}
499 Obj.oldX := InitX;
500 Obj.oldY := InitY;
501 Obj.X := InitX;
502 Obj.Y := InitY;
503 Obj.Vel.X := 0;
504 Obj.Vel.Y := 0;
505 Obj.Accel.X := 0;
506 Obj.Accel.Y := 0;
507 positionChanged(); // this updates spatial accelerators
509 alive := true;
511 if g_Game_IsNet then MH_SEND_ItemSpawn(QuietRespawn, i);
512 QuietRespawn := false;
513 end;
514 end;
516 if (Animation <> nil) then Animation.Update();
517 end;
518 end;
519 end;
521 procedure g_Items_SetDrop (ID: DWORD);
522 begin
523 if (ID < Length(ggItems)) then
524 begin
525 ggItems[ID].dropped := true;
526 end;
527 end;
530 procedure g_Items_Pick (ID: DWORD);
531 begin
532 if (ID < Length(ggItems)) then
533 begin
534 ggItems[ID].Obj.oldX := ggItems[ID].Obj.X;
535 ggItems[ID].Obj.oldY := ggItems[ID].Obj.Y;
536 ggItems[ID].alive := false;
537 ggItems[ID].RespawnTime := IfThen(gLMSRespawn = LMS_RESPAWN_NONE, gGameSettings.ItemRespawnTime, 15) * 36;
538 end;
539 end;
542 procedure g_Items_Remove (ID: DWORD);
543 var
544 it: PItem;
545 trig: Integer;
546 begin
547 if not g_Items_ValidId(ID) then
548 begin
549 //writeln('g_Items_Remove: invalid item id: ', ID);
550 raise Exception.Create('g_Items_Remove: invalid item id');
551 //exit;
552 end;
554 it := @ggItems[ID];
555 if (it.arrIdx <> Integer(ID)) then raise Exception.Create('g_Items_Remove: arrIdx desync');
557 it.Obj.oldX := it.Obj.X;
558 it.Obj.oldY := it.Obj.Y;
559 trig := it.SpawnTrigger;
561 releaseItem(ID);
563 if (trig > -1) then g_Triggers_DecreaseSpawner(trig);
564 end;
567 procedure g_Items_SaveState (st: TStream);
568 var
569 count, i: Integer;
570 tt: Byte;
571 begin
572 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ïðåäìåòîâ
573 count := 0;
574 for i := 0 to High(ggItems) do if (ggItems[i].ItemType <> ITEM_NONE) and (ggItems[i].slotIsUsed) then Inc(count);
576 // Êîëè÷åñòâî ïðåäìåòîâ
577 utils.writeInt(st, LongInt(count));
578 if (count = 0) then exit;
580 for i := 0 to High(ggItems) do
581 begin
582 if (ggItems[i].ItemType <> ITEM_NONE) and (ggItems[i].slotIsUsed) then
583 begin
584 // Ñèãíàòóðà ïðåäìåòà
585 utils.writeSign(st, 'ITEM');
586 utils.writeInt(st, Byte(0));
587 // Òèï ïðåäìåòà
588 tt := ggItems[i].ItemType;
589 if ggItems[i].dropped then tt := tt or $80;
590 utils.writeInt(st, Byte(tt));
591 // Åñòü ëè ðåñïàóí
592 utils.writeBool(st, ggItems[i].Respawnable);
593 // Êîîðäèíàòû ðåñïóíà
594 utils.writeInt(st, LongInt(ggItems[i].InitX));
595 utils.writeInt(st, LongInt(ggItems[i].InitY));
596 // Âðåìÿ äî ðåñïàóíà
597 utils.writeInt(st, Word(ggItems[i].RespawnTime));
598 // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
599 utils.writeBool(st, ggItems[i].alive);
600 // Ìîæåò ëè îí ïàäàòü
601 utils.writeBool(st, ggItems[i].Fall);
602 // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
603 utils.writeInt(st, LongInt(ggItems[i].SpawnTrigger));
604 // Îáúåêò ïðåäìåòà
605 Obj_SaveState(st, @ggItems[i].Obj);
606 end;
607 end;
608 end;
611 procedure g_Items_LoadState (st: TStream);
612 var
613 count, i, a: Integer;
614 b: Byte;
615 begin
616 assert(st <> nil);
618 g_Items_Free();
620 // Êîëè÷åñòâî ïðåäìåòîâ
621 count := utils.readLongInt(st);
622 if (count = 0) then exit;
623 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid number of items');
625 for a := 0 to count-1 do
626 begin
627 // Ñèãíàòóðà ïðåäìåòà
628 if not utils.checkSign(st, 'ITEM') then raise XStreamError.Create('invalid item signature');
629 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid item version');
630 // Òèï ïðåäìåòà
631 b := utils.readByte(st); // bit7=1: monster drop
632 // Ñîçäàåì ïðåäìåò
633 i := g_Items_Create(0, 0, b and $7F, False, False);
634 if ((b and $80) <> 0) then g_Items_SetDrop(i);
635 // Åñòü ëè ðåñïàóí
636 ggItems[i].Respawnable := utils.readBool(st);
637 // Êîîðäèíàòû ðåñïóíà
638 ggItems[i].InitX := utils.readLongInt(st);
639 ggItems[i].InitY := utils.readLongInt(st);
640 // Âðåìÿ äî ðåñïàóíà
641 ggItems[i].RespawnTime := utils.readWord(st);
642 // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
643 ggItems[i].alive := utils.readBool(st);
644 // Ìîæåò ëè îí ïàäàòü
645 ggItems[i].Fall := utils.readBool(st);
646 // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
647 ggItems[i].SpawnTrigger := utils.readLongInt(st);
648 // Îáúåêò ïðåäìåòà
649 Obj_LoadState(@ggItems[i].Obj, st);
650 end;
651 end;
654 procedure g_Items_RestartRound ();
655 var
656 i: Integer;
657 it: PItem;
658 begin
659 for i := 0 to High(ggItems) do
660 begin
661 it := @ggItems[i];
662 it.Obj.oldX := it.Obj.X;
663 it.Obj.oldY := it.Obj.Y;
664 if not it.slotIsUsed then continue;
665 if it.Respawnable and (it.ItemType <> ITEM_NONE) then
666 begin
667 it.QuietRespawn := True;
668 it.RespawnTime := 0;
669 end
670 else
671 begin
672 g_Items_Remove(i);
673 if g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
674 end;
675 end;
676 end;
679 function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false): Boolean;
680 var
681 idx: Integer;
682 begin
683 result := false;
684 if (ggItems = nil) or not assigned(cb) then exit;
686 if backwards then
687 begin
688 for idx := High(ggItems) downto 0 do
689 begin
690 if ggItems[idx].alive and ggItems[idx].slotIsUsed then
691 begin
692 result := cb(@ggItems[idx]);
693 if result then exit;
694 end;
695 end;
696 end
697 else
698 begin
699 for idx := 0 to High(ggItems) do
700 begin
701 if ggItems[idx].alive and ggItems[idx].slotIsUsed then
702 begin
703 result := cb(@ggItems[idx]);
704 if result then exit;
705 end;
706 end;
707 end;
708 end;
711 // ////////////////////////////////////////////////////////////////////////// //
712 procedure g_Items_EmitPickupSound (idx: Integer);
713 var
714 it: PItem;
715 begin
716 if not g_Items_ValidId(idx) then exit;
717 it := @ggItems[idx];
718 g_Items_EmitPickupSoundAt(idx, it.Obj.X, it.Obj.Y);
719 end;
721 procedure g_Items_EmitPickupSoundAt (idx, x, y: Integer);
722 var
723 it: PItem;
724 begin
725 if not g_Items_ValidId(idx) then exit;
727 it := @ggItems[idx];
728 if gSoundEffectsDF then
729 begin
730 if it.ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL,
731 ITEM_INVIS, ITEM_MEDKIT_BLACK, ITEM_JETPACK] then
732 begin
733 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', x, y);
734 end
735 else if it.ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
736 ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
737 ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER,
738 ITEM_AMMO_BACKPACK] then
739 begin
740 g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON', x, y);
741 end
742 else
743 begin
744 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', x, y);
745 end;
746 end
747 else
748 begin
749 if it.ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_SUIT,
750 ITEM_MEDKIT_BLACK, ITEM_INVUL, ITEM_INVIS, ITEM_JETPACK] then
751 begin
752 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', x, y);
753 end
754 else if it.ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
755 ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
756 ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER] then
757 begin
758 g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON', x, y);
759 end
760 else
761 begin
762 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', x, y);
763 end;
764 end;
765 end;
768 procedure g_Items_AddDynLights();
769 var
770 f: Integer;
771 it: PItem;
772 begin
773 for f := 0 to High(ggItems) do
774 begin
775 it := @ggItems[f];
776 if not it.alive then continue;
777 case it.ItemType of
778 ITEM_KEY_RED: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 24, 1.0, 0.0, 0.0, 0.6);
779 ITEM_KEY_GREEN: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 24, 0.0, 1.0, 0.0, 0.6);
780 ITEM_KEY_BLUE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 24, 0.0, 0.0, 1.0, 0.6);
781 ITEM_ARMOR_GREEN: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 42, 0.0, 1.0, 0.0, 0.6);
782 ITEM_ARMOR_BLUE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 42, 0.0, 0.0, 1.0, 0.6);
783 ITEM_JETPACK: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32, 1.0, 1.0, 1.0, 0.6);
784 ITEM_SPHERE_BLUE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32, 0.0, 1.0, 0.0, 0.6);
785 ITEM_SPHERE_WHITE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32, 1.0, 1.0, 1.0, 0.6);
786 ITEM_INVUL: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32, 1.0, 0.0, 0.0, 0.6);
787 ITEM_INVIS: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32, 1.0, 1.0, 0.0, 0.6);
788 ITEM_BOTTLE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 16, 0.0, 0.0, 0.8, 0.6);
789 ITEM_HELMET: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 16, 0.0, 0.8, 0.0, 0.6);
790 end;
791 end;
792 end;
795 end.