diff --git a/app/Console/Commands/SC/ImportLootArchetypes.php b/app/Console/Commands/SC/ImportLootArchetypes.php new file mode 100644 index 000000000..654994afb --- /dev/null +++ b/app/Console/Commands/SC/ImportLootArchetypes.php @@ -0,0 +1,115 @@ +withProgressBar($files, function (string $file) { + + $data = (new Archetype($file))->getData(); + + $tagId = EntityTag::query()->firstOrCreate(['tag' => $data['uuid']])->id; + + /** @var \App\Models\SC\Loot\Archetype $type */ + $type = \App\Models\SC\Loot\Archetype::query()->updateOrCreate([ + 'uuid' => $data['uuid'], + ], [ + 'name' => $data['class_name'], + 'tag_id' => $tagId, + ]); + + $this->createPrimaryGroups($type, $data); + $this->createSecondaryGroups($type, $data); + }); + } + + private function createPrimaryGroups(\App\Models\SC\Loot\Archetype $archetype, array $data): void + { + collect($data['primary_groups'])->each(function (array $group) use ($archetype) { + $groupTagId = EntityTag::query()->firstOrCreate(['tag' => $group['tag']])->id; + + /** @var PrimaryGroup $group */ + $groupModel = $archetype->primaryGroups()->updateOrCreate([ + 'archetype_id' => $archetype->id, + 'tag' => $group['tag'], + ], [ + 'name' => $group['name'], + 'weight' => $group['weight'], + 'tag_id' => $groupTagId, + ]); + + $positiveTags = collect($group['positive_tags'])->map(fn ($tag) => EntityTag::query()->firstOrCreate(['tag' => $tag])->id); + $negativeTags = collect($group['negative_tags'])->map(fn ($tag) => EntityTag::query()->firstOrCreate(['tag' => $tag])->id); + + $groupModel->tags()->syncWithPivotValues($negativeTags, ['is_positive' => false], false); + $groupModel->tags()->syncWithPivotValues($positiveTags, ['is_positive' => true], false); + + if (! empty($group['stack_size'])) { + $groupModel->stackSize()->updateOrCreate([ + 'primary_group_id' => $groupModel->id, + ], [ + 'min' => $group['stack_size']['min'], + 'max' => $group['stack_size']['max'], + ]); + } + + if (! empty($group['spawn_with'])) { + /** @var SpawnWith $spawnWith */ + $spawnWith = $groupModel->spawnWith()->updateOrCreate([ + 'primary_group_id' => $groupModel->id, + ], [ + 'name' => $group['spawn_with']['name'], + 'mode' => $group['spawn_with']['mode'], + 'min' => $group['spawn_with']['min'], + 'max' => $group['spawn_with']['max'], + ]); + + $positiveTags = collect($group['spawn_with']['positive_tags'])->map(fn ($tag) => EntityTag::query()->firstOrCreate(['tag' => $tag])->id); + $negativeTags = collect($group['spawn_with']['negative_tags'])->map(fn ($tag) => EntityTag::query()->firstOrCreate(['tag' => $tag])->id); + + $spawnWith->tags()->syncWithPivotValues($positiveTags, ['is_positive' => true], false); + $spawnWith->tags()->syncWithPivotValues($negativeTags, ['is_positive' => false], false); + } + }); + } + + private function createSecondaryGroups(\App\Models\SC\Loot\Archetype $archetype, array $data): void + { + collect($data['secondary_groups'])->each(function (array $group) use ($archetype) { + $archetype->secondaryGroups()->updateOrCreate([ + 'archetype_id' => $archetype->id, + 'tag' => $group['tag'], + ], [ + 'weight' => $group['weight'], + ]); + }); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ecdf83c5c..2f50101b6 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -20,6 +20,7 @@ use App\Console\Commands\SC\ComputeItemBaseIds; use App\Console\Commands\SC\ImportClothing; use App\Console\Commands\SC\ImportItems; +use App\Console\Commands\SC\ImportLootArchetypes; use App\Console\Commands\SC\ImportPersonalWeapons; use App\Console\Commands\SC\ImportShops; use App\Console\Commands\SC\ImportVehicleItems; @@ -115,6 +116,7 @@ class Kernel extends ConsoleKernel ImportClothing::class, ImportVehicleItems::class, ImportPersonalWeapons::class, + ImportLootArchetypes::class, ComputeItemBaseIds::class, TranslateItems::class, diff --git a/app/Http/Resources/SC/Item/ItemResource.php b/app/Http/Resources/SC/Item/ItemResource.php index 3c4f2077a..8b1386c07 100644 --- a/app/Http/Resources/SC/Item/ItemResource.php +++ b/app/Http/Resources/SC/Item/ItemResource.php @@ -32,9 +32,12 @@ use App\Http\Resources\SC\ItemSpecification\ShieldResource; use App\Http\Resources\SC\ItemSpecification\ThrusterResource; use App\Http\Resources\SC\ItemSpecification\TractorBeamResource; +use App\Http\Resources\SC\Loot\ArchetypeResource; +use App\Http\Resources\SC\Loot\PrimaryGroupResource; use App\Http\Resources\SC\Manufacturer\ManufacturerLinkResource; use App\Http\Resources\SC\Shop\ShopResource; use App\Http\Resources\SC\Vehicle\Weapon\VehicleWeaponResource; +use App\Models\SC\Loot\PrimaryGroup; use Illuminate\Http\Request; use OpenApi\Attributes as OA; @@ -161,6 +164,7 @@ public function __construct($resource, bool $isVehicleItem = false, bool $onlyBa public static function validIncludes(): array { return parent::validIncludes() + [ + 'loot_archetypes', 'shops', 'shops.items', ]; @@ -177,9 +181,13 @@ public function toArray(Request $request): array $vehicleItem = $this->vehicleItem; +// dd($this->lootGroups()->map(fn (PrimaryGroup $group) => $group->archetype->name)); + return [ 'uuid' => $this->uuid, 'name' => $this->name, + 'loot_groups' => $this->lootGroups()->pluck('name')->toArray(), + 'loot_archetypes' => $this->lootArchetypes()->pluck('name')->toArray(), 'class_name' => $this->class_name, 'description' => $this->getTranslation($this, $request), 'size' => $this->size, diff --git a/app/Http/Resources/SC/Loot/ArchetypeResource.php b/app/Http/Resources/SC/Loot/ArchetypeResource.php new file mode 100644 index 000000000..04d2601f8 --- /dev/null +++ b/app/Http/Resources/SC/Loot/ArchetypeResource.php @@ -0,0 +1,22 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'uuid' => $this->uuid, + 'name' => $this->name, + ]; + } +} diff --git a/app/Http/Resources/SC/Loot/PrimaryGroupResource.php b/app/Http/Resources/SC/Loot/PrimaryGroupResource.php new file mode 100644 index 000000000..3614319ec --- /dev/null +++ b/app/Http/Resources/SC/Loot/PrimaryGroupResource.php @@ -0,0 +1,21 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'name' => $this->name + ]; + } +} diff --git a/app/Jobs/SC/Import/Item.php b/app/Jobs/SC/Import/Item.php index b67745e63..d58c006e0 100644 --- a/app/Jobs/SC/Import/Item.php +++ b/app/Jobs/SC/Import/Item.php @@ -4,6 +4,7 @@ namespace App\Jobs\SC\Import; +use App\Models\SC\EntityTag; use App\Models\SC\Item\Interaction; use App\Models\SC\Item\ItemPort; use App\Models\SC\Item\ItemPortType; @@ -94,6 +95,7 @@ public function handle(): void $this->addTags($itemModel, $this->data, 'tags'); $this->addTags($itemModel, $this->data, 'required_tags', true); $this->addInteractions($itemModel, $this->data); + $this->addEntityTags($itemModel, $this->data); } private function createDimensionModel(\App\Models\SC\Item\Item $itemModel): void @@ -319,4 +321,27 @@ private function addInteractions($model, $data): void $model->interactions()->sync($interactions); } + + /** + * @param \App\Models\SC\Item\Item $model + * @param $data + */ + private function addEntityTags(\App\Models\SC\Item\Item $model, $data): void + { + if (empty($data['entity_tags'])) { + return; + } + + $tags = collect($data['entity_tags']) + ->map('trim') + ->map(function ($tag) { + $tag = EntityTag::query()->firstOrCreate([ + 'tag' => $tag, + ]); + + return $tag->id; + }); + + $model->entityTags()->sync($tags); + } } diff --git a/app/Models/SC/EntityTag.php b/app/Models/SC/EntityTag.php new file mode 100644 index 000000000..b667d2cb2 --- /dev/null +++ b/app/Models/SC/EntityTag.php @@ -0,0 +1,29 @@ +belongsToMany( + Item::class, + 'sc_item_entity_tag', + 'item_id', + 'entity_tag_id' + ); + } +} diff --git a/app/Models/SC/Item/Item.php b/app/Models/SC/Item/Item.php index b57b1d143..8c3909e01 100644 --- a/app/Models/SC/Item/Item.php +++ b/app/Models/SC/Item/Item.php @@ -13,6 +13,7 @@ use App\Models\SC\Char\PersonalWeapon\Knife; use App\Models\SC\Char\PersonalWeapon\PersonalWeapon; use App\Models\SC\Char\PersonalWeapon\PersonalWeaponMagazine; +use App\Models\SC\EntityTag; use App\Models\SC\Food\Food; use App\Models\SC\ItemSpecification\Bomb\Bomb; use App\Models\SC\ItemSpecification\Cooler; @@ -32,6 +33,8 @@ use App\Models\SC\ItemSpecification\Shield; use App\Models\SC\ItemSpecification\Thruster; use App\Models\SC\ItemSpecification\TractorBeam; +use App\Models\SC\Loot\Archetype; +use App\Models\SC\Loot\PrimaryGroup; use App\Models\SC\Manufacturer; use App\Models\SC\Shop\Shop; use App\Models\SC\Shop\ShopItem; @@ -487,4 +490,41 @@ public function variants(): HasMany { return $this->hasMany(self::class, 'base_id', 'id'); } + + public function entityTags(): BelongsToMany + { + return $this->belongsToMany( + EntityTag::class, + 'sc_item_entity_tag', + 'item_id', + 'entity_tag_id' + ); + } + + + public function lootArchetypes(): BelongsToMany + { + return $this->belongsToMany( + Archetype::class, + 'sc_item_entity_tag', + 'item_id', + 'entity_tag_id', + 'id', + 'tag_id', + ); + } + + public function lootGroups()//: BelongsToMany + { + return PrimaryGroup::query()->whereIn('tag', $this->entityTags()->pluck('tag')->toArray())->get(); + + return $this->belongsToMany( + PrimaryGroup::class, + 'sc_item_entity_tag', + 'item_id', + 'entity_tag_id', + 'id', + 'tag_id', + ); + } } diff --git a/app/Models/SC/Loot/Archetype.php b/app/Models/SC/Loot/Archetype.php new file mode 100644 index 000000000..9afd7e7a8 --- /dev/null +++ b/app/Models/SC/Loot/Archetype.php @@ -0,0 +1,48 @@ +hasMany( + PrimaryGroup::class, + 'archetype_id', + 'id' + ); + } + + public function secondaryGroups(): HasMany + { + return $this->hasMany( + SecondaryGroup::class, + 'archetype_id', + 'id' + ); + } + + public function items(): HasManyThrough + { + return $this->hasManyThrough( + Item::class, + PrimaryGroup::class, + ); + } +} diff --git a/app/Models/SC/Loot/PrimaryGroup.php b/app/Models/SC/Loot/PrimaryGroup.php new file mode 100644 index 000000000..17fc4bcdd --- /dev/null +++ b/app/Models/SC/Loot/PrimaryGroup.php @@ -0,0 +1,74 @@ + 'double', + ]; + + public function archetype(): BelongsTo + { + return $this->belongsTo( + Archetype::class, + 'archetype_id', + 'id', + 'sc_loot_archetypes', + ); + } + + public function tag(): BelongsTo + { + return $this->belongsTo(EntityTag::class, 'tag', 'tag'); + } + + public function tags(): BelongsToMany + { + return $this->belongsToMany( + EntityTag::class, + 'sc_loot_primary_group_entity_tag', + 'primary_group_id', + 'entity_tag_id' + )->withPivot(['is_positive']); + } + + public function positiveTags(): BelongsToMany + { + return $this->tags()->wherePivot('is_positive', true); + } + + public function negativeTags(): BelongsToMany + { + return $this->tags()->wherePivot('is_positive', false); + } + + public function spawnWith(): HasMany + { + return $this->hasMany(SpawnWith::class, 'primary_group_id'); + } + + public function stackSize(): HasMany + { + return $this->hasMany(StackSize::class, 'primary_group_id'); + } +} diff --git a/app/Models/SC/Loot/SecondaryGroup.php b/app/Models/SC/Loot/SecondaryGroup.php new file mode 100644 index 000000000..6254cd04e --- /dev/null +++ b/app/Models/SC/Loot/SecondaryGroup.php @@ -0,0 +1,37 @@ + 'double', + ]; + + public function tag(): BelongsTo + { + return $this->belongsTo(EntityTag::class, 'tag', 'tag'); + } + + public function items(): Collection + { + return Item::query()->whereRelation('entity_tag', 'tag', $this->tag)->get(); + } +} diff --git a/app/Models/SC/Loot/SpawnWith.php b/app/Models/SC/Loot/SpawnWith.php new file mode 100644 index 000000000..355976fb4 --- /dev/null +++ b/app/Models/SC/Loot/SpawnWith.php @@ -0,0 +1,48 @@ + 'int', + 'max' => 'int', + ]; + + public function tags(): BelongsToMany + { + return $this->belongsToMany( + EntityTag::class, + 'sc_loot_spawn_with_entity_tag', + 'spawn_with_id', + 'entity_tag_id' + )->withPivot(['is_positive']); + } + + public function positiveTags(): BelongsToMany + { + return $this->tags()->wherePivot('is_positive', true); + } + + public function negativeTags(): BelongsToMany + { + return $this->tags()->wherePivot('is_positive', false); + } +} diff --git a/app/Models/SC/Loot/StackSize.php b/app/Models/SC/Loot/StackSize.php new file mode 100644 index 000000000..c3d04e985 --- /dev/null +++ b/app/Models/SC/Loot/StackSize.php @@ -0,0 +1,24 @@ + 'int', + 'max' => 'int', + ]; +} diff --git a/app/Models/SC/Loot/Table.php b/app/Models/SC/Loot/Table.php new file mode 100644 index 000000000..31561515a --- /dev/null +++ b/app/Models/SC/Loot/Table.php @@ -0,0 +1,11 @@ +where('name_raw', 'NOT LIKE', '%Levski%') - ->where('name_raw', 'NOT LIKE', '%IAE Expo%'); + ->where('name_raw', 'NOT LIKE', '%IAE Expo%'); } ); } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 36ed0f0e3..1d591425e 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -69,11 +69,16 @@ protected function mapApiRoutes(): void ->middleware(['api']) ->group(base_path('routes/api/api_v1.php')); + $v2Middleware = ['api.v2']; + if (config('app.env') === 'production') { + $v2Middleware[] = 'throttle:api'; + } + Route::middleware('api.v2') ->name('api.v2.') ->namespace($this->namespace.'\Api\V2') ->prefix('api/v2') - ->middleware(['api.v2', 'throttle:api']) + ->middleware($v2Middleware) ->group(base_path('routes/api/api_v2.php')); } diff --git a/app/Services/Parser/SC/ItemBaseData.php b/app/Services/Parser/SC/ItemBaseData.php index 413222660..1f58565bd 100644 --- a/app/Services/Parser/SC/ItemBaseData.php +++ b/app/Services/Parser/SC/ItemBaseData.php @@ -17,7 +17,7 @@ public static function getData(Collection $item): ?array 'lifetime' => Arr::get($item, 'Raw.Entity.Components.SDegradationParams.MaxLifetimeHours'), 'salvageable' => Arr::get($item, 'Raw.Entity.Components.SHealthComponentParams.IsSalvagable'), 'repairable' => Arr::get($item, 'Raw.Entity.Components.SHealthComponentParams.IsRepairable'), - ], function ($entry) { + ], static function ($entry) { return $entry !== null; }); @@ -27,33 +27,34 @@ public static function getData(Collection $item): ?array $out['heat'] = self::addHeatData($item); $out['distortion'] = self::addDistortionData($item); $out['interactions'] = self::addInteractionData($item); + $out['entity_tags'] = collect($item['tags'] ?? [])->map(fn (array $tag) => $tag['value']); return $out; } private static function addPowerData(Collection $item): array { - if (!isset($item['Components']['EntityComponentPowerConnection'])) { + if (! isset($item['Components']['EntityComponentPowerConnection'])) { return []; } $basePath = 'Components.EntityComponentPowerConnection.'; return array_filter([ - 'power_base' => Arr::get($item, $basePath . 'PowerBase'), - 'power_draw' => Arr::get($item, $basePath . 'PowerDraw'), + 'power_base' => Arr::get($item, $basePath.'PowerBase'), + 'power_draw' => Arr::get($item, $basePath.'PowerDraw'), - 'throttleable' => Arr::get($item, $basePath . 'IsThrottleable'), - 'overclockable' => Arr::get($item, $basePath . 'IsOverclockable'), + 'throttleable' => Arr::get($item, $basePath.'IsThrottleable'), + 'overclockable' => Arr::get($item, $basePath.'IsOverclockable'), - 'overclock_threshold_min' => Arr::get($item, $basePath . 'OverclockThresholdMin'), - 'overclock_threshold_max' => Arr::get($item, $basePath . 'OverclockThresholdMax'), - 'overclock_performance' => Arr::get($item, $basePath . 'OverclockPerformance'), + 'overclock_threshold_min' => Arr::get($item, $basePath.'OverclockThresholdMin'), + 'overclock_threshold_max' => Arr::get($item, $basePath.'OverclockThresholdMax'), + 'overclock_performance' => Arr::get($item, $basePath.'OverclockPerformance'), - 'overpower_performance' => Arr::get($item, $basePath . 'OverpowerPerformance'), + 'overpower_performance' => Arr::get($item, $basePath.'OverpowerPerformance'), - 'power_to_em' => Arr::get($item, $basePath . 'PowerToEM'), - 'decay_rate_em' => Arr::get($item, $basePath . 'DecayRateOfEM'), + 'power_to_em' => Arr::get($item, $basePath.'PowerToEM'), + 'decay_rate_em' => Arr::get($item, $basePath.'DecayRateOfEM'), ], static function ($entry) { return $entry !== null; }); @@ -61,32 +62,32 @@ private static function addPowerData(Collection $item): array private static function addHeatData(Collection $item): array { - if (!isset($item['Components']['EntityComponentHeatConnection'])) { + if (! isset($item['Components']['EntityComponentHeatConnection'])) { return []; } $basePath = 'Components.EntityComponentHeatConnection.'; return array_filter([ - 'temperature_to_ir' => Arr::get($item, $basePath . 'TemperatureToIR'), - 'ir_temperature_threshold' => Arr::get($item, $basePath . 'StartIRTemperature'), - 'overpower_heat' => Arr::get($item, $basePath . 'OverpowerHeat'), - 'overclock_threshold_min' => Arr::get($item, $basePath . 'OverclockThresholdMinHeat'), - 'overclock_threshold_max' => Arr::get($item, $basePath . 'OverclockThresholdMaxHeat'), - 'thermal_energy_base' => Arr::get($item, $basePath . 'ThermalEnergyBase'), - 'thermal_energy_draw' => Arr::get($item, $basePath . 'ThermalEnergyDraw'), - 'thermal_conductivity' => Arr::get($item, $basePath . 'ThermalConductivity'), - 'specific_heat_capacity' => Arr::get($item, $basePath . 'SpecificHeatCapacity'), - 'mass' => Arr::get($item, $basePath . 'Mass'), - 'surface_area' => Arr::get($item, $basePath . 'SurfaceArea'), - 'start_cooling_temperature' => Arr::get($item, $basePath . 'StartCoolingTemperature'), - 'max_cooling_rate' => Arr::get($item, $basePath . 'MaxCoolingRate'), - 'max_temperature' => Arr::get($item, $basePath . 'MaxTemperature'), - 'min_temperature' => Arr::get($item, $basePath . 'MinTemperature'), - 'overheat_temperature' => Arr::get($item, $basePath . 'OverheatTemperature'), - 'recovery_temperature' => Arr::get($item, $basePath . 'RecoveryTemperature'), - 'misfire_min_temperature' => Arr::get($item, $basePath . 'MisfireMinTemperature'), - 'misfire_max_temperature' => Arr::get($item, $basePath . 'MisfireMaxTemperature'), + 'temperature_to_ir' => Arr::get($item, $basePath.'TemperatureToIR'), + 'ir_temperature_threshold' => Arr::get($item, $basePath.'StartIRTemperature'), + 'overpower_heat' => Arr::get($item, $basePath.'OverpowerHeat'), + 'overclock_threshold_min' => Arr::get($item, $basePath.'OverclockThresholdMinHeat'), + 'overclock_threshold_max' => Arr::get($item, $basePath.'OverclockThresholdMaxHeat'), + 'thermal_energy_base' => Arr::get($item, $basePath.'ThermalEnergyBase'), + 'thermal_energy_draw' => Arr::get($item, $basePath.'ThermalEnergyDraw'), + 'thermal_conductivity' => Arr::get($item, $basePath.'ThermalConductivity'), + 'specific_heat_capacity' => Arr::get($item, $basePath.'SpecificHeatCapacity'), + 'mass' => Arr::get($item, $basePath.'Mass'), + 'surface_area' => Arr::get($item, $basePath.'SurfaceArea'), + 'start_cooling_temperature' => Arr::get($item, $basePath.'StartCoolingTemperature'), + 'max_cooling_rate' => Arr::get($item, $basePath.'MaxCoolingRate'), + 'max_temperature' => Arr::get($item, $basePath.'MaxTemperature'), + 'min_temperature' => Arr::get($item, $basePath.'MinTemperature'), + 'overheat_temperature' => Arr::get($item, $basePath.'OverheatTemperature'), + 'recovery_temperature' => Arr::get($item, $basePath.'RecoveryTemperature'), + 'misfire_min_temperature' => Arr::get($item, $basePath.'MisfireMinTemperature'), + 'misfire_max_temperature' => Arr::get($item, $basePath.'MisfireMaxTemperature'), ], static function ($entry) { return $entry !== null; }); @@ -94,23 +95,23 @@ private static function addHeatData(Collection $item): array private static function addDistortionData(Collection $item): array { - if (!isset($item['Components']['SDistortionParams'])) { + if (! isset($item['Components']['SDistortionParams'])) { return []; } $basePath = 'Components.SDistortionParams.'; return array_filter([ - 'decay_rate' => Arr::get($item, $basePath . 'DecayRate'), - 'decay_delay' => Arr::get($item, $basePath . 'DecayDelay'), + 'decay_rate' => Arr::get($item, $basePath.'DecayRate'), + 'decay_delay' => Arr::get($item, $basePath.'DecayDelay'), - 'maximum' => Arr::get($item, $basePath . 'Maximum'), + 'maximum' => Arr::get($item, $basePath.'Maximum'), - 'overload_ratio' => Arr::get($item, $basePath . 'OverloadRatio'), + 'overload_ratio' => Arr::get($item, $basePath.'OverloadRatio'), - 'warning_ratio' => Arr::get($item, $basePath . 'WarningRatio'), - 'recovery_ratio' => Arr::get($item, $basePath . 'RecoveryRatio'), - 'recovery_time' => Arr::get($item, $basePath . 'RecoveryTime'), + 'warning_ratio' => Arr::get($item, $basePath.'WarningRatio'), + 'recovery_ratio' => Arr::get($item, $basePath.'RecoveryRatio'), + 'recovery_time' => Arr::get($item, $basePath.'RecoveryTime'), ], static function ($entry) { return $entry !== null; }); @@ -118,14 +119,13 @@ private static function addDistortionData(Collection $item): array private static function addInteractionData(Collection $item): array { - if (!isset($item['Components']['SEntityInteractableParams'])) { + if (! isset($item['Components']['SEntityInteractableParams'])) { return []; } $basePath = 'Components.SEntityInteractableParams.Interactable.SharedInteractions'; - - return collect(Arr::get($item, $basePath))->map(fn(array $interaction) => $interaction['Name']) + return collect(Arr::get($item, $basePath))->map(fn (array $interaction) => $interaction['Name']) ->unique() ->map('trim') ->map('strtolower') diff --git a/app/Services/Parser/SC/Loot/Archetype.php b/app/Services/Parser/SC/Loot/Archetype.php new file mode 100644 index 000000000..92490e33c --- /dev/null +++ b/app/Services/Parser/SC/Loot/Archetype.php @@ -0,0 +1,93 @@ +archetype = collect(json_decode($items, true, 512, JSON_THROW_ON_ERROR)); + } + + public function getData(): ?array + { + $excludedTags = $this->get('excludedTags.tags'); + + return [ + 'class_name' => $this->archetype->get('ClassName'), + 'uuid' => $this->archetype->get('__ref'), + 'primary_groups' => $this->getPrimaryGroups(), + 'secondary_groups' => $this->getSecondaryGroups(), + 'excluded_tags' => collect($excludedTags ?? [])->map(fn (array $tag) => $tag['value'])->toArray(), + ]; + } + + private function getPrimaryGroups(): array + { + if ($this->get('primaryOrGroup.entries') === null) { + return []; + } + + return collect($this->get('primaryOrGroup.entries'))->map(function (array $entry) { + $stackSize = Arr::get($entry, 'optionalData.EntryOptionalData_StackSize'); + $spawnWith = Arr::get($entry, 'optionalData.EntryOptionalData_SpawnWith'); + + return [ + 'name' => $entry['name'], + 'tag' => $entry['tag'], + 'weight' => $entry['weight'], + 'positive_tags' => collect(Arr::get($entry, 'additionalTags.positiveTags', []))->map(fn (array $tag) => $tag['value'])->toArray(), + 'negative_tags' => collect(Arr::get($entry, 'additionalTags.negativeTags', []))->map(fn (array $tag) => $tag['value'])->toArray(), + 'stack_size' => $stackSize !== null ? [ + 'min' => $stackSize['min'], + 'max' => $stackSize['max'], + ] : [], + 'spawn_with' => $spawnWith !== null ? [ + 'name' => $spawnWith['name'], + 'mode' => $spawnWith['mode'], + 'min' => $spawnWith['min'], + 'max' => $spawnWith['max'], + 'positive_tags' => collect(Arr::get($spawnWith, 'tagsToMatch.positiveTags') ?? [])->map(fn (array $tag) => $tag['value'])->toArray(), + 'negative_tags' => collect(Arr::get($spawnWith, 'tagsToMatch.negativeTags') ?? [])->map(fn (array $tag) => $tag['value'])->toArray(), + ] : null, + ]; + })->toArray(); + } + + private function getSecondaryGroups(): array + { + if ($this->get('secondaryOrGroups.LootArchetypeOrGroup_Secondary.entries') === null) { + return []; + } + + return collect($this->get('secondaryOrGroups.LootArchetypeOrGroup_Secondary.entries'))->map(function (array $entry) { + return [ + 'tag' => $entry['tag'], + 'weight' => $entry['weight'], + ]; + })->toArray(); + } + + private function get(string $key, $default = null): mixed + { + return Arr::get($this->archetype, $key, $default); + } +} diff --git a/database/migrations/base_structure/sc/2024_04_22_194116_create_sc_entity_tags_table.php b/database/migrations/base_structure/sc/2024_04_22_194116_create_sc_entity_tags_table.php new file mode 100644 index 000000000..4c1292840 --- /dev/null +++ b/database/migrations/base_structure/sc/2024_04_22_194116_create_sc_entity_tags_table.php @@ -0,0 +1,28 @@ +id(); + $table->uuid('tag'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_entity_tags'); + } +}; diff --git a/database/migrations/base_structure/sc/items/2024_04_22_194242_create_sc_item_entity_tag_table.php b/database/migrations/base_structure/sc/items/2024_04_22_194242_create_sc_item_entity_tag_table.php new file mode 100644 index 000000000..b247f13fb --- /dev/null +++ b/database/migrations/base_structure/sc/items/2024_04_22_194242_create_sc_item_entity_tag_table.php @@ -0,0 +1,37 @@ +unsignedBigInteger('item_id'); + $table->unsignedBigInteger('entity_tag_id'); + + $table->foreign('item_id', 'sc_i_e_tag_item_id') + ->references('id') + ->on('sc_items') + ->onDelete('cascade'); + + $table->foreign('entity_tag_id', 'sc_i_e_tag_tag_id') + ->references('id') + ->on('sc_entity_tags') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_item_entity_tag'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_22_193714_create_sc_loot_archetypes_table.php b/database/migrations/base_structure/sc/loot/2024_04_22_193714_create_sc_loot_archetypes_table.php new file mode 100644 index 000000000..60a2e7a38 --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_22_193714_create_sc_loot_archetypes_table.php @@ -0,0 +1,30 @@ +id(); + $table->uuid(); + $table->unsignedBigInteger('tag_id'); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_archetypes'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_22_193727_create_sc_loot_tables_table.php_ b/database/migrations/base_structure/sc/loot/2024_04_22_193727_create_sc_loot_tables_table.php_ new file mode 100644 index 000000000..74f6f7c8b --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_22_193727_create_sc_loot_tables_table.php_ @@ -0,0 +1,27 @@ +id(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_tables'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_22_200214_create_sc_loot_primary_groups_table.php b/database/migrations/base_structure/sc/loot/2024_04_22_200214_create_sc_loot_primary_groups_table.php new file mode 100644 index 000000000..cab5033be --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_22_200214_create_sc_loot_primary_groups_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('archetype_id'); + $table->uuid('tag'); + $table->unsignedBigInteger('tag_id'); + $table->string('name'); + $table->double('weight'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_primary_groups'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_23_193502_create_sc_loot_primary_group_entity_tag_table.php b/database/migrations/base_structure/sc/loot/2024_04_23_193502_create_sc_loot_primary_group_entity_tag_table.php new file mode 100644 index 000000000..9d7fed676 --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_23_193502_create_sc_loot_primary_group_entity_tag_table.php @@ -0,0 +1,28 @@ +unsignedInteger('primary_group_id'); + $table->unsignedInteger('entity_tag_id'); + $table->boolean('is_positive'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_primary_group_entity_tag'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_23_193822_create_sc_loot_spawn_withs_table.php b/database/migrations/base_structure/sc/loot/2024_04_23_193822_create_sc_loot_spawn_withs_table.php new file mode 100644 index 000000000..547ab6e0a --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_23_193822_create_sc_loot_spawn_withs_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('primary_group_id'); + $table->string('name'); + $table->string('mode'); + $table->unsignedInteger('min'); + $table->unsignedInteger('max'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_spawn_withs'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_23_194432_create_sc_loot_spawn_with_entity_tag_table.php b/database/migrations/base_structure/sc/loot/2024_04_23_194432_create_sc_loot_spawn_with_entity_tag_table.php new file mode 100644 index 000000000..40c7be62f --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_23_194432_create_sc_loot_spawn_with_entity_tag_table.php @@ -0,0 +1,28 @@ +unsignedInteger('spawn_with_id'); + $table->unsignedInteger('entity_tag_id'); + $table->boolean('is_positive'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_spawn_with_entity_tag'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_23_194503_create_sc_loot_stack_sizes_table.php b/database/migrations/base_structure/sc/loot/2024_04_23_194503_create_sc_loot_stack_sizes_table.php new file mode 100644 index 000000000..4df973394 --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_23_194503_create_sc_loot_stack_sizes_table.php @@ -0,0 +1,30 @@ +id(); + $table->unsignedBigInteger('primary_group_id'); + $table->unsignedInteger('min'); + $table->unsignedInteger('max'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_stack_sizes'); + } +}; diff --git a/database/migrations/base_structure/sc/loot/2024_04_23_194603_create_sc_loot_secondary_groups_table.php b/database/migrations/base_structure/sc/loot/2024_04_23_194603_create_sc_loot_secondary_groups_table.php new file mode 100644 index 000000000..cb3e292b4 --- /dev/null +++ b/database/migrations/base_structure/sc/loot/2024_04_23_194603_create_sc_loot_secondary_groups_table.php @@ -0,0 +1,30 @@ +id(); + $table->unsignedBigInteger('archetype_id'); + $table->uuid('tag'); + $table->double('weight'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sc_loot_secondary_groups'); + } +};