This file is indexed.

/usr/src/castle-game-engine-4.1.1/game/castleitems.pas is in castle-game-engine-src 4.1.1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

   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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
{
  Copyright 2006-2013 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Items, things that can be picked up, carried and used. }
unit CastleItems;

interface

uses CastleBoxes, X3DNodes, CastleScene, CastleVectors, CastleUtils,
  CastleClassUtils, Classes, CastleImages, GL, GLU, CastleGLUtils,
  CastlePrecalculatedAnimation, CastleResources, CastleGLImages,
  CastleXMLConfig, CastleSoundEngine, CastleFrustum, Castle3D, FGL, CastleColors;

type
  TInventoryItem = class;
  TInventoryItemClass = class of TInventoryItem;

  { Basic resource of an item that can be picked up, used and such.

    A "resource" is an information shared by all items of given type,
    for example you can have two instances of class TItemResource:
    Sword and LifePotion. (Actually, TItemWeaponResource, which is a descendant
    of TItemResource, sounds like a better candidate for the Sword.)
    Using them, you can create milions of actual swords and life potions,
    and place them of your level (as well as in inventories of creatures
    able to carry items). Every life potion (TInventoryItem instance)
    may keep some individual information (for example, how much of the potion
    is already used/drunk), but all life potions will share the same
    TItemResource instance, so e.g. they all will be displayed using the same model
    on 3D level (TItemResource.BaseAnimation) and the same image in 2D inventory
    (TItemResource.Image). }
  TItemResource = class(T3DResource)
  private
    FBaseAnimation: T3DResourceAnimation;
    FCaption: string;
    FImageURL: string;
    FImage: TCastleImage;
    FGLImage: TGLImage;
    FBoundingBoxRotated: TBox3D;
  protected
    procedure PrepareCore(const BaseLights: TAbstractLightInstancesList;
      const GravityUp: TVector3Single;
      const DoProgress: boolean); override;
    procedure ReleaseCore; override;
    { Which TInventoryItem descendant to create when constructing item
      of this resource by CreateItem. }
    function ItemClass: TInventoryItemClass; virtual;
  public
    constructor Create(const AName: string); override;
    destructor Destroy; override;

    procedure LoadFromFile(ResourceConfig: TCastleConfig); override;

    { Nice caption to display. }
    property Caption: string read FCaption;

    property BaseAnimation: T3DResourceAnimation read FBaseAnimation;

    { 2D image representing an item, to be used when showing inventory and such.
      The core engine itself doesn't use it, and doesn't force anything
      about such image (whether it should have some specific size,
      whether it should have alpha channel and such).
      It is up to the final game to make use of these images.

      If you're wondering how to generate such image:
      one option is to open the item 3D model in
      [http://castle-engine.sourceforge.net/view3dscene.php]
      and use "Display -> Screenshot ..." menu option (maybe the one
      that makes transparent background).
      It is usually a good idea to also remember the camera used for such
      screenshot with "Console -> Print Current Camera (Viewpoint)..."
      menu option. }
    function Image: TCastleImage;

    property ImageURL: string read FImageURL;

    { OpenGL resource to draw @link(Image). }
    function GLImage: TGLImage;

    { The largest possible bounding box of the 3D item,
      taking into account that actual item 3D model will be rotated when
      placed on world. You usually want to add current item position to this. }
    property BoundingBoxRotated: TBox3D read FBoundingBoxRotated;

    { Create item. This is how you should create new TInventoryItem instances.
      It is analogous to TCreatureResource.CreateCreature, but now for items.

      Note that the item itself doesn't exist on a 3D world --- you have to
      put it there if you want by TInventoryItem.PutOnWorld. That is because items
      can also exist only in player's backpack and such, and then they
      are independent from 3D world.

      @bold(Examples:)

      You usually define your own item resources by adding a subdirectory with
      resource.xml file to your game data. See
      [http://castle-engine.sourceforge.net/creating_data_resources.php]
      and engine tutorial for examples how to do this. Then you load the item
      resources with

@longCode(#
var
  Sword: TItemResource;
...
  Resources.LoadFromFiles;
  Sword := Resources.FindName('Sword') as TItemResource;
#)

      where 'Sword' is just our example item resource, assuming that one of your
      resource.xml files has resource with name="Sword".

      Now if you want to add the sword to your 3D world by code:

      Because TInventoryItem instance is automatically
      owned (freed) by the 3D world or inventory that contains it, a simplest
      example how to add an item to your 3D world is this:

@longCode(#
  Sword.CreateItem(1).PutOnWorld(SceneManager.World, Vector3Single(2, 3, 4));
#)

      This adds 1 item of the MyItemResource to the 3D world,
      on position (2, 3, 4). In simple cases you can get SceneManager
      instance from TCastleWindow.SceneManager or TCastleControl.SceneManager.

      If you want to instead add sword to the inventory of Player,
      you can call

@longCode(#
  SceneManager.Player.PickItem(Sword.CreateItem(1));
#)

      This assumes that you use SceneManager.Player property. It's not really
      obligatory, but it's the simplest way to have player with an inventory.
      See engine tutorial for examples how to create player.
      Anyway, if you have any TInventory instance, you can use
      TInventory.Pick to add TInventoryItem this way. }
    function CreateItem(const AQuantity: Cardinal): TInventoryItem;

    { Instantiate placeholder by create new item with CreateItem
      and putting it on level with TInventoryItem.PutOnWorld. }
    procedure InstantiatePlaceholder(World: T3DWorld;
      const APosition, ADirection: TVector3Single;
      const NumberPresent: boolean; const Number: Int64); override;

    function AlwaysPrepared: boolean; override;
  end;

  { Weapon that can make an immiediate attack (short-range/shoot)
    or fire a missile. }
  TItemWeaponResource = class(TItemResource)
  private
    FEquippingSound: TSoundType;
    FAttackAnimation: T3DResourceAnimation;
    FReadyAnimation: T3DResourceAnimation;
    FAttackTime: Single;
    FAttackSoundStart: TSoundType;
    FAttackAmmo: string;
    FAttackSoundHit: TSoundType;
    FAttackDamageConst: Single;
    FAttackDamageRandom: Single;
    FAttackKnockbackDistance: Single;
    FAttackShoot: boolean;
    FFireMissileName: string;
    FFireMissileSound: TSoundType;
  protected
    function ItemClass: TInventoryItemClass; override;
  public
    const
      DefaultAttackTime = 0.0;
      DefaultAttackDamageConst = 0.0;
      DefaultAttackDamageRandom = 0.0;
      DefaultAttackKnockbackDistance = 0.0;
      DefaultAttackShoot = false;

    constructor Create(const AName: string); override;

    { Sound to make on equipping. Each weapon can have it's own
      equipping sound. }
    property EquippingSound: TSoundType
      read FEquippingSound write FEquippingSound;

    { Animation of attack with this weapon. }
    property AttackAnimation: T3DResourceAnimation read FAttackAnimation;

    { Animation of keeping weapon ready. }
    property ReadyAnimation: T3DResourceAnimation read FReadyAnimation;

    { Common properties for all types of attack (short-range/shoot,
      fire missile) below. }

    { A time within AttackAnimationat at which TItemWeapon.Attack
      method will be called, which actually hits the enemy. }
    property AttackTime: Single read FAttackTime write FAttackTime
      default DefaultAttackTime;

    { Sound when attack starts. This is played when attack animation starts,
      and it means that we already checked that you have necessary ammunition
      (see AttackAmmo). }
    property AttackSoundStart: TSoundType
      read FAttackSoundStart write FAttackSoundStart default stNone;

    { Ammunition required to make an attack (applies to both immediate attack,
      like short-range/shoot, and firing missiles).
      Indicates item resource name to use as ammunition (like 'Quiver' or 'Bullets').
      It may be empty, in which case the ammunition is not necessary to make
      an attack.
      If it's set, we will check whether owner of the weapon (like a player)
      has at least one item of this resource, and we'll decrease it
      when firing.

      For example, if this weapon is a pistol, then you can set
      AttackDamageConst and AttackDamageRandom to non-zero and AttackShoot
      to @true to perform an immediatele shooting attack.
      And set AttackAmmo to something like 'Bullets'.

      For example, if this weapon is a bow, then you can set
      FireMissileName to 'Arrow', to fire arrows as missiles (they will
      fly and can be avoided by fast moving enemies).
      And set AttackAmmo to something like 'Quiver'. }
    property AttackAmmo: string read FAttackAmmo write FAttackAmmo;

    { Immediate attack (short-range/shoot) damage and knockback.
      This type of attack (along with AttackSoundHit) is only
      done if one of these properties is non-zero. They must be >= 0.
      @groupBegin }
    property AttackDamageConst: Single read FAttackDamageConst write FAttackDamageConst
      default DefaultAttackDamageConst;
    property AttackDamageRandom: Single read FAttackDamageRandom write FAttackDamageRandom
      default DefaultAttackDamageRandom;
    property AttackKnockbackDistance: Single
      read FAttackKnockbackDistance write FAttackKnockbackDistance
      default DefaultAttackKnockbackDistance;
    { @groupEnd }

    { Does the immediate attack is shooting.

      @unorderedList(
        @item(Shooting means that the hit enemy is determined by casting a ray from
          owner (like a shooting player) and seeing what it hits. Even enemies
          far away may be hit, but you have to aim precisely.)

        @item(When this is @false, we make a short-range (melee) attack.
          In this case the hit enemy is determined by looking at the weapon
          bounding volume near owner. Only the enemies
          very close to the owner get hit.)
      ) }
    property AttackShoot: boolean read FAttackShoot write FAttackShoot
      default DefaultAttackShoot;

    { Sound on successfull hit by an immediate attack (short-range/shoot). }
    property AttackSoundHit: TSoundType read FAttackSoundHit write FAttackSoundHit;

    { Creature resource name to be created (like 'Arrow') when firing a missile.
      Must be set to something not empty to actually fire a missile. }
    property FireMissileName: string read FFireMissileName write FFireMissileName;

    { Sound on missile fired. }
    property FireMissileSound: TSoundType read FFireMissileSound write FFireMissileSound default stNone;

    procedure LoadFromFile(ResourceConfig: TCastleConfig); override;
  end;

  TItemOnWorld = class;
  T3DAliveWithInventory = class;

  { An item that can be used, kept in the inventory, or (using PutOnWorld
    that wraps it in TItemOnWorld) dropped on 3D world.
    Thanks to the @link(Quantity) property, this may actually represent
    many "stacked" items, all having the same properties. }
  TInventoryItem = class(TComponent)
  private
    FResource: TItemResource;
    FQuantity: Cardinal;
    FOwner3D: T3D;
  protected
    { Try to sum (stack) the given Item with current TInventoryItem.
      The idea is that if player has 5 arrows, and picks an item representing
      another 5 arrows, then we sum then into one TInventoryItem instance
      representing 10 arrows.

      Various games, and various items,
      may require various approaches: for example, maybe you don't want
      some items to stack at all (e.g. you want to only allow stacking
      for items naturally appearing in vast quantities, like arrows and bolts
      and bullets (if you represent them as TInventoryItem at all)).
      Maybe you want to allow stacking only to centain number, e.g. arrows
      are summed into groups of maximum 20 items, anything above creates new stack?
      Overriding this procedure allows you to do all this.

      You can here increase the Quantity of current item,
      decrease the Quantity of parameter Item. In case the parameter Item
      no longer exists (it's Quantity reaches 0) you have to free it
      and set to @nil the Item parameter, in practice you usually want
      to call then @code(FreeAndNil(Item)).

      The default implementation of this in TInventoryItem class
      allows stacking always, as long as the Resource matches.
      This means that, by default, every TItemResource is existing at most once
      in T3DAliveWithInventory.Inventory. }
    procedure Stack(var Item: TInventoryItem); virtual;

    { Item is picked by an alive player/creature.
      The default implementation in this class adds the item to the Inventory
      by calling T3DAliveWithInventory.PickItem.

      You can override this to cause different behavior (for example,
      to consume some items right at pickup).
      Remember that this method must take care of memory management
      of this item. }
    procedure Picked(const NewOwner: T3DAliveWithInventory); virtual;

    { Use this item.

      In this class, this just prints a message "this item cannot be used".

      Implementation of this method can assume for now that this is one of
      player's owned Items. Implementation of this method can change
      our properties, including Quantity.
      As a very special exception, implementation of this method
      is allowed to set Quantity of Item to 0.

      Never call this method when Player.Dead. Implementation of this
      method may assume that Player is not Dead.

      Caller of this method should always be prepared to immediately
      handle the "Quantity = 0" situation by freeing given item,
      removing it from any list etc. }
    procedure Use; virtual;
  public
    property Resource: TItemResource read FResource;

    { Quantity of this item.
      This must always be >= 1. }
    property Quantity: Cardinal read FQuantity write FQuantity;

    { Splits item (with Quantity >= 2) into two items.
      It returns newly created object with the same properties
      as this object, and with Quantity set to QuantitySplit.
      And it lowers our Quantity by QuantitySplit.

      Always QuantitySplit must be >= 1 and < Quantity. }
    function Split(QuantitySplit: Cardinal): TInventoryItem;

    { Create TItemOnWorld instance referencing this item,
      and add this to the given 3D AWorld.
      Although normal item knows (through Owner3D) the world it lives in,
      but this method may be used for items that don't have an owner yet,
      so we take AWorld parameter explicitly.
      This is how you should create new TItemOnWorld instances.
      It is analogous to TCreatureResource.CreateCreature, but now for items. }
    function PutOnWorld(const AWorld: T3DWorld;
      const APosition: TVector3Single): TItemOnWorld;

    { 3D owner of the item,
      like a player or creature (if the item is in the backpack)
      or the TItemOnWorld instance (if the item is lying on the world,
      pickable). May be @nil only in special situations (when item is moved
      from one 3D to another, and technically it's safer to @nil this
      property).

      The owner is always responsible for freeing this TInventoryItem instance
      (in case of TItemOnWorld, it does it directly;
      in case of player or creature, it does it by TInventory). }
    property Owner3D: T3D read FOwner3D;

    { 3D world of this item, if any.

      Although the TInventoryItem, by itself, is not a 3D thing
      (only pickable TItemOnWorld is a 3D thing).
      But it exists inside a 3D world: either as pickable (TItemOnWorld),
      or as being owned by a 3D object (like player or creature) that are
      part of 3D world. In other words, our Owner3D.World is the 3D world
      this item lives in. }
    function World: T3DWorld;
  end;

  TItemWeapon = class(TInventoryItem)
  private
    { Weapon AttackAnimation is in progress.}
    Attacking: boolean;
    { If Attacking, then this is time of attack start, from LifeTime. }
    AttackStartTime: Single;
    { If Attacking, then this says whether Attack was already called. }
    AttackDone: boolean;
  protected
    { Make real attack, immediate (short-range/shoot) or firing missile.
      Called during weapon TItemWeaponResource.AttackAnimation,
      at the time TItemWeaponResource.AttackTime.
      The default implementation in @className does a short-range/shoot
      attack (if AttackDamageConst or AttackDamageRandom or AttackKnockbackDistance
      non-zero) and fires a missile (if FireMissileName not empty). }
    procedure Attack; virtual;
    procedure Use; override;
  public
    function Resource: TItemWeaponResource;

    { Owner equips this weapon. }
    procedure Equip; virtual;

    { Owner starts attack with this equipped weapon. }
    procedure EquippedAttack(const LifeTime: Single); virtual;

    { Time passses for equipped weapon. }
    procedure EquippedUpdate(const LifeTime: Single); virtual;

    { Return the 3D model to render for this equipped weapon, or @nil if none. }
    function EquippedScene(const LifeTime: Single): TCastleScene; virtual;
  end;

  { List of items, with a 3D object (like a player or creature) owning
    these items. Do not directly change this list, always use
    the owner (T3DAliveWithInventory) methods like
    @link(T3DAliveWithInventory.PickItem) or
    @link(T3DAliveWithInventory.DropItem).
    They make sure that items are correctly stacked, and that
    TInventoryItem.Owner3D and memory management is good. }
  TInventory = class(specialize TFPGObjectList<TInventoryItem>)
  private
    FOwner3D: T3DAliveWithInventory;
  protected
    { Add Item to inventory. See T3DAliveWithInventory.PickItemUpdate description,
      this method actually implements it. }
    function Pick(var Item: TInventoryItem): Integer;

    { Drop item with given index.
      ItemIndex must be valid (between 0 and Items.Count - 1).
      You @italic(must) take care yourself of returned TInventoryItem memory
      management.
      This is the low-level basis for T3DAliveWithInventory.DropItem. }
    function Drop(const ItemIndex: Integer): TInventoryItem;

    { Pass here items owned by this list, immediately after decreasing
      their Quantity. This frees the item (removing it from the list)
      if it's quantity reached zero. }
    procedure CheckDepleted(const Item: TInventoryItem);

    { Use the item of given index.
      This is the low-level basis for T3DAliveWithInventory.UseItem. }
    procedure Use(const Index: Integer);
  public
    constructor Create(const AOwner3D: T3DAliveWithInventory);

    { Owner of the inventory (like a player or creature).
      Never @nil, always valid for given inventory.
      All items on this list always have the same TInventoryItem.Owner3D value
      as the inventory they are in. }
    property Owner3D: T3DAliveWithInventory read FOwner3D;

    { Searches for item of given Resource. Returns index of first found,
      or -1 if not found. }
    function FindResource(Resource: TItemResource): Integer;
  end;

  { Item that is placed on a 3D world, ready to be picked up.
    It's not in anyone's inventory. }
  TItemOnWorld = class(T3DOrient)
  private
    FItem: TInventoryItem;
    Rotation, LifeTime: Single;
  protected
    function GetExists: boolean; override;
    function GetChild: T3D; override;
  public
    { Speed of the rotation of 3D item on world.
      In radians per second, default is DefaultRotationSpeed.
      Set to zero to disable rotation. }
    RotationSpeed: Single; static;

    { Does the player automatically picks up items by walking over them.
      Default is @true. If you set this to @false, you most probably want to
      implement some other way of picking up items, use the ExtractItem method.

      More precisely, this variable controls when TInventoryItem.Picked
      is called. When @true, it is called for player when player steps over
      an item (otherwise it's never called).
      You can always override TInventoryItem.Picked for particular items
      to customize what happens at "pick" --- the default implementation
      picks an item by adding it to inventory, but you could override it
      e.g. to consume some potions immediately on pickup. }
    AutoPick: boolean; static;

    const
      DefaultRotationSpeed = Pi;

    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    { The Item owned by this TItemOnWorld instance. Never @nil. }
    property Item: TInventoryItem read FItem;

    { Render the item, on current Position with current rotation etc.
      Current matrix should be modelview, this pushes/pops matrix state
      (so it 1. needs one place on matrix stack,
      2. doesn't modify current matrix).

      Pass current viewing Frustum to allow optimizing this
      (when item for sure is not within Frustum, we don't have
      to push it to OpenGL). }
    procedure Render(const Frustum: TFrustum;
      const Params: TRenderParams); override;

    procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); override;

    property Collides default false;
    property CollidesWithMoving default true;

    { Extract the @link(Item), used when picking up the TInventoryItem
      instance referenced by this TItemOnWorld instance. This returns our
      @link(Item) property, and clears it (clearing also TInventoryItem.Owner3D).
      At the next @link(Update), this TItemOnWorld instance will be freed
      and removed from 3D world.

      It's up to you what to do with resulting TInventoryItem instance.
      You can pick it up, by T3DAliveWithInventory.PickItem
      (for example player is an instance of T3DAliveWithInventory),
      or add it back to 3D world by TInventoryItem.PutOnWorld,
      or at least free it (or you'll get a memory leak). }
    function ExtractItem: TInventoryItem;
  end;

  { Alive 3D thing that has inventory (can keep items). }
  T3DAliveWithInventory = class(T3DAlive)
  private
    FInventory: TInventory;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    { Owned items. Never change the contents of this list directly,
      always use T3DAliveWithInventory methods like PickItem
      or DropItem for this. }
    property Inventory: TInventory read FInventory;

    { Add given Item to @link(Inventory).
      Because an item may be stacked with others,
      the actual Item instance may be freed and replaced with other by
      this method, that is why Item parameter is "var".
      Use PickItem method if you don't care about your Item instance.

      Returns index to the added item.

      Using this method means that the memory management of the item
      becomes the responsibility of this list. }
    function PickItemUpdate(var Item: TInventoryItem): Integer; virtual;

    { Add given Item to @link(Inventory). See PickItemUpdate for details.

      This is a shortcut to call PickItemUpdate and then ignore changes
      to Item instance. Calling this method may be comfortable,
      but remember that the Item instance possibly doesn't exist after we finish. }
    function PickItem(Item: TInventoryItem): Integer;

    { Drop item from @link(Inventory).
      It is Ok to pass here Index out of range, it will be ignored.
      @returns(Droppped item, or @nil if the operation was not completed due
        to any reason (e.g. no space on 3D world to fit this item).) }
    function DropItem(const Index: Integer): TItemOnWorld; virtual;

    { Use an item from @link(Inventory).
      Calls TInventoryItem.Use, and then checks whether
      the item was depleted (and eventually removes it from repository).
      It is Ok to pass here Index out of range, it will be ignored. }
    procedure UseItem(const Index: Integer); virtual;
  end;

var
  { Global callback to control items on level existence. }
  OnItemOnWorldExists: T3DExistsEvent;

implementation

uses SysUtils, CastleFilesUtils, CastlePlayer, CastleGameNotifications,
  CastleConfig, CastleCreatures;

{ TItemResource ------------------------------------------------------------ }

constructor TItemResource.Create(const AName: string);
begin
  inherited;
  FBaseAnimation := T3DResourceAnimation.Create(Self, 'base');
end;

destructor TItemResource.Destroy;
begin
  FreeAndNil(FImage);
  inherited;
end;

procedure TItemResource.LoadFromFile(ResourceConfig: TCastleConfig);
begin
  inherited;

  FImageURL := ResourceConfig.GetURL('image');

  FCaption := ResourceConfig.GetValue('caption', '');
  if FCaption = '' then
    raise Exception.CreateFmt('Empty caption attribute for item "%s"', [Name]);
end;

function TItemResource.Image: TCastleImage;
begin
  if FImage = nil then
    FImage := LoadImage(ImageURL, []);
  Result := FImage;
end;

function TItemResource.GLImage: TGLImage;
begin
  if FGLImage = nil then
    FGLImage := TGLImage.Create(Image);
  Result := FGLImage;
end;

procedure TItemResource.PrepareCore(const BaseLights: TAbstractLightInstancesList;
  const GravityUp: TVector3Single;
  const DoProgress: boolean);
var
  B: TBox3D;
begin
  inherited;
  B := FBaseAnimation.BoundingBox;
  FBoundingBoxRotated :=
    B.Transform(RotationMatrixDeg(45         , GravityUp)) +
    B.Transform(RotationMatrixDeg(45 + 90    , GravityUp)) +
    B.Transform(RotationMatrixDeg(45 + 90 * 2, GravityUp)) +
    B.Transform(RotationMatrixDeg(45 + 90 * 3, GravityUp));
  { prepare GLImage now }
  GLImage;
end;

procedure TItemResource.ReleaseCore;
begin
  FreeAndNil(FGLImage);
  inherited;
end;

function TItemResource.CreateItem(const AQuantity: Cardinal): TInventoryItem;
begin
  Result := ItemClass.Create(nil { for now, TInventoryItem.Owner is always nil });
  { set properties that in practice must have other-than-default values
    to sensibly use the item }
  Result.FResource := Self;
  Result.FQuantity := AQuantity;
  Assert(Result.Quantity >= 1, 'Item''s Quantity must be >= 1');
end;

function TItemResource.ItemClass: TInventoryItemClass;
begin
  Result := TInventoryItem;
end;

procedure TItemResource.InstantiatePlaceholder(World: T3DWorld;
  const APosition, ADirection: TVector3Single;
  const NumberPresent: boolean; const Number: Int64);
var
  ItemQuantity: Cardinal;
begin
  { calculate ItemQuantity }
  if NumberPresent then
    ItemQuantity := Number else
    ItemQuantity := 1;

  CreateItem(ItemQuantity).PutOnWorld(World, APosition);
end;

function TItemResource.AlwaysPrepared: boolean;
begin
  Result := true;
end;

{ TItemWeaponResource ------------------------------------------------------------ }

constructor TItemWeaponResource.Create(const AName: string);
begin
  inherited;
  FAttackAnimation := T3DResourceAnimation.Create(Self, 'attack');
  FReadyAnimation := T3DResourceAnimation.Create(Self, 'ready');
  FAttackTime := DefaultAttackTime;
  FAttackDamageConst := DefaultAttackDamageConst;
  FAttackDamageRandom := DefaultAttackDamageRandom;
  FAttackKnockbackDistance := DefaultAttackKnockbackDistance;
  FAttackShoot := DefaultAttackShoot;
end;

procedure TItemWeaponResource.LoadFromFile(ResourceConfig: TCastleConfig);
begin
  inherited;

  EquippingSound := SoundEngine.SoundFromName(
    ResourceConfig.GetValue('equipping_sound', ''));

  AttackTime := ResourceConfig.GetFloat('attack/time', DefaultAttackTime);
  AttackSoundStart := SoundEngine.SoundFromName(
    ResourceConfig.GetValue('attack/sound_start', ''));
  AttackAmmo := ResourceConfig.GetValue('attack/ammo', '');
  AttackSoundHit := SoundEngine.SoundFromName(
    ResourceConfig.GetValue('attack/sound_hit', ''));
  AttackDamageConst := ResourceConfig.GetFloat('attack/damage/const',
    DefaultAttackDamageConst);
  AttackDamageRandom := ResourceConfig.GetFloat('attack/damage/random',
    DefaultAttackDamageRandom);
  AttackKnockbackDistance := ResourceConfig.GetFloat('attack/knockback_distance',
    DefaultAttackKnockbackDistance);
  AttackShoot := ResourceConfig.GetValue('attack/shoot', DefaultAttackShoot);
  FireMissileName := ResourceConfig.GetValue('fire_missile/name', '');
  FireMissileSound := SoundEngine.SoundFromName(
    ResourceConfig.GetValue('fire_missile/sound', ''));
end;

function TItemWeaponResource.ItemClass: TInventoryItemClass;
begin
  Result := TItemWeapon;
end;

{ TInventoryItem ------------------------------------------------------------ }

procedure TInventoryItem.Stack(var Item: TInventoryItem);
begin
  if Item.Resource = Resource then
  begin
    { Stack Item with us }
    Quantity := Quantity + Item.Quantity;
    FreeAndNil(Item);
  end;
end;

function TInventoryItem.Split(QuantitySplit: Cardinal): TInventoryItem;
begin
  Check(Between(Integer(QuantitySplit), 1, Quantity - 1),
    'You must split >= 1 and less than current Quantity');

  Result := Resource.CreateItem(QuantitySplit);

  FQuantity -= QuantitySplit;
end;

function TInventoryItem.PutOnWorld(const AWorld: T3DWorld;
  const APosition: TVector3Single): TItemOnWorld;
begin
  Result := TItemOnWorld.Create(AWorld { owner });
  { set properties that in practice must have other-than-default values
    to sensibly use the item }
  Result.FItem := Self;
  FOwner3D := Result;
  Result.SetView(APosition, AnyOrthogonalVector(AWorld.GravityUp), AWorld.GravityUp);
  Result.Gravity := true;
  Result.FallSpeed := Resource.FallSpeed;
  Result.GrowSpeed := Resource.GrowSpeed;
  Result.CastShadowVolumes := Resource.CastShadowVolumes;
  AWorld.Add(Result);
end;

procedure TInventoryItem.Use;
begin
  Notifications.Show('This item cannot be used');
end;

function TInventoryItem.World: T3DWorld;
begin
  if Owner3D <> nil then
    Result := Owner3D.World else
    Result := nil;
end;

procedure TInventoryItem.Picked(const NewOwner: T3DAliveWithInventory);
begin
  NewOwner.PickItem(Self);
end;

{ TItemWeapon ---------------------------------------------------------------- }

procedure TItemWeapon.Attack;
var
  Attacker: T3DAlive;
  AttackDC, AttackDR, AttackKD: Single;
  AttackSoundHitDone: boolean;

  procedure ImmediateAttackHit(Enemy: T3DAlive);
  begin
    if not AttackSoundHitDone then
    begin
      SoundEngine.Sound(Resource.AttackSoundHit);
      AttackSoundHitDone := true;
    end;
    Enemy.Hurt(AttackDC + Random * AttackDR, Attacker.Direction, AttackKD, Attacker);
  end;

  procedure ShootAttack;
  var
    I: Integer;
    Hit: TRayCollision;
  begin
    { TODO: allow some helpers for aiming,
      similar to TCastleSceneManager.ApproximateActivation
      or maybe just collide a tube (not infinitely thin ray) with world. }
    Hit := Attacker.Ray(Attacker.Middle, Attacker.Direction);
    if Hit <> nil then
    begin
      for I := 0 to Hit.Count - 1 do
        if Hit[I].Item is T3DAlive then
        begin
          ImmediateAttackHit(T3DAlive(Hit[I].Item));
          Break;
        end;
      FreeAndNil(Hit);
    end;
  end;

  procedure ShortRangeAttack;
  var
    I: Integer;
    Enemy: T3DAlive;
    WeaponBoundingBox: TBox3D;
  begin
    { Attacker.Direction may be multiplied by something here for long-range weapons }
    WeaponBoundingBox := Attacker.BoundingBox.Translate(Attacker.Direction);
    { Tests: Writeln('WeaponBoundingBox is ', WeaponBoundingBox.ToNiceStr); }
    { TODO: we would prefer to use World.BoxCollision for this,
      but we need to know which creature was hit. }
    for I := 0 to World.Count - 1 do
      if World[I] is T3DAlive then
      begin
        Enemy := T3DAlive(World[I]);
        { Tests: Writeln('Creature bbox is ', C.BoundingBox.ToNiceStr); }
        if (Enemy <> Attacker) and
          Enemy.BoundingBox.Collision(WeaponBoundingBox) then
          ImmediateAttackHit(Enemy);
      end;
  end;

  procedure FireMissileAttack;
  begin
    (Resources.FindName(Resource.FireMissileName) as TCreatureResource).
       CreateCreature(World, Attacker.Position, Attacker.Direction);
    SoundEngine.Sound(Resource.FireMissileSound);
  end;

begin
  AttackSoundHitDone := false;

  { attacking only works when there's an owner (player, in the future creature
    should also be able to use it) of the weapon }
  if (Owner3D <> nil) and
     (Owner3D is T3DAlive) then
  begin
    Attacker := T3DAlive(Owner3D);

    AttackDC := Resource.AttackDamageConst;
    AttackDR := Resource.AttackDamageRandom;
    AttackKD := Resource.AttackKnockbackDistance;

    if (AttackDC >= 0) or
       (AttackDR >= 0) or
       (AttackKD >= 0) then
    begin
      if Resource.AttackShoot then
        ShootAttack else
        ShortRangeAttack;
    end;

    if Resource.FireMissileName <> '' then
      FireMissileAttack;
  end;
end;

procedure TItemWeapon.Use;
begin
  if (Owner3D <> nil) and
     (Owner3D is TPlayer) then
    TPlayer(Owner3D).EquippedWeapon := Self;
end;

function TItemWeapon.Resource: TItemWeaponResource;
begin
  Result := (inherited Resource) as TItemWeaponResource;
end;

procedure TItemWeapon.Equip;
begin
  SoundEngine.Sound(Resource.EquippingSound);

  { Just in case we had Attacking=true from previous weapon usage, clear it }
  Attacking := false;
end;

procedure TItemWeapon.EquippedAttack(const LifeTime: Single);

  { Check do you have ammunition to perform attack, and decrease it if yes. }
  function CheckAmmo: boolean;
  var
    Inventory: TInventory;
    AmmoIndex: Integer;
    AmmoItem: TInventoryItem;
    AmmoResource: TItemResource;
  begin
    { When AttackAmmo is set, check whether the owner has ammunition.
      Currently, only Player may have Inventory with items. }
    if Resource.AttackAmmo <> '' then
    begin
      if (Owner3D <> nil) and
         (Owner3D is T3DAliveWithInventory) then
      begin
        Inventory := T3DAliveWithInventory(Owner3D).Inventory;
        AmmoResource := Resources.FindName(Resource.AttackAmmo) as TItemResource;
        AmmoIndex := Inventory.FindResource(AmmoResource);
        Result := AmmoIndex <> -1;
        if Result then
        begin
          { delete ammunition from inventory }
          AmmoItem := Inventory[AmmoIndex];
          AmmoItem.Quantity := AmmoItem.Quantity - 1;
          Inventory.CheckDepleted(AmmoItem);
        end else
        begin
          Notifications.Show('You have no ammunition');
          SoundEngine.Sound(stPlayerInteractFailed);
        end;
      end else
        Result := false; // other creatures cannot have ammo for now
    end else
      Result := true; // no ammo required
  end;

begin
  if (not Attacking) and CheckAmmo then
  begin
    SoundEngine.Sound(Resource.AttackSoundStart);
    AttackStartTime := LifeTime;
    Attacking := true;
    AttackDone := false;
  end;
end;

procedure TItemWeapon.EquippedUpdate(const LifeTime: Single);
begin
  if Attacking and (not AttackDone) and
    (LifeTime - AttackStartTime >= Resource.AttackTime) then
  begin
    AttackDone := true;
    Attack;
  end;
end;

function TItemWeapon.EquippedScene(const LifeTime: Single): TCastleScene;
var
  AttackTime: Single;
  AttackAnim: T3DResourceAnimation;
begin
  if not Resource.Prepared then Exit(nil);

  AttackAnim := Resource.AttackAnimation;
  AttackTime := LifeTime - AttackStartTime;
  if Attacking and (AttackTime <= AttackAnim.Duration) then
  begin
    Result := AttackAnim.Scene(AttackTime, false);
  end else
  begin
    { turn off Attacking, if AttackTime passed }
    Attacking := false;
    { although current weapons animations are just static,
      we use LifeTime to enable any weapon animation
      (like weapon swaying, or some fire over the sword or such) in the future. }
    Result :=  Resource.ReadyAnimation.Scene(LifeTime, true);
  end;
end;

{ TInventory ------------------------------------------------------------ }

constructor TInventory.Create(const AOwner3D: T3DAliveWithInventory);
begin
  inherited Create(true);
  FOwner3D := AOwner3D;
end;

function TInventory.FindResource(Resource: TItemResource): Integer;
begin
  for Result := 0 to Count - 1 do
    if Items[Result].Resource = Resource then
      Exit;
  Result := -1;
end;

function TInventory.Pick(var Item: TInventoryItem): Integer;
begin
  for Result := 0 to Count - 1 do
  begin
    Items[Result].Stack(Item);
    if Item = nil then Break;
  end;

  if Item <> nil then
  begin
    Add(Item);
    Result := Count - 1;
  end else
    Item := Items[Result];

  Item.FOwner3D := Owner3D;
end;

function TInventory.Drop(const ItemIndex: Integer): TInventoryItem;
var
  SelectedItem: TInventoryItem;
  DropQuantity: Cardinal;
begin
  SelectedItem := Items[ItemIndex];

  { For now, always drop 1 item.
    This makes it independent from message boxes, and also suitable for
    items owned by creatures.

  if SelectedItem.Quantity > 1 then
  begin
    DropQuantity := SelectedItem.Quantity;

    if not MessageInputQueryCardinal(Window,
      Format('You have %d items "%s". How many of them do you want to drop ?',
        [SelectedItem.Quantity, SelectedItem.Resource.Caption]),
      DropQuantity, taLeft) then
      Exit(nil);

    if not Between(DropQuantity, 1, SelectedItem.Quantity) then
    begin
      Notifications.Show(Format('You cannot drop %d items', [DropQuantity]));
      Exit(nil);
    end;
  end else }
    DropQuantity := 1;

  if DropQuantity = SelectedItem.Quantity then
  begin
    Result := SelectedItem;
    Extract(SelectedItem); { Extract, not Remove, do not free }
  end else
  begin
    Result := SelectedItem.Split(DropQuantity);
  end;

  Result.FOwner3D := nil;
end;

procedure TInventory.CheckDepleted(const Item: TInventoryItem);
var
  Index: Integer;
begin
  if Item.Quantity = 0 then
  begin
    Index := IndexOf(Item);
    if Index <> -1 then
      Delete(Index);
  end;
end;

procedure TInventory.Use(const Index: Integer);
var
  Item: TInventoryItem;
begin
  Item := Items[Index];
  Item.Use;
  { CheckDepleted will search for new index, since using an item
    may change the items list and so change the index. }
  CheckDepleted(Item);
end;

{ TItemOnWorld ------------------------------------------------------------ }

constructor TItemOnWorld.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  CollidesWithMoving := true;
  Gravity := true;

  { Items are not collidable, player can enter them to pick them up.
    For now, this also means that creatures can pass through them,
    which isn't really troublesome now. }
  Collides := false;
end;

destructor TItemOnWorld.Destroy;
begin
  FreeAndNil(FItem);
  inherited;
end;

function TItemOnWorld.GetChild: T3D;
begin
  if (Item = nil) or not Item.Resource.Prepared then Exit(nil);
  Result := Item.Resource.BaseAnimation.Scene(LifeTime, true);
end;

procedure TItemOnWorld.Render(const Frustum: TFrustum;
  const Params: TRenderParams);
var
  BoxRotated: TBox3D;
begin
  inherited;
  if RenderDebug3D and GetExists and
    (not Params.Transparent) and Params.ShadowVolumesReceivers then
  begin
    BoxRotated := Item.Resource.BoundingBoxRotated.Translate(Position);
    if Frustum.Box3DCollisionPossibleSimple(BoxRotated) then
    begin
      glPushAttrib(GL_ENABLE_BIT);
        glDisable(GL_LIGHTING);
        glEnable(GL_DEPTH_TEST);
        glColorv(Gray3Single);
        glDrawBox3DWire(BoundingBox);
        glDrawBox3DWire(BoxRotated);
        glColorv(Yellow3Single);
        glDrawAxisWire(Middle, BoxRotated.AverageSize(true, 0));
      glPopAttrib;
    end;
  end;
end;

procedure TItemOnWorld.Update(const SecondsPassed: Single; var RemoveMe: TRemoveType);
var
  DirectionZero, U: TVector3Single;
begin
  inherited;
  if not GetExists then Exit;

  LifeTime += SecondsPassed;

  { LifeTime is used to choose animation frame in GetChild.
    So the item constantly changes, even when it's
    transformation (things taken into account in T3DOrient) stay equal. }
  VisibleChangeHere([vcVisibleGeometry]);

  Rotation += RotationSpeed * SecondsPassed;
  U := World.GravityUp; // copy to local variable for speed
  DirectionZero := Normalized(AnyOrthogonalVector(U));
  SetView(RotatePointAroundAxisRad(Rotation, DirectionZero, U), U);

  if AutoPick and
     (World.Player <> nil) and
     (World.Player is T3DAliveWithInventory) and
     (not World.Player.Dead) and
     BoundingBox.Collision(World.Player.BoundingBox) then
    ExtractItem.Picked(T3DAliveWithInventory(World.Player));

  { Since we cannot live with Item = nil, we free ourselves }
  if Item = nil then
    RemoveMe := rtRemoveAndFree;
end;

function TItemOnWorld.ExtractItem: TInventoryItem;
begin
  { We no longer own this Item, so clear references. }
  Result := Item;
  Result.FOwner3D := nil;
  FItem := nil;
end;

function TItemOnWorld.GetExists: boolean;
begin
  Result := (inherited GetExists) and
    ((not Assigned(OnItemOnWorldExists)) or OnItemOnWorldExists(Self));
end;

{ T3DAliveWithInventory ------------------------------------------------------ }

constructor T3DAliveWithInventory.Create(AOwner: TComponent);
begin
  inherited;
  FInventory := TInventory.Create(Self);
end;

destructor T3DAliveWithInventory.Destroy;
begin
  FreeAndNil(FInventory);
  inherited;
end;

function T3DAliveWithInventory.PickItemUpdate(var Item: TInventoryItem): Integer;
begin
  Result := Inventory.Pick(Item);
end;

function T3DAliveWithInventory.PickItem(Item: TInventoryItem): Integer;
begin
  Result := PickItemUpdate(Item);
end;

function T3DAliveWithInventory.DropItem(const Index: Integer): TItemOnWorld;

  function GetItemDropPosition(DroppedItemResource: TItemResource;
    out DropPosition: TVector3Single): boolean;
  var
    ItemBox: TBox3D;
    ItemBoxRadius: Single;
    ItemBoxMiddle: TVector3Single;
  begin
    ItemBox := DroppedItemResource.BoundingBoxRotated;
    ItemBoxMiddle := ItemBox.Middle;
    { Box3DRadius calculates radius around (0, 0, 0) and we want
      radius around ItemBoxMiddle }
    ItemBoxRadius := ItemBox.Translate(VectorNegate(ItemBoxMiddle)).Radius;

    { Calculate DropPosition.

      We must move the item a little before us to
      1. show visually player that the item was dropped
      2. to avoid automatically picking it again

      Note that I take direction from DirectionInGravityPlane,
      not from Direction, otherwise when player is looking
      down he could be able to put item "inside the ground".
      Collision detection with the level below would actually
      prevent putting item "inside the ground", but the item
      would be too close to the player --- he could pick it up
      immediately. }
    DropPosition := Camera.Position +
      Camera.DirectionInGravityPlane *
        (0.6 * (Camera.RealPreferredHeight * Sqrt3 + ItemBox.Diagonal));

    { Now check is DropPosition actually possible
      (i.e. check collisions item<->everything).
      The assumption is that item starts from
      Camera.Position and is moved to DropPosition.

      But actually we must shift both these positions,
      so that we check positions that are ideally in the middle
      of item's BoundingBoxRotated. Otherwise the item
      could get *partially* stuck within the wall, which wouldn't
      look good. }

    Result := World.WorldMoveAllowed(
      ItemBoxMiddle + Camera.Position,
      ItemBoxMiddle + DropPosition, true, ItemBoxRadius,
      ItemBox + Camera.Position,
      ItemBox + DropPosition, false);
  end;

var
  DropPosition: TVector3Single;
  DropppedItem: TInventoryItem;
begin
  Result := nil;

  if Between(Index, 0, Inventory.Count - 1) then
  begin
    if GetItemDropPosition(Inventory[Index].Resource, DropPosition) then
    begin
      DropppedItem := Inventory.Drop(Index);
      Result := DropppedItem.PutOnWorld(World, DropPosition);
    end;
  end;
end;

procedure T3DAliveWithInventory.UseItem(const Index: Integer);
begin
  if Between(Index, 0, Inventory.Count - 1) then
    Inventory.Use(Index);
end;

initialization
  TItemOnWorld.RotationSpeed := TItemOnWorld.DefaultRotationSpeed;
  TItemOnWorld.AutoPick := true;

  RegisterResourceClass(TItemResource, 'Item');
  RegisterResourceClass(TItemWeaponResource, 'Weapon');
end.