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
44 NeedSend: Boolean;
46 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
47 procedure getMapBox (out x, y, w, h: Integer); inline;
48 procedure moveBy (dx, dy: Integer); inline;
50 property used: Boolean read slotIsUsed;
51 property myid: Integer read arrIdx;
52 end;
54 procedure g_Items_LoadData();
55 procedure g_Items_FreeData();
56 procedure g_Items_Init();
57 procedure g_Items_Free();
58 function g_Items_Create(X, Y: Integer; ItemType: Byte;
59 Fall, Respawnable: Boolean; AdjCoord: Boolean = False; ForcedID: Integer = -1): DWORD;
60 procedure g_Items_SetDrop (ID: DWORD);
61 procedure g_Items_PreUpdate();
62 procedure g_Items_Update();
63 procedure g_Items_Pick(ID: DWORD);
64 procedure g_Items_Remove(ID: DWORD);
65 procedure g_Items_SaveState (st: TStream);
66 procedure g_Items_LoadState (st: TStream);
68 procedure g_Items_RestartRound ();
70 function g_Items_ValidId (idx: Integer): Boolean; inline;
71 function g_Items_ByIdx (idx: Integer): PItem;
72 function g_Items_ObjByIdx (idx: Integer): PObj;
74 procedure g_Items_EmitPickupSound (idx: Integer); // at item position
75 procedure g_Items_EmitPickupSoundAt (idx, x, y: Integer);
77 procedure g_Items_AddDynLights();
80 type
81 TItemEachAliveCB = function (it: PItem): Boolean is nested; // return `true` to stop
83 function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false): Boolean;
84 function g_Items_NextAlive (startIdx: Integer): PItem;
86 var
87 gMaxDist: Integer = 1; // for sounds
89 var (* private state *)
90 ggItems: Array of TItem = nil;
92 implementation
94 uses
95 {$IFDEF ENABLE_GFX}
96 g_gfx,
97 {$ENDIF}
98 Math,
99 g_basic, g_sound, g_map,
100 g_game, g_triggers, g_console, g_player, g_net, g_netmsg,
101 e_log, g_options,
102 g_grid, binheap, idpool, utils, xstreams
105 // ////////////////////////////////////////////////////////////////////////// //
106 var
107 freeIds: TIdPool = nil;
110 // ////////////////////////////////////////////////////////////////////////// //
111 function g_Items_ValidId (idx: Integer): Boolean; inline;
112 begin
113 result := false;
114 if (idx < 0) or (idx > High(ggItems)) then exit;
115 if not ggItems[idx].slotIsUsed then exit;
116 result := true;
117 end;
120 function g_Items_ByIdx (idx: Integer): PItem;
121 begin
122 if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('g_ItemObjByIdx: invalid index');
123 result := @ggItems[idx];
124 if not result.slotIsUsed then raise Exception.Create('g_ItemObjByIdx: requested inexistent item');
125 end;
128 function g_Items_ObjByIdx (idx: Integer): PObj;
129 begin
130 if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('g_ItemObjByIdx: invalid index');
131 if not ggItems[idx].slotIsUsed then raise Exception.Create('g_ItemObjByIdx: requested inexistent item');
132 result := @ggItems[idx].Obj;
133 end;
136 // ////////////////////////////////////////////////////////////////////////// //
137 procedure TItem.positionChanged ();
138 begin
139 NeedSend := NeedSend or (Obj.X <> Obj.oldX) or (Obj.Y <> Obj.oldY);
140 end;
142 procedure TItem.getMapBox (out x, y, w, h: Integer); inline;
143 begin
144 x := Obj.X+Obj.Rect.X;
145 y := Obj.Y+Obj.Rect.Y;
146 w := Obj.Rect.Width;
147 h := Obj.Rect.Height;
148 end;
150 procedure TItem.moveBy (dx, dy: Integer); inline;
151 begin
152 if (dx <> 0) or (dy <> 0) then
153 begin
154 Obj.X += dx;
155 Obj.Y += dy;
156 positionChanged();
157 end;
158 end;
160 // ////////////////////////////////////////////////////////////////////////// //
161 const
162 ITEM_SIGNATURE = $4D455449; // 'ITEM'
164 ITEMSIZE: Array [ITEM_MEDKIT_SMALL..ITEM_MAX] of Array [0..1] of Byte =
165 (((14), (15)), // MEDKIT_SMALL
166 ((28), (19)), // MEDKIT_LARGE
167 ((28), (19)), // MEDKIT_BLACK
168 ((31), (16)), // ARMOR_GREEN
169 ((31), (16)), // ARMOR_BLUE
170 ((25), (25)), // SPHERE_BLUE
171 ((25), (25)), // SPHERE_WHITE
172 ((24), (47)), // SUIT
173 ((14), (27)), // OXYGEN
174 ((25), (25)), // INVUL
175 ((62), (24)), // WEAPON_SAW
176 ((63), (12)), // WEAPON_SHOTGUN1
177 ((54), (13)), // WEAPON_SHOTGUN2
178 ((54), (16)), // WEAPON_CHAINGUN
179 ((62), (16)), // WEAPON_ROCKETLAUNCHER
180 ((54), (16)), // WEAPON_PLASMA
181 ((61), (36)), // WEAPON_BFG
182 ((54), (16)), // WEAPON_SUPERPULEMET
183 (( 9), (11)), // AMMO_BULLETS
184 ((28), (16)), // AMMO_BULLETS_BOX
185 ((15), ( 7)), // AMMO_SHELLS
186 ((32), (12)), // AMMO_SHELLS_BOX
187 ((12), (27)), // AMMO_ROCKET
188 ((54), (21)), // AMMO_ROCKET_BOX
189 ((15), (12)), // AMMO_CELL
190 ((32), (21)), // AMMO_CELL_BIG
191 ((22), (29)), // AMMO_BACKPACK
192 ((16), (16)), // KEY_RED
193 ((16), (16)), // KEY_GREEN
194 ((16), (16)), // KEY_BLUE
195 (( 1), ( 1)), // WEAPON_KASTET
196 ((43), (16)), // WEAPON_PISTOL
197 ((14), (18)), // BOTTLE
198 ((16), (15)), // HELMET
199 ((32), (24)), // JETPACK
200 ((25), (25)), // INVIS
201 ((53), (20)), // WEAPON_FLAMETHROWER
202 ((13), (20))); // AMMO_FUELCAN
204 procedure g_Items_LoadData();
205 begin
206 e_WriteLog('Loading items data...', TMsgType.Notify);
208 g_Sound_CreateWADEx('SOUND_ITEM_RESPAWNITEM', GameWAD+':SOUNDS\RESPAWNITEM');
209 g_Sound_CreateWADEx('SOUND_ITEM_GETRULEZ', GameWAD+':SOUNDS\GETRULEZ');
210 g_Sound_CreateWADEx('SOUND_ITEM_GETWEAPON', GameWAD+':SOUNDS\GETWEAPON');
211 g_Sound_CreateWADEx('SOUND_ITEM_GETITEM', GameWAD+':SOUNDS\GETITEM');
213 freeIds := TIdPool.Create();
214 end;
217 procedure g_Items_FreeData();
218 begin
219 e_WriteLog('Releasing items data...', TMsgType.Notify);
221 g_Sound_Delete('SOUND_ITEM_RESPAWNITEM');
222 g_Sound_Delete('SOUND_ITEM_GETRULEZ');
223 g_Sound_Delete('SOUND_ITEM_GETWEAPON');
224 g_Sound_Delete('SOUND_ITEM_GETITEM');
226 freeIds.Free();
227 freeIds := nil;
228 end;
231 procedure releaseItem (idx: Integer);
232 var
233 it: PItem;
234 begin
235 if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('releaseItem: invalid item id');
236 if not freeIds.hasAlloced[LongWord(idx)] then raise Exception.Create('releaseItem: trying to release unallocated item (0)');
237 it := @ggItems[idx];
238 if not it.slotIsUsed then raise Exception.Create('releaseItem: trying to release unallocated item (1)');
239 if (it.arrIdx <> idx) then raise Exception.Create('releaseItem: arrIdx inconsistency');
240 it.slotIsUsed := false;
241 if (it.Animation <> nil) then
242 begin
243 it.Animation.Free();
244 it.Animation := nil;
245 end;
246 it.alive := False;
247 it.SpawnTrigger := -1;
248 it.ItemType := ITEM_NONE;
249 it.NeedSend := false;
250 freeIds.release(LongWord(idx));
251 end;
254 procedure growItemArrayTo (newsz: Integer);
255 var
256 i, olen: Integer;
257 it: PItem;
258 begin
259 if (newsz < Length(ggItems)) then exit;
260 // no free slots
261 olen := Length(ggItems);
262 SetLength(ggItems, newsz);
263 for i := olen to High(ggItems) do
264 begin
265 it := @ggItems[i];
266 it.slotIsUsed := false;
267 it.arrIdx := i;
268 it.ItemType := ITEM_NONE;
269 it.Animation := nil;
270 it.alive := false;
271 it.SpawnTrigger := -1;
272 it.Respawnable := false;
273 it.NeedSend := false;
274 //if not freeIds.hasFree[LongWord(i)] then raise Exception.Create('internal error in item idx manager');
275 end;
276 end;
279 function allocItem (): DWORD;
280 begin
281 result := freeIds.alloc();
282 if (result >= Length(ggItems)) then growItemArrayTo(Integer(result)+64);
283 if (Integer(result) > High(ggItems)) then raise Exception.Create('allocItem: freeid list corrupted');
284 if (ggItems[result].arrIdx <> Integer(result)) then raise Exception.Create('allocItem: arrIdx inconsistency');
285 end;
288 // it will be slow if the slot is free (we have to rebuild the heap)
289 function wantItemSlot (slot: Integer): Integer;
290 var
291 olen: Integer;
292 it: PItem;
293 begin
294 if (slot < 0) or (slot > $0fffffff) then raise Exception.Create('wantItemSlot: bad item slot request');
295 // do we need to grow item storate?
296 olen := Length(ggItems);
297 if (slot >= olen) then growItemArrayTo(slot+64);
299 it := @ggItems[slot];
300 if not it.slotIsUsed then
301 begin
302 freeIds.alloc(LongWord(slot));
303 end
304 else
305 begin
306 if not freeIds.hasAlloced[slot] then raise Exception.Create('wantItemSlot: internal error in item idx manager');
307 end;
308 it.slotIsUsed := false;
310 result := slot;
311 end;
314 // ////////////////////////////////////////////////////////////////////////// //
315 procedure g_Items_Init ();
316 var
317 a, b: Integer;
318 begin
319 if gMapInfo.Height > gPlayerScreenSize.Y then a := gMapInfo.Height-gPlayerScreenSize.Y else a := gMapInfo.Height;
320 if gMapInfo.Width > gPlayerScreenSize.X then b := gMapInfo.Width-gPlayerScreenSize.X else b := gMapInfo.Width;
321 gMaxDist := Trunc(Hypot(a, b));
322 end;
325 procedure g_Items_Free ();
326 var
327 i: Integer;
328 begin
329 if (ggItems <> nil) then
330 begin
331 for i := 0 to High(ggItems) do ggItems[i].Animation.Free();
332 ggItems := nil;
333 end;
334 freeIds.clear();
335 end;
338 function g_Items_Create (X, Y: Integer; ItemType: Byte;
339 Fall, Respawnable: Boolean; AdjCoord: Boolean = False; ForcedID: Integer = -1): DWORD;
340 var
341 find_id: DWORD;
342 it: PItem;
343 begin
344 if ForcedID < 0 then find_id := allocItem() else find_id := wantItemSlot(ForcedID);
346 //{$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('allocated item #%d', [Integer(find_id)]), MSG_NOTIFY);{$ENDIF}
348 it := @ggItems[find_id];
350 if (it.arrIdx <> Integer(find_id)) then raise Exception.Create('g_Items_Create: arrIdx inconsistency');
351 //it.arrIdx := find_id;
352 it.slotIsUsed := true;
354 it.ItemType := ItemType;
355 it.Respawnable := Respawnable;
356 it.InitX := X;
357 it.InitY := Y;
358 it.RespawnTime := 0;
359 it.Fall := Fall;
360 it.alive := True;
361 it.QuietRespawn := False;
362 it.dropped := false;
363 it.NeedSend := false;
365 g_Obj_Init(@it.Obj);
366 it.Obj.X := X;
367 it.Obj.Y := Y;
368 it.Obj.Rect.Width := ITEMSIZE[ItemType][0];
369 it.Obj.Rect.Height := ITEMSIZE[ItemType][1];
371 it.Animation := nil;
372 it.SpawnTrigger := -1;
374 // Êîîðäèíàòû îòíîñèòåëüíî öåíòðà íèæíåãî ðåáðà
375 if AdjCoord then
376 begin
377 with it^ do
378 begin
379 Obj.X := X - (Obj.Rect.Width div 2);
380 Obj.Y := Y - Obj.Rect.Height;
381 InitX := Obj.X;
382 InitY := Obj.Y;
383 end;
384 end;
386 it.Obj.oldX := it.Obj.X;
387 it.Obj.oldY := it.Obj.Y;
389 // Óñòàíîâêà àíèìàöèè
390 case it.ItemType of
391 ITEM_ARMOR_GREEN: it.Animation := TAnimationState.Create(True, 20, 3);
392 ITEM_ARMOR_BLUE: it.Animation := TAnimationState.Create(True, 20, 3);
393 ITEM_JETPACK: it.Animation := TAnimationState.Create(True, 15, 3);
394 ITEM_SPHERE_BLUE: it.Animation := TAnimationState.Create(True, 15, 4);
395 ITEM_SPHERE_WHITE: it.Animation := TAnimationState.Create(True, 20, 4);
396 ITEM_INVUL: it.Animation := TAnimationState.Create(True, 20, 4);
397 ITEM_INVIS: it.Animation := TAnimationState.Create(True, 20, 4);
398 ITEM_BOTTLE: it.Animation := TAnimationState.Create(True, 20, 4);
399 ITEM_HELMET: it.Animation := TAnimationState.Create(True, 20, 4);
400 end;
402 it.positionChanged();
404 result := find_id;
405 end;
407 procedure g_Items_PreUpdate ();
408 var
409 i: Integer;
410 begin
411 if (ggItems = nil) then Exit;
412 for i := 0 to High(ggItems) do
413 if (ggItems[i].ItemType <> ITEM_NONE) and ggItems[i].slotIsUsed then
414 begin
415 ggItems[i].Obj.oldX := ggItems[i].Obj.X;
416 ggItems[i].Obj.oldY := ggItems[i].Obj.Y;
417 end;
418 end;
420 procedure g_Items_Update ();
421 var
422 i, j, k: Integer;
423 m, ItemRespawnTime: Word;
424 r, nxt: Boolean;
425 begin
426 if (ggItems = nil) then exit;
428 // respawn items in 15 seconds regardless of settings during warmup
429 ItemRespawnTime := IfThen(gLMSRespawn = LMS_RESPAWN_NONE, gGameSettings.ItemRespawnTime, 15);
431 for i := 0 to High(ggItems) do
432 begin
433 if (ggItems[i].ItemType = ITEM_NONE) then continue;
434 if not ggItems[i].slotIsUsed then continue; // just in case
436 with ggItems[i] do
437 begin
438 nxt := False;
440 if alive then
441 begin
442 if Fall then
443 begin
444 m := g_Obj_Move(@Obj, True, True);
445 positionChanged(); // this updates spatial accelerators
447 // Ñîïðîòèâëåíèå âîçäóõà
448 if gTime mod (GAME_TICK*2) = 0 then Obj.Vel.X := z_dec(Obj.Vel.X, 1);
450 // Åñëè âûïàë çà êàðòó
451 if WordBool(m and MOVE_FALLOUT) then
452 begin
453 if SpawnTrigger = -1 then
454 begin
455 g_Items_Pick(i);
456 end
457 else
458 begin
459 g_Items_Remove(i);
460 if g_Game_IsServer and g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
461 end;
462 continue;
463 end;
464 end;
466 // Åñëè èãðîêè ïîáëèçîñòè
467 if (gPlayers <> nil) then
468 begin
469 j := Random(Length(gPlayers))-1;
471 for k := 0 to High(gPlayers) do
472 begin
473 Inc(j);
474 if j > High(gPlayers) then j := 0;
476 if (gPlayers[j] <> nil) and gPlayers[j].alive and g_Obj_Collide(@gPlayers[j].Obj, @Obj) then
477 begin
478 if g_Game_IsClient then continue;
480 if not gPlayers[j].PickItem(ItemType, Respawnable, r) then continue;
482 if g_Game_IsNet then MH_SEND_PlayerStats(gPlayers[j].UID);
485 Doom 2D: Original:
486 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
487 +2. I_MEGA,I_INVL,I_SUPER
488 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
490 g_Items_EmitPickupSoundAt(i, gPlayers[j].Obj.X, gPlayers[j].Obj.Y);
492 // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòüñÿ ñ äðóãèì èãðîêîì
493 if r then
494 begin
495 if not (Respawnable and (ItemRespawnTime > 0)) then
496 g_Items_Remove(i)
497 else
498 g_Items_Pick(i);
499 if g_Game_IsNet then MH_SEND_ItemDestroy(False, i);
500 nxt := True;
501 break;
502 end;
503 end;
504 end;
505 end;
507 if nxt then continue;
508 end;
510 if Respawnable and g_Game_IsServer then
511 begin
512 DecMin(RespawnTime, 0);
513 if (RespawnTime = 0) and (not alive) then
514 begin
515 if not QuietRespawn then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', InitX, InitY);
516 {$IFDEF ENABLE_GFX}
517 g_GFX_QueueEffect(
518 R_GFX_ITEM_RESPAWN,
519 InitX + (Obj.Rect.Width div 2) - 16,
520 InitY + (Obj.Rect.Height div 2) - 16
521 );
522 {$ENDIF}
523 Obj.oldX := InitX;
524 Obj.oldY := InitY;
525 Obj.X := InitX;
526 Obj.Y := InitY;
527 Obj.Vel.X := 0;
528 Obj.Vel.Y := 0;
529 Obj.Accel.X := 0;
530 Obj.Accel.Y := 0;
531 positionChanged(); // this updates spatial accelerators
533 alive := true;
535 if g_Game_IsNet then MH_SEND_ItemSpawn(QuietRespawn, i);
536 QuietRespawn := false;
537 end;
538 end;
540 if (Animation <> nil) then Animation.Update();
541 end;
542 end;
543 end;
545 procedure g_Items_SetDrop (ID: DWORD);
546 begin
547 if (ID < Length(ggItems)) then
548 begin
549 ggItems[ID].dropped := true;
550 end;
551 end;
554 procedure g_Items_Pick (ID: DWORD);
555 begin
556 if (ID < Length(ggItems)) then
557 begin
558 ggItems[ID].Obj.oldX := ggItems[ID].Obj.X;
559 ggItems[ID].Obj.oldY := ggItems[ID].Obj.Y;
560 ggItems[ID].alive := false;
561 ggItems[ID].RespawnTime := IfThen(gLMSRespawn = LMS_RESPAWN_NONE, gGameSettings.ItemRespawnTime, 15) * 36;
562 end;
563 end;
566 procedure g_Items_Remove (ID: DWORD);
567 var
568 it: PItem;
569 trig: Integer;
570 begin
571 if not g_Items_ValidId(ID) then
572 begin
573 //writeln('g_Items_Remove: invalid item id: ', ID);
574 raise Exception.Create('g_Items_Remove: invalid item id');
575 //exit;
576 end;
578 it := @ggItems[ID];
579 if (it.arrIdx <> Integer(ID)) then raise Exception.Create('g_Items_Remove: arrIdx desync');
581 it.Obj.oldX := it.Obj.X;
582 it.Obj.oldY := it.Obj.Y;
583 trig := it.SpawnTrigger;
585 releaseItem(ID);
587 if (trig > -1) then g_Triggers_DecreaseSpawner(trig);
588 end;
591 procedure g_Items_SaveState (st: TStream);
592 var
593 count, i: Integer;
594 tt: Byte;
595 begin
596 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ïðåäìåòîâ
597 count := 0;
598 for i := 0 to High(ggItems) do if (ggItems[i].ItemType <> ITEM_NONE) and (ggItems[i].slotIsUsed) then Inc(count);
600 // Êîëè÷åñòâî ïðåäìåòîâ
601 utils.writeInt(st, LongInt(count));
602 if (count = 0) then exit;
604 for i := 0 to High(ggItems) do
605 begin
606 if (ggItems[i].ItemType <> ITEM_NONE) and (ggItems[i].slotIsUsed) then
607 begin
608 // Ñèãíàòóðà ïðåäìåòà
609 utils.writeSign(st, 'ITEM');
610 utils.writeInt(st, Byte(0));
611 // Òèï ïðåäìåòà
612 tt := ggItems[i].ItemType;
613 if ggItems[i].dropped then tt := tt or $80;
614 utils.writeInt(st, Byte(tt));
615 // Åñòü ëè ðåñïàóí
616 utils.writeBool(st, ggItems[i].Respawnable);
617 // Êîîðäèíàòû ðåñïóíà
618 utils.writeInt(st, LongInt(ggItems[i].InitX));
619 utils.writeInt(st, LongInt(ggItems[i].InitY));
620 // Âðåìÿ äî ðåñïàóíà
621 utils.writeInt(st, Word(ggItems[i].RespawnTime));
622 // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
623 utils.writeBool(st, ggItems[i].alive);
624 // Ìîæåò ëè îí ïàäàòü
625 utils.writeBool(st, ggItems[i].Fall);
626 // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
627 utils.writeInt(st, LongInt(ggItems[i].SpawnTrigger));
628 // Îáúåêò ïðåäìåòà
629 Obj_SaveState(st, @ggItems[i].Obj);
630 end;
631 end;
632 end;
635 procedure g_Items_LoadState (st: TStream);
636 var
637 count, i, a: Integer;
638 b: Byte;
639 begin
640 assert(st <> nil);
642 g_Items_Free();
644 // Êîëè÷åñòâî ïðåäìåòîâ
645 count := utils.readLongInt(st);
646 if (count = 0) then exit;
647 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid number of items');
649 for a := 0 to count-1 do
650 begin
651 // Ñèãíàòóðà ïðåäìåòà
652 if not utils.checkSign(st, 'ITEM') then raise XStreamError.Create('invalid item signature');
653 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid item version');
654 // Òèï ïðåäìåòà
655 b := utils.readByte(st); // bit7=1: monster drop
656 // Ñîçäàåì ïðåäìåò
657 i := g_Items_Create(0, 0, b and $7F, False, False);
658 if ((b and $80) <> 0) then g_Items_SetDrop(i);
659 // Åñòü ëè ðåñïàóí
660 ggItems[i].Respawnable := utils.readBool(st);
661 // Êîîðäèíàòû ðåñïóíà
662 ggItems[i].InitX := utils.readLongInt(st);
663 ggItems[i].InitY := utils.readLongInt(st);
664 // Âðåìÿ äî ðåñïàóíà
665 ggItems[i].RespawnTime := utils.readWord(st);
666 // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
667 ggItems[i].alive := utils.readBool(st);
668 // Ìîæåò ëè îí ïàäàòü
669 ggItems[i].Fall := utils.readBool(st);
670 // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
671 ggItems[i].SpawnTrigger := utils.readLongInt(st);
672 // Îáúåêò ïðåäìåòà
673 Obj_LoadState(@ggItems[i].Obj, st);
674 end;
675 end;
678 procedure g_Items_RestartRound ();
679 var
680 i: Integer;
681 it: PItem;
682 begin
683 for i := 0 to High(ggItems) do
684 begin
685 it := @ggItems[i];
686 it.Obj.oldX := it.Obj.X;
687 it.Obj.oldY := it.Obj.Y;
688 if not it.slotIsUsed then continue;
689 if it.Respawnable and (it.ItemType <> ITEM_NONE) then
690 begin
691 it.QuietRespawn := True;
692 it.RespawnTime := 0;
693 end
694 else
695 begin
696 g_Items_Remove(i);
697 if g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
698 end;
699 end;
700 end;
703 function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false): Boolean;
704 var
705 idx: Integer;
706 begin
707 result := false;
708 if (ggItems = nil) or not assigned(cb) then exit;
710 if backwards then
711 begin
712 for idx := High(ggItems) downto 0 do
713 begin
714 if ggItems[idx].alive and ggItems[idx].slotIsUsed then
715 begin
716 result := cb(@ggItems[idx]);
717 if result then exit;
718 end;
719 end;
720 end
721 else
722 begin
723 for idx := 0 to High(ggItems) do
724 begin
725 if ggItems[idx].alive and ggItems[idx].slotIsUsed then
726 begin
727 result := cb(@ggItems[idx]);
728 if result then exit;
729 end;
730 end;
731 end;
732 end;
734 function g_Items_NextAlive (startIdx: Integer): PItem;
735 var
736 idx: Integer;
737 begin
738 result := nil;
739 if (ggItems = nil) or (startIdx >= High(ggItems)) then exit;
740 for idx := startIdx + 1 to High(ggItems) do
741 if ggItems[idx].alive and ggItems[idx].slotIsUsed then
742 begin
743 result := @ggItems[idx];
744 exit;
745 end;
746 end;
748 // ////////////////////////////////////////////////////////////////////////// //
749 procedure g_Items_EmitPickupSound (idx: Integer);
750 var
751 it: PItem;
752 begin
753 if not g_Items_ValidId(idx) then exit;
754 it := @ggItems[idx];
755 g_Items_EmitPickupSoundAt(idx, it.Obj.X, it.Obj.Y);
756 end;
758 procedure g_Items_EmitPickupSoundAt (idx, x, y: Integer);
759 var
760 it: PItem;
761 begin
762 if not g_Items_ValidId(idx) then exit;
764 it := @ggItems[idx];
765 if gSoundEffectsDF then
766 begin
767 if it.ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL,
768 ITEM_INVIS, ITEM_MEDKIT_BLACK, ITEM_JETPACK] then
769 begin
770 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', x, y);
771 end
772 else if it.ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
773 ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
774 ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER,
775 ITEM_AMMO_BACKPACK] then
776 begin
777 g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON', x, y);
778 end
779 else
780 begin
781 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', x, y);
782 end;
783 end
784 else
785 begin
786 if it.ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_SUIT,
787 ITEM_MEDKIT_BLACK, ITEM_INVUL, ITEM_INVIS, ITEM_JETPACK] then
788 begin
789 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', x, y);
790 end
791 else if it.ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
792 ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
793 ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER] then
794 begin
795 g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON', x, y);
796 end
797 else
798 begin
799 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', x, y);
800 end;
801 end;
802 end;
805 procedure g_Items_AddDynLights();
806 var
807 f: Integer;
808 it: PItem;
809 begin
810 for f := 0 to High(ggItems) do
811 begin
812 it := @ggItems[f];
813 if not it.alive then continue;
814 case it.ItemType of
815 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);
816 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);
817 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);
818 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);
819 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);
820 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);
821 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);
822 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);
823 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);
824 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);
825 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);
826 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);
827 end;
828 end;
829 end;
832 end.