Merge pull request #60 from Deathgarden-Rebirth/feature-filemanager_history

Feature file manager history
This commit is contained in:
Patrik Leifert 2025-08-03 22:22:26 +02:00 committed by GitHub
commit 45fa0f2483
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
115 changed files with 7681 additions and 2882 deletions

View File

@ -40,36 +40,55 @@ abstract class CharacterItemConfig
protected static array $additionalPowers = [];
public static function getCharacterId(): UuidInterface {
public static function getCharacterId(): UuidInterface
{
return Uuid::fromHexadecimal(new Hexadecimal(static::$characterId));
}
public static function getDefaultEquippedPerks(): array {
public static function getDefaultEquippedPerks(): array
{
return static::$defaultEquippedPerks;
}
public static function getDefaultEquippedWeapons(): array {
return static::$defaultEquippedWeapons;
public static function getDefaultEquippedWeapons(): array
{
return [
...static::$defaultEquippedWeapons,
];
}
public static function getDefaultEquipment(): array {
public static function getDefaultEquipment(): array
{
return static::$defaultEquipment;
}
public static function getDefaultEquippedBonuses(): array {
public static function getDefaultEquippedBonuses(): array
{
return static::$defaultEquippedBonuses;
}
public static function getDefaultPowers()
public static function getDefaultPowers(): array
{
return static::$defaultEquippedPowers;
return [
...static::$defaultEquippedPowers,
...static::$additionalPowers,
];
}
public static function getAllowedPerks(): array {
public static function getDefaultWeapons(): array {
return [
...static::$defaultEquippedWeapons,
...static::$additionalWeapons,
];
}
public static function getAllowedPerks(): array
{
return [...static::$additionalPerks, ...static::$defaultEquippedPerks];
}
public static function getAllowedWeapons(): array {
public static function getAllowedWeapons(): array
{
return [...static::$additionalWeapons, ...static::$defaultEquippedWeapons];
}

View File

@ -14,8 +14,8 @@ class InquisitorItemConfig extends CharacterItemConfig
];
protected static array $defaultEquippedWeapons = [
'50D3005B437066E4C4D99F9397CF1B0B',
'48072EAA49C83C6F387236955B3C7B6E',
'46B5D90B466F5CB8D13035BEEB11774D',
'5925AB494B3E90DD520E6B8A418B4AEF',
];
protected static array $defaultEquipment = [
@ -102,36 +102,36 @@ class InquisitorItemConfig extends CharacterItemConfig
];
protected static array $additionalWeapons = [
'0A9DB5BC418E1EE3F256D59129BB4E5B',
'2BBB9DD842102F0818864F83FDDE8577',
'D2BF15FC4BC92D230DF5DD983DFC2ED4',
'D9F4022445A9905B8D37429F870F911E',
'BCB675974E4757CA9CD2ACB80AAD4881',
'1808EF1C4A86E2E984522D9763791771',
'877F8AE8438318FC69F238950D41FFB7',
'5691AC1B43E8AC7606D59BACC213FDB1',
'472DB7284944C0E569411DBB33C07107',
'792590BE451B239CC0E6838C3B2E0C11',
'3540E3DC4010A6EE61C1CE8002EE4633',
'973C9176404A29F926D13BACB76A2425',
'46B5D90B466F5CB8D13035BEEB11774D',
'50D3005B437066E4C4D99F9397CF1B0B',
'682EC6AF444E8B15035489B7B59D7026',
'C84FCD1A4E518AC0C520A89F25870D83',
'91E4726C472DAAD995C0EB8E7ADB28CD',
'80A2BF864A6E743B95D8A8BBA20E286E',
'80B0BA974566289F2819AA85B5F50969',
'91E4726C472DAAD995C0EB8E7ADB28CD',
'C84FCD1A4E518AC0C520A89F25870D83',
'0A9DB5BC418E1EE3F256D59129BB4E5B',
'EFC6A5BE442C2554DB9D84AC5F2F0F5C',
'1054BCA742B538D9EF7C5482B6A94B51',
'48072EAA49C83C6F387236955B3C7B6E',
'4F5758F14D7404EB36CEB79D1917F35A',
'557039DA4846C1D85F63E98BE618E1BE',
'682EC6AF444E8B15035489B7B59D7026',
'2BBB9DD842102F0818864F83FDDE8577',
'50D3005B437066E4C4D99F9397CF1B0B',
'5925AB494B3E90DD520E6B8A418B4AEF',
'5CBB9D9740F8A4B0CF8A63AF7F336C8F',
'645074744007206132D20DAD7BE60D60',
'CE9B843E414FBE382247C48F935CC5FA',
'D40B955E444987490620FE978FC8DB07',
'E949314146FBD7739DACF2AB17F2A8E2',
'1E9F4B0B411B0CFF9009638EFBBB68BB',
'31E332854F033E7370DB219F194871FA',
'42029CB64E6D0E28CF818ABF164C7DE4',
'4D4481224066F26EB62F08957BAAB7F8',
'7A70DC4F40A5BC51F3944CAADE0D434B',
'B83A141A45FB8D96D48A5185CD607AA3',
'E4B6A31141FB3AF9271E18882CFC0DB6',
'EC7BE7FF44840117459D8FBE2AE5BE27',
'F8240052491F02CD1F1B73854173F0EB',
'FB23E04C4730F3451388DF8CD2FAF31A',
'D40B955E444987490620FE978FC8DB07',
'557039DA4846C1D85F63E98BE618E1BE',
'5CBB9D9740F8A4B0CF8A63AF7F336C8F',
'4F5758F14D7404EB36CEB79D1917F35A',
'1054BCA742B538D9EF7C5482B6A94B51',
'CE9B843E414FBE382247C48F935CC5FA',
'48072EAA49C83C6F387236955B3C7B6E',
];
protected static array $additionalPowers = [

View File

@ -28,7 +28,7 @@ class PoacherItemConfig extends CharacterItemConfig
];
protected static array $defaultEquippedBonuses = [
'4431395744543533907B099952F81510',
'0FBE5C46F94E906875E84BA3C115B9B5',
'6BE28177483A89CF00B2FD839726ACE1',
];

View File

@ -28,8 +28,8 @@ class StalkerItemConfig extends CharacterItemConfig
];
protected static array $defaultEquippedBonuses = [
'791F12E047DA9E26E246E3859C3F587E',
'8A5BF2274640C2F23EF3C996A6F6404D',
'545F88D24EAD402513C4948DE31DD6AE',
];
protected static array $defaultEquippedPowers = [
@ -102,36 +102,36 @@ class StalkerItemConfig extends CharacterItemConfig
];
protected static array $additionalWeapons = [
'065629E04A181ADD3062169AAFB88F43',
'0D96281E4440DF505B9CA8B1457F50D8',
'1727C5E74C3C7454A6CC0FB1F7562CE2',
'AB2F540D448B533998EC38B8ABAFFD35',
'1BB15A214C78FBC23E74369682117834',
'2C03CA70439065116446DA9F6F347900',
'364665404243311408F6A0BD4DCE05BD',
'858747E74B7F30A0F6C77888D2D255AF',
'8CE61C214F9BD049028EF2B896D3B161',
'912C2B42486993E01D0B9DB6F1D1E408',
'AB2F540D448B533998EC38B8ABAFFD35',
'192EC9DD4F40FE3C66F8A2AD84568769',
'1945D4B24A31EB6C6CA508A4CDFBB44F',
'1B51D59C47F565A20E743BA187B83642',
'71F194C145E9629058957E9183C5C9C6',
'773C61C84B175DB0314B809DC142739A',
'7FD66FCD42766C53D2CB42A7A611B88E',
'86711D8347A60ED764BE78A051CD4C11',
'9AA46980454C38B8CBC1CE87F7E04F37',
'CA7D61574EB6F0AE1D6D39A4EDD719D7',
'D4CBB3C9425AB53EB481558BED82AE7C',
'0FDFC87D45DF769282EA1FA7AB57E409',
'8CE61C214F9BD049028EF2B896D3B161',
'858747E74B7F30A0F6C77888D2D255AF',
'0D96281E4440DF505B9CA8B1457F50D8',
'065629E04A181ADD3062169AAFB88F43',
'1727C5E74C3C7454A6CC0FB1F7562CE2',
'364665404243311408F6A0BD4DCE05BD',
'169446014AA2A782E51ECFAF9EF33A39',
'307A0B13417737DED675309F8B978AB8',
'3D535D304B5E5113CA31D2838C840129',
'4E4E95BB4EBD26E7CFA0E489979DA0CC',
'5DE495E742FB3EB0FBEF92BDE718E59F',
'656CB0C644C6677C470D21AD0EA8437A',
'8C365CF442AE795157A187B017E4D17F',
'A9349C774A13D353C0F315BB7B979324',
'C55D9EFF46EB08ABDD8467BC23969E53',
'656CB0C644C6677C470D21AD0EA8437A',
'3D535D304B5E5113CA31D2838C840129',
'0FDFC87D45DF769282EA1FA7AB57E409',
'307A0B13417737DED675309F8B978AB8',
'773C61C84B175DB0314B809DC142739A',
'9AA46980454C38B8CBC1CE87F7E04F37',
'71F194C145E9629058957E9183C5C9C6',
'7FD66FCD42766C53D2CB42A7A611B88E',
'D4CBB3C9425AB53EB481558BED82AE7C',
'CA7D61574EB6F0AE1D6D39A4EDD719D7',
'192EC9DD4F40FE3C66F8A2AD84568769',
'86711D8347A60ED764BE78A051CD4C11',
'1945D4B24A31EB6C6CA508A4CDFBB44F',
'1B51D59C47F565A20E743BA187B83642',
];
protected static array $additionalPowers = [

View File

@ -28,8 +28,8 @@ class VeteranItemConfig extends CharacterItemConfig
];
protected static array $defaultEquippedBonuses = [
'172080544A05F838E2473790FDF4873A',
'3D377249421D35F0F750578919A7E210',
'791F12E047DA9E26E246E3859C3F587E',
'03783FB54D88B7D88DD4509FCBBAFE68',
];
protected static array $defaultEquippedPowers = [
@ -102,36 +102,37 @@ class VeteranItemConfig extends CharacterItemConfig
];
protected static array $additionalWeapons = [
'479BE509436547C4222CF6BB01802009',
'6562B26B48C9C791C82A3EAE344EBEE1',
'6F3359C54870B7B8A4D7B9A8C561D001',
'90F742AD4BDE1CBF81B4B0BE57271C9B',
'A17BA09141027848493CAA9D5EDC9D72',
'A87672CA4D2BBFF35E273C82F2524278',
'B02BE54A4758C5169593BC9EFF9DA028',
'BBA2090F49BA1F26443400AC8ABD6A7D',
'EC7BE7FF44840117459D8FBE2AE5BE27',
'4D4481224066F26EB62F08957BAAB7F8',
'1E9F4B0B411B0CFF9009638EFBBB68BB',
'31E332854F033E7370DB219F194871FA',
'7A70DC4F40A5BC51F3944CAADE0D434B',
'42029CB64E6D0E28CF818ABF164C7DE4',
'FB23E04C4730F3451388DF8CD2FAF31A',
'F8240052491F02CD1F1B73854173F0EB',
'E4B6A31141FB3AF9271E18882CFC0DB6',
'B83A141A45FB8D96D48A5185CD607AA3',
'EAF5179F4F62DCC06CE3E29F4E86207E',
'479BE509436547C4222CF6BB01802009',
'F317A5504A82074040F104A0AE68B360',
'0606F8464D4C7EB70601CC84C50BCAC6',
'22C48CEE49DC29EED48A82A7423DCCE6',
'A17BA09141027848493CAA9D5EDC9D72',
'6F3359C54870B7B8A4D7B9A8C561D001',
'BBA2090F49BA1F26443400AC8ABD6A7D',
'B02BE54A4758C5169593BC9EFF9DA028',
'A87672CA4D2BBFF35E273C82F2524278',
'90F742AD4BDE1CBF81B4B0BE57271C9B',
'6562B26B48C9C791C82A3EAE344EBEE1',
'23C4DCE74DB1D6CDC9147587F5C42450',
'369D5D0C4175FD4445EF67BEF060B270',
'377368444AFD0D25687CEBA2B721CEA9',
'4BCE89A640F8687625ABF58B35A734AC',
'4D3110C745832FC4FAD3CDBB2988CDD3',
'D33211F643BF66DE32E555928266B97A',
'EC2B43B6444929A1346A12B64D4DC8D3',
'369D5D0C4175FD4445EF67BEF060B270',
'D33211F643BF66DE32E555928266B97A',
'4D3110C745832FC4FAD3CDBB2988CDD3',
'4BCE89A640F8687625ABF58B35A734AC',
'377368444AFD0D25687CEBA2B721CEA9',
'FF5ADA454579CD95C8536DB944C75F24',
'1808EF1C4A86E2E984522D9763791771',
'3540E3DC4010A6EE61C1CE8002EE4633',
'472DB7284944C0E569411DBB33C07107',
'5691AC1B43E8AC7606D59BACC213FDB1',
'792590BE451B239CC0E6838C3B2E0C11',
'877F8AE8438318FC69F238950D41FFB7',
'973C9176404A29F926D13BACB76A2425',
'BCB675974E4757CA9CD2ACB80AAD4881',
'D2BF15FC4BC92D230DF5DD983DFC2ED4',
'D9F4022445A9905B8D37429F870F911E',
'22C48CEE49DC29EED48A82A7423DCCE6',
'0606F8464D4C7EB70601CC84C50BCAC6',
];
protected static array $additionalPowers = [

View File

@ -94,26 +94,26 @@ class DashItemConfig extends CharacterItemConfig
];
protected static array $additionalWeapons = [
'097521AE4B974026F31B92BBAD436A38',
'2331ED814F30635500FEDB96A0674A3A',
'272369C042147225E364CFA42947859F',
'36B2650B4564CC7E7ED310AD3FCA0D75',
'66DAA6C946CA0A7C36314A94164B78BF',
'98BCBE234BF7CCB50DC4A78CA78F8672',
'9B6943CD4DF6CD2FCD94CB89E6F55380',
'097521AE4B974026F31B92BBAD436A38',
'36B2650B4564CC7E7ED310AD3FCA0D75',
'A3FF5F7B4D2657164847319A2E52A0FD',
'BE76A25F44710E8AD011B2B74ED3F3A9',
'98BCBE234BF7CCB50DC4A78CA78F8672',
'D93A7E864CFECE74FDE2D9BA50AB7135',
'5A2DD3F6433AB83A725513B868D240CF',
'240BC7484AF781EBA93C47932DA8D061',
'2920B01748AA9AB4EC6299AD15FF0E6A',
'3636FD2240F282F0921FA68E8F88ABBF',
'2331ED814F30635500FEDB96A0674A3A',
'9B6943CD4DF6CD2FCD94CB89E6F55380',
'BE76A25F44710E8AD011B2B74ED3F3A9',
'272369C042147225E364CFA42947859F',
'BC8866F343E7970E145C889D9CE7B994',
'42795DDF4ED4B7700A6555B586C5AD2B',
'51C1825B4469FF483278DDB12D116E04',
'8B886664468A2878B26813A097F375E5',
'240BC7484AF781EBA93C47932DA8D061',
'B2ECD91349955E809A54DB9B36B7D6CF',
'BC8866F343E7970E145C889D9CE7B994',
'DC91C3414F22F217BCB30590FAB59A9E',
'8B886664468A2878B26813A097F375E5',
'2920B01748AA9AB4EC6299AD15FF0E6A',
'3636FD2240F282F0921FA68E8F88ABBF',
'5A2DD3F6433AB83A725513B868D240CF',
];
protected static array $defaultEquippedPowers = [

View File

@ -26,7 +26,7 @@ class FogItemConfig extends CharacterItemConfig
protected static array $defaultEquippedBonuses = [
'1E08AFFA485E92BAFF2C1BB85CEFB81E',
'1F5CD9004224C56746D81991AA40448A',
'D8305B60C8455B51368681BFD337525A',
];
protected static array $additionalPerks = [

View File

@ -25,8 +25,8 @@ class InkedItemConfig extends CharacterItemConfig
];
protected static array $defaultEquippedBonuses = [
'261144CC43A9F74A60506AB0335B23B2',
'92B767F442A89C87CC3C9CB5D279D6EA',
'CBE325ABBD44C58723A7268F7FB90D49',
];
protected static array $additionalPerks = [
@ -40,16 +40,16 @@ class InkedItemConfig extends CharacterItemConfig
'5AE771024971ED9C8DE3188A30BE3946',
'180B90BC4577469F4D52DA917A7FED6B',
'CB95BEF142FE7EEAA6D9828DE3E4D6EA',
'04650B23493C386FA87E48947D26D79F',
'168A9C7E4AF1C1D321885ABEF7538DE9',
'F329A7724E21169E99739196DFC797D1',
'03B907684E4B8058A274F1A1958A7FB2',
'B245A69F4145075488DD01A477A24385',
'72F7A311494D090473C38C8391AC6F89',
'AD024E8E46E056286FC5F0AB07EC2FA0',
'3A769A1F400DC24C97DF28B8969992E1',
'AC4E66024C3E56B3675EA1B273AB7B6E',
'B377AE6047A4688FE2164FB2E341C525',
'9FCAAC9143A827E79DC179B762B1E520',
'9A9042614697C385E5744C903F5E19C3',
'25B8F52744CAEB17E599888B5E80162E',
'EAC46B4C4979624CD3C15B9189D1CC93',
'2822F9B547761DD69A4853920A5A3A67',
'46686C2F49C88ADD7635A4B3D5B1D594',
'FF5821084098286D2E31E99192BB0988',
'77EF1CAC48919368F529018D6E512B94',
'6BABAFD74690D55F470764A1ACC7DFE1',
'67B8A74643EA6CB8A41457AD5A576D69',
'6340FA564B0C4E692AD174BB743607F5',
'418880784C86E2A81AB395A998B09901',
'75831C73438DA8C3E873339E58FC1674',
@ -90,7 +90,6 @@ class InkedItemConfig extends CharacterItemConfig
'F2F5C6BB4536127CD0D0768422BCF154',
'DFF40D824EDD8D0EF7DC4EBE81E7D121',
'5EEF1C6040DE7AB766F9E48795BD0500',
];
protected static array $additionalWeapons = [

View File

@ -30,16 +30,16 @@ class SawbonesItemConfig extends CharacterItemConfig
];
protected static array $additionalPerks = [
'9FCAAC9143A827E79DC179B762B1E520',
'9A9042614697C385E5744C903F5E19C3',
'25B8F52744CAEB17E599888B5E80162E',
'EAC46B4C4979624CD3C15B9189D1CC93',
'2822F9B547761DD69A4853920A5A3A67',
'46686C2F49C88ADD7635A4B3D5B1D594',
'FF5821084098286D2E31E99192BB0988',
'77EF1CAC48919368F529018D6E512B94',
'6BABAFD74690D55F470764A1ACC7DFE1',
'67B8A74643EA6CB8A41457AD5A576D69',
'04650B23493C386FA87E48947D26D79F',
'168A9C7E4AF1C1D321885ABEF7538DE9',
'F329A7724E21169E99739196DFC797D1',
'03B907684E4B8058A274F1A1958A7FB2',
'B245A69F4145075488DD01A477A24385',
'72F7A311494D090473C38C8391AC6F89',
'AD024E8E46E056286FC5F0AB07EC2FA0',
'3A769A1F400DC24C97DF28B8969992E1',
'AC4E66024C3E56B3675EA1B273AB7B6E',
'B377AE6047A4688FE2164FB2E341C525',
'274EB0B34AB39E468BFA878F7E87465B',
'4AE4801A440EFF8B54760EBFF92B128C',
'42E05FC94E6B09CCBB6208BAF94AE98F',
@ -90,7 +90,6 @@ class SawbonesItemConfig extends CharacterItemConfig
'E3139A504D205C06438904B1ECD68D44',
'E9564AFF410C3F82BD53B7890DA9DB97',
'A1463E12485BA0B2D678EA863BCC8ED9',
];
protected static array $additionalWeapons = [

View File

@ -131,49 +131,61 @@ class CatalogPriceConfig
const PERKS_COST = [
1 => [
'CurrencyA' => 500,
'CurrencyB' => 500,
],
2 => [
'CurrencyB' => 750,
'CurrencyA' => 700,
'CurrencyB' => 800,
],
3 => [
'CurrencyB' => 1000,
'CurrencyA' => 900,
'CurrencyB' => 1200,
],
4 => [
'CurrencyB' => 1250,
'CurrencyA' => 1100,
'CurrencyB' => 1600,
'CurrencyC' => 400,
],
5 => [
'CurrencyB' => 1500,
'CurrencyA' => 1300,
'CurrencyB' => 2000,
'CurrencyC' => 1100,
],
6 => [
'CurrencyA' => 425,
'CurrencyB' => 1300,
'CurrencyA' => 1500,
'CurrencyB' => 2400,
'CurrencyC' => 1700,
],
7 => [
'CurrencyA' => 525,
'CurrencyB' => 1400,
'CurrencyA' => 1700,
'CurrencyB' => 2900,
'CurrencyC' => 2400,
],
8 => [
'CurrencyA' => 625,
'CurrencyB' => 1500,
'CurrencyA' => 1900,
'CurrencyB' => 3400,
'CurrencyC' => 3100,
],
9 => [
'CurrencyA' => 725,
'CurrencyB' => 1600,
'CurrencyA' => 2100,
'CurrencyB' => 4000,
'CurrencyC' => 3900,
],
10 => [
'CurrencyA' => 825,
'CurrencyB' => 1700,
'CurrencyA' => 2300,
'CurrencyB' => 4600,
'CurrencyC' => 4700,
],
];
const SKINS_COST = [
ItemQuality::Basic->value => ['CurrencyC' => 2500],
ItemQuality::Specialized->value => ['CurrencyC' => 3500],
ItemQuality::Rare->value => ['CurrencyC' => 4500],
ItemQuality::Superior->value => ['CurrencyC' => 5500],
ItemQuality::Epic->value => ['CurrencyC' => 6250],
ItemQuality::Ultra->value => ['CurrencyC' => 7500],
ItemQuality::Basic->value => ['CurrencyA' => 800, 'CurrencyC'=> 1200],
ItemQuality::Specialized->value => ['CurrencyA' => 1600, 'CurrencyC' => 1900],
ItemQuality::Rare->value => ['CurrencyA' => 2400, 'CurrencyC' => 2600],
ItemQuality::Superior->value => ['CurrencyA' => 3200, 'CurrencyC' => 3000],
ItemQuality::Epic->value => ['CurrencyA' => 4000, 'CurrencyC' => 5400],
ItemQuality::Ultra->value => ['CurrencyA' => 6000, 'CurrencyC' => 8500],
];
public static function GetCategoryPriceForLevel(CatalogPriceCategory $category, int|string $level): array

View File

@ -0,0 +1,172 @@
<?php
namespace App\Classes\Factory;
use App\Enums\Game\ChallengeType;
use App\Enums\Game\Faction;
use App\Enums\Game\RewardType;
use App\Exceptions\TimedChallengeFactoryException;
use App\Http\Responses\Api\General\Reward;
use App\Models\Game\TimedChallenge;
use Illuminate\Support\Carbon;
abstract class TimedChallengeFactory
{
const AVAILABLE_DAILY_RUNNER = [
'/Game/Challenges/Weekly/Challenge_BleedOut_RunnerWeekly.Challenge_BleedOut_RunnerWeekly' => 1,
'/Game/Challenges/Challenge_JustPlay.Challenge_JustPlay' => 1,
'/Game/Challenges/Challenge_Deliver_Runner.Challenge_Deliver_Runner' => 15,
'/Game/Challenges/Weekly/Challenge_Shields_RunnerWeekly.Challenge_Shields_RunnerWeekly' => 10,
'/Game/Challenges/Progression/General/Challenge_Heal_Runner.Challenge_Heal_Runner.' => 100,
'/Game/Challenges/Progression/General/Challenge_Evade_Runner.Challenge_Evade_Runner' => 25,
] ;
const AVAILABLE_DAILY_HUNTER = [
'/Game/Challenges/Challenge_JustPlay.Challenge_JustPlay' => 1,
'/Game/Challenges/Weekly/Challenge_DroneActivation_HunterWeekly.Challenge_DroneActivation_HunterWeekly' => 5,
'/Game/Challenges/Weekly/Challenge_Headshot_HunterWeekly.Challenge_Headshot_HunterWeekly' => 1,
'/Game/Challenges/Challenge_Down_Hunter.Challenge_Down_Hunter' => 3,
'/Game/Challenges/Weekly/Challenge_InDenial_HunterWeekly.Challenge_InDenial_HunterWeekly' => 3,
];
const AVAILABLE_WEEKLY_RUNNER = [
'/Game/Challenges/Weekly/Challenge_BleedOut_RunnerWeekly.Challenge_BleedOut_RunnerWeekly' => 5,
'/Game/Challenges/Weekly/Challenge_Greed_RunnerWeekly.Challenge_Greed_RunnerWeekly' => 50,
'/Game/Challenges/Weekly/Challenge_Shields_RunnerWeekly.Challenge_Shields_RunnerWeekly' => 100,
'/Game/Challenges/Challenge_Deliver_Runner.Challenge_Deliver_Runner' => 150,
'/Game/Challenges/Weekly/Challenge_SpeedCapture_RunnerWeekly.Challenge_SpeedCapture_RunnerWeekly' => 15,
'/Game/Challenges/Weekly/Challenge_UPs_RunnerWeekly.Challenge_UPs_RunnerWeekly' => 5,
'/Game/Challenges/Weekly/Challenge_Wasteful_RunnerWeekly.Challenge_Wasteful_RunnerWeekly' => 100,
'/Game/Challenges/Challenge_JustPlay.Challenge_JustPlay' => 5,
'/Game/Challenges/Progression/General/Challenge_Heal_Runner.Challenge_Heal_Runner' => 600,
'/Game/Challenges/Progression/General/Challenge_Evade_Runner.Challenge_Evade_Runner' => 150,
];
const AVAILABLE_WEEKLY_HUNTER = [
'/Game/Challenges/Weekly/Challenge_ARB_Damage_HunterWeekly.Challenge_ARB_Damage_HunterWeekly' => 5000,
'/Game/Challenges/Weekly/Challenge_AssaultRifleWins_HunterWeekly.Challenge_AssaultRifleWins_HunterWeekly' => 100,
'/Game/Challenges/Weekly/Challenge_Damage_HunterWeekly.Challenge_Damage_HunterWeekly' => 5000,
'/Game/Challenges/Weekly/Challenge_DroneActivation_HunterWeekly.Challenge_DroneActivation_HunterWeekly' => 25,
'/Game/Challenges/Weekly/Challenge_Greed_HunterWeekly.Challenge_Greed_HunterWeekly' => 50,
'/Game/Challenges/Weekly/Challenge_Headshot_HunterWeekly.Challenge_Headshot_HunterWeekly' => 10,
'/Game/Challenges/Weekly/Challenge_HuntingShotgunWins_HunterWeekly.Challenge_HuntingShotgunWins_HunterWeekly' => 10,
'/Game/Challenges/Weekly/Challenge_InDenial_HunterWeekly.Challenge_InDenial_HunterWeekly' => 20,
'/Game/Challenges/Weekly/Challenge_LMGWins_HunterWeekly.Challenge_LMGWins_HunterWeekly' => 10,
'/Game/Challenges/Weekly/Challenge_Reveals_hunterWeekly.Challenge_Reveals_hunterWeekly' => 10,
'/Game/Challenges/Weekly/Challenge_RingOut_hunterWeekly.Challenge_RingOut_hunterWeekly' => 5,
'/Game/Challenges/Weekly/Challenge_Mines_HunterWeekly.Challenge_Mines_HunterWeekly' => 10,
'/Game/Challenges/Weekly/Challenge_ShotgunDowns_HunterWeekly.Challenge_ShotgunDowns_HunterWeekly' => 10,
'/Game/Challenges/Weekly/Challenge_Wasteful_HunterWeekly.Challenge_Wasteful_HunterWeekly' => 100,
'/Game/Challenges/Challenge_JustPlay.Challenge_JustPlay' => 5,
];
const DAILY_REWARDS = [
'CurrencyA' => [
'min' => 1000,
'max' => 2500,
],
'CurrencyB' => [
'min' => 250,
'max' => 500,
],
'CurrencyC' => [
'min' => 500,
'max' => 750,
],
];
const WEEKLY_REWARDS = [
'CurrencyA' => [
'min' => 3000,
'max' => 5000,
],
'CurrencyB' => [
'min' => 250,
'max' => 350,
],
'CurrencyC' => [
'min' => 600,
'max' => 900,
],
];
/**
* Makes a new TimedChallenge Instance that's not yet saved to the Database.
*
* @param Carbon $startTime
* @param Faction $faction
* @param ChallengeType $type
* @return void
* @throws TimedChallengeFactoryException
*/
public static function makeChallenge(
Carbon $startTime,
Faction $faction,
ChallengeType $type,
): TimedChallenge
{
[$blueprintPath, $completionValue] = static::pickChallenge($faction, $type);
$challenge = new TimedChallenge();
$challenge->type = $type;
$challenge->faction = $faction;
$challenge->blueprint_path = $blueprintPath;
$challenge->completion_value = $completionValue;
$challenge->start_time = $startTime;
if($type === ChallengeType::Daily)
$challenge->end_time = $startTime->copy()->addDay();
else
$challenge->end_time = $startTime->copy()->addWeek();
$challenge->rewards = [static::pickReward($type)];
return $challenge;
}
protected static function pickChallenge(
Faction $faction,
ChallengeType $type,
): array {
$challengeArray = match ($faction) {
Faction::Hunter => $type === ChallengeType::Daily ? static::AVAILABLE_DAILY_HUNTER : static::AVAILABLE_WEEKLY_HUNTER,
Faction::Runner => $type === ChallengeType::Daily ? static::AVAILABLE_DAILY_RUNNER : static::AVAILABLE_WEEKLY_RUNNER,
default => null,
};
if($challengeArray === null)
throw new TimedChallengeFactoryException('Unallowed Faction ('. $faction->value .'), could not select a challenge.');
$blueprintPath = array_rand($challengeArray);
$completionValue = $challengeArray[$blueprintPath];
return [$blueprintPath, $completionValue];
}
/**
* @param ChallengeType $type
* @return Reward
* @throws TimedChallengeFactoryException
*/
protected static function pickReward(
ChallengeType $type,
): Reward {
$rewardsArray = match ($type) {
ChallengeType::Daily => static::DAILY_REWARDS,
ChallengeType::Weekly => static::WEEKLY_REWARDS,
default => null,
};
if($rewardsArray === null)
throw new TimedChallengeFactoryException('Unallowed Challenge Type (' . $type->value . '), could not select a reward.');
$pickedReward = array_rand($rewardsArray);
return new Reward(
RewardType::Currency,
round(rand($rewardsArray[$pickedReward]['min'], $rewardsArray[$pickedReward]['max']) / 10) * 10,
$pickedReward,
);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Classes\Frontend;
use App\View\Components\Auth\User;
use Illuminate\Support\Carbon;
use JsonSerializable;
class ChatMessage implements JsonSerializable
{
public function __construct(
public string $gameId,
public string $userId,
public Carbon $messageTime,
public string $message,
)
{}
public function getUser(): ?\App\Models\User\User
{
return \App\Models\User\User::find($this->userId);
}
public function jsonSerialize(): array
{
return [
'gameId' => $this->gameId,
'userId' => $this->userId,
'messageTime' => $this->messageTime->toJSON(),
'message' => $this->message,
];
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use DirectoryIterator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Str;
class CleanupLogs extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:cleanup-logs';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Cleanup old log files.';
// Delete files older than this number of days.
const FILE_DAYS_OLD = 14;
// Delete session files older than this number of days.
const SESSION_FILE_DAYS_OLD = 7;
/**
* Execute the console command.
*/
public function handle()
{
$disk = Storage::disk('logs');
$files = $disk->allFiles();
$now = Carbon::now();
// Delete older modified files
foreach ($files as $file) {
$lastModified = Carbon::createFromTimestamp($disk->lastModified($file));
if(Str::startsWith($file, 'sessions/'))
$shouldDelete = $now->diff($lastModified)->total('days') * -1 > self::SESSION_FILE_DAYS_OLD;
else
$shouldDelete = $now->diff($lastModified)->total('days') * -1 > self::FILE_DAYS_OLD;
if($shouldDelete)
$disk->delete($file);
}
//Loop over all directories to delete empty ones.
$directories = $disk->allDirectories();
foreach ($directories as $directory) {
$files = $disk->allFiles($directory);
if(count($files) === 0)
$disk->deleteDirectory($directory);
}
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Console\Commands;
use App\Classes\Factory\TimedChallengeFactory;
use App\Enums\Game\ChallengeType;
use App\Enums\Game\Faction;
use App\Enums\Game\RewardType;
use App\Exceptions\TimedChallengeFactoryException;
use App\Http\Responses\Api\General\Reward;
use App\Models\Game\TimedChallenge;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
class GenerateTimedChallenges extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:generate-timed-challenges';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate the Daily and Weekly challenges for the upcoming day/week';
const INTERVAL_HOUR = 21;
const INTERVAL_MINUTE = 0;
const WEEKLY_INTERVAL_DAY = 'tuesday';
/**
* Execute the console command.
*/
public function handle()
{
$dailyToday = Carbon::today();
$dailyToday->setTime(static::INTERVAL_HOUR, static::INTERVAL_MINUTE);
// Put the current Daily a day in the past when the current time the job runs is still before the new Daily
if(Carbon::now()->isBefore($dailyToday))
$dailyToday->subDay();
$dailyTomorrow = $dailyToday->copy()->addDay();
$currentWeekly = Carbon::parse('last '.static::WEEKLY_INTERVAL_DAY);
$currentWeekly->setTime(static::INTERVAL_HOUR, static::INTERVAL_MINUTE);
$nextWeekly = $currentWeekly->copy()->addWeek();
$dailys = [$dailyToday, $dailyTomorrow];
$weeklys = [$currentWeekly, $nextWeekly];
$log = Log::channel('challengeCreation');
foreach ([Faction::Hunter, Faction::Runner] as $faction) {
foreach ($dailys as $time) {
$dailyExists = TimedChallenge::whereDate('start_time', $time)
->where('type', ChallengeType::Daily)
->where('faction', $faction)
->exists();
if(!$dailyExists) {
try {
$newDaily = TimedChallengeFactory::makeChallenge($time, $faction, ChallengeType::Daily);
$newDaily->save();
} catch (TimedChallengeFactoryException $e) {
$log->error($e->getMessage());
}
}
}
foreach ($weeklys as $time) {
$weeklyExists = TimedChallenge::whereDate('start_time', $time)
->where('type', ChallengeType::Weekly)
->where('faction', $faction)
->exists();
if(!$weeklyExists) {
try {
$newDaily = TimedChallengeFactory::makeChallenge($time, $faction, ChallengeType::Weekly);
$newDaily->save();
} catch (TimedChallengeFactoryException $e) {
$log->error($e->getMessage());
}
}
}
}
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use App\Classes\Character\CharacterItemConfig;
use App\Enums\Game\Characters;
use App\Helper\Uuid\UuidHelper;
use App\Models\User\PlayerData;
use App\Models\User\User;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
class GiveAllUsersDefaultItems extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:give-all-users-default-items {userId?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
$userId = $this->argument('userId');
if($userId === null)
$playerDatas = PlayerData::all();
else
$playerDatas = [User::findOrFail($userId)->playerData()];
foreach ($playerDatas as $playerData) {
foreach (Characters::cases() as $case) {
$this->info('Adding items for user ' . $playerData->user_id . ' for Character ' . $case->value);
$configClass = $case->getCharacter()->getItemConfigClass();
$this->addItemsToUser($playerData, $configClass);
}
}
}
/**
* @param PlayerData $user
* @param class-string<CharacterItemConfig> $config
* @return void
*/
public function addItemsToUser(PlayerData &$playerData, string $config): void {
$itemIds = [
...$config::getDefaultEquippedBonuses(),
...$config::getDefaultEquippedWeapons(),
...$config::getDefaultEquipment(),
...$config::getDefaultPowers(),
...$config::getDefaultWeapons(),
...$config::getDefaultEquippedPerks(),
];
$itemIds = UuidHelper::convertFromHexToUuidCollecton($itemIds, true);
try {
$playerData->inventory()->syncWithoutDetaching($itemIds);
} catch (QueryException $e) {
Log::channel('single')->error($e->getMessage());
$this->error('Exception: ' . $e->getMessage());
$this->error("Failed to add items to inventory: " . json_encode($itemIds, JSON_PRETTY_PRINT));
}
}
}

View File

@ -123,6 +123,45 @@ private function importChallenges(): void {
}
}
$achievementChallenges = [
[
'id' => 'e51981b9-46be-e3d4-5c5c-41b2fcff310b',
'value' => 500,
'path' => '/Game/Challenges/Challenge_Deliver_Runner.Challenge_Deliver_Runner',
],
[
'id' => 'efab89e6-465d-1163-d62a-07b11048f2b6',
'value' => 50,
'path' => '/Game/Challenges/Challenge_JustPlay.Challenge_JustPlay',
],
[
'id' => '2caebb35-4d50-6d7c-43b9-41bc1da775a0',
'value' => 10,
'path' => '/Game/Challenges/Progression/General/Challenge_Exit_Runner.Challenge_Exit_Runner',
],
[
'id' => 'aad05b9d-4647-1dc8-11bb-e0ba91916ab7',
'value' => 50,
'path' => '/Game/Challenges/Challenge_Down_Hunter.Challenge_Down_Hunter',
],
[
'id' => 'ba2d4a54-45cb-7027-6a8f-5d9e1afce080',
'value' => 100,
'path' => '/Game/Challenges/Challenge_DroneCharger_Hunter.Challenge_DroneCharger_Hunter',
],
];
foreach ($achievementChallenges as $challenge) {
$newChallenge = Challenge::findOrNew($challenge['id']);
$newChallenge->id = $challenge['id'];
$newChallenge->completion_value = $challenge['value'];
$newChallenge->asset_path = $challenge['path'];
$this->info('Importing Achievement Challenge: '.$challenge['id']);
$newChallenge->save();
}
$this->info('Finished importing challenges.');
}

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Enums\Game\Faction;
use App\Enums\Game\Matchmaking\MatchStatus;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\Matchmaking\QueuedPlayer;
@ -39,7 +40,7 @@ class MatchmakingCleanup extends Command
/**
* Execute the console command.
*/
public function handle()
public function handle(): void
{
$log = Log::channel('matchmaking_cleanup');
@ -50,12 +51,18 @@ public function handle()
$log->info('Queued players: ' . json_encode($cleanedQueuedPlayers, JSON_PRETTY_PRINT));
$deletedClosedGames = Game::where('status', '=', MatchStatus::Closed->value)
$closedGamesToKill = Game::where('status', '=', MatchStatus::Closed->value)
->where('updated_at', '<', Carbon::now()->subSeconds(static::GAME_MAX_TIME * 60))
->delete();
->get();
$log->info('Deleted Closed games: ' . json_encode($deletedClosedGames, JSON_PRETTY_PRINT));
foreach ($closedGamesToKill as $game) {
//archive the games set to closed, so we don't lose the chat history
$game->archiveGame(Faction::None);
$game->status = MatchStatus::Killed;
$game->save();
}
$log->info('Closed games set to Killed: ' . json_encode($closedGamesToKill->getQueueableIds(), JSON_PRETTY_PRINT));
// delete games where the hunter crashed on loading into the arena, leaving the game stuck at created
$deletedCreatedGames = Game::where('status', '=', MatchStatus::Created)

View File

@ -5,16 +5,23 @@
use App\Classes\Matchmaking\MatchmakingPlayerCount;
use App\Enums\Game\Matchmaking\MatchmakingSide;
use App\Enums\Game\Matchmaking\MatchStatus;
use App\Models\Admin\MatchmakingSettings;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\Matchmaking\MatchConfiguration;
use App\Models\Game\Matchmaking\QueuedPlayer;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Log;
use Psr\Log\LoggerInterface;
class ProcessMatchmaking extends Command
{
public static int $repeatTimeSeconds = 10;
const ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY = 'matchmaking_attempt_1v4_1v5';
/**
* The name and signature of the console command.
*
@ -36,6 +43,7 @@ class ProcessMatchmaking extends Command
*/
public function handle(): void
{
$matchmakingSettings = MatchmakingSettings::get();
// Select all queued Players/party leaders, descending by party size
$players = QueuedPlayer::withCount('followingUsers')
->sharedLock()
@ -78,12 +86,33 @@ public function handle(): void
));
$playerCount = $this->getTotalPlayersCount($players);
// If we only have one Hunter and could make a 1v4 or 1v5 we want to wait a bit before making a match.
// because there could be some runners not being fast enough in queue or the matchmaking command running unfortunatly while players are queuing up.
if ($playerCount->hunters === 1 && ($playerCount->runners === 4 || $playerCount->runners === 5)) {
if (Cache::has(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY)) {
/** @var Carbon $firstAttempt */
$firstAttempt = Cache::get(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);
if ($firstAttempt->diffInSeconds(Carbon::now()) < $matchmakingSettings->matchWaitingTime){
return;
}
Cache::forget(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);
}
else {
Cache::set(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY, Carbon::now());
return;
}
}
else {
Cache::forget(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);
}
$availableMatchConfigs = MatchConfiguration::getAvailableMatchConfigs($playerCount->runners, $playerCount->hunters);
if($availableMatchConfigs->isEmpty())
return;
$selectedConfig = MatchConfiguration::selectRandomConfigByWeight($availableMatchConfigs);
$selectedConfig = MatchConfiguration::selectMatchConfig($availableMatchConfigs, $playerCount->runners, $playerCount->hunters);
// Should never happen, but just to be careful
if($selectedConfig === null)

View File

@ -0,0 +1,73 @@
<?php
namespace App\Console\Commands;
use App\Models\Admin\CurrencyMultipliers;
use App\Models\Game\Matchmaking\MatchConfiguration;
use App\Models\Game\Messages\News;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
class SetCurrencyModifiers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:set-currency-modifiers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Sets automated currency modifiers and news announcement for a timeframe';
const DAILY_START_TIME = '00:00';
const DAILY_END_TIME = '09:00';
const MODIFIER_AMOUNT = 1.5;
const GAME_NEWS_ID = '9f6a2801-c8c2-40ae-8d55-5dab3b601863';
/**
* Execute the console command.
*/
public function handle()
{
$startTime = Carbon::today()->setTimeFromTimeString(static::DAILY_START_TIME);
$endTime = Carbon::today()->setTimeFromTimeString(static::DAILY_END_TIME);
if(Carbon::now()->between($startTime, $endTime)) {
$this->setModifiers(static::MODIFIER_AMOUNT);
$this->setGamenewsVisibility(true);
}
else {
$this->setModifiers(1);
$this->setGamenewsVisibility(false);
}
}
private function setGamenewsVisibility(bool $enabled): void
{
$news = News::find(static::GAME_NEWS_ID);
if($news === null)
return;
$news->enabled = $enabled;
$news->save();
}
private function setModifiers(float $modifier): void {
$config = CurrencyMultipliers::get();
$config->currencyA = $modifier;
$config->currencyB = $modifier;
$config->currencyC = $modifier;
$config->save();
}
}

View File

@ -13,8 +13,11 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$schedule->command('model:prune')->daily();
$schedule->command('matchmaking:process')->everyFiveSeconds();
$schedule->command('matchmaking:cleanup')->everyFifteenSeconds();
$schedule->command('matchmaking:process')->everyTenSeconds();
$schedule->command('matchmaking:cleanup')->everyThirtySeconds();
$schedule->command('app:generate-timed-challenges')->daily();
$schedule->command('app:cleanup-logs')->daily();
$schedule->command('app:set-currency-modifiers')->hourlyAt(1);
}
/**

11
dist/app/Enums/Game/ChallengeType.php vendored Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Enums\Game;
enum ChallengeType: string
{
case None = 'None';
case Daily = 'Daily';
case Weekly = 'Weekly';
case Event = 'Event';
}

View File

@ -41,7 +41,10 @@ public function getGroupType(): ItemGroupType
};
}
public function getItemConfigClass(): string|CharacterItemConfig
/**
* @return class-string<CharacterItemConfig>
*/
public function getItemConfigClass(): string
{
return match ($this) {
Hunter::Stalker => StalkerItemConfig::class,

View File

@ -4,7 +4,7 @@
enum RewardType: string
{
case Currency = 'Currency';
case Inventory = 'Inventory';
case Progression = 'Progression';
case Currency = 'currency';
case Inventory = 'inventory';
case Progression = 'progression';
}

View File

@ -49,7 +49,10 @@ public function getGroupType(): ItemGroupType
};
}
public function getItemConfigClass(): string|CharacterItemConfig
/**
* @return class-string<CharacterItemConfig>
*/
public function getItemConfigClass(): string
{
return match ($this) {
self::Smoke => FogItemConfig::class,

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class TimedChallengeFactoryException extends \Exception
{
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api\Matchmaking;
use App\Console\Commands\ProcessMatchmaking;
use App\Enums\Game\Matchmaking\MatchStatus;
use App\Enums\Game\Matchmaking\QueueStatus;
use App\Http\Controllers\Controller;
@ -16,6 +17,7 @@
use App\Models\Admin\Archive\ArchivedGame;
use App\Models\Admin\Archive\ArchivedPlayerProgression;
use App\Models\Admin\CurrencyMultipliers;
use App\Models\Admin\MatchmakingSettings;
use App\Models\Admin\Versioning\CurrentGameVersion;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\Matchmaking\QueuedPlayer;
@ -39,10 +41,10 @@ public function getRegions()
public function queue(QueueRequest $request)
{
if($request->category !== CurrentGameVersion::get()?->gameVersion)
if ($request->category !== CurrentGameVersion::get()?->gameVersion)
abort(403, 'Too old mod version');
if($request->checkOnly)
if ($request->checkOnly)
return json_encode($this->checkQueueStatus($request));
return json_encode($this->addPlayerToQueue($request));
@ -52,20 +54,20 @@ public function cancelQueue(MatchmakingRequest $request)
{
$user = Auth::user();
if($user->id !== $request->playerId || $request->endState !== 'Cancelled')
if ($user->id !== $request->playerId || $request->endState !== 'Cancelled')
return response('', 204);
$lock = Cache::lock(static::QUEUE_LOCK, 10);
try {
$lock->block(20 ,function () use (&$user) {
$lock->block(20, function () use (&$user) {
// Delete the player from the Queue
QueuedPlayer::where('user_id', '=', $user->id)->delete();
// And also from any game they are matched for.
DB::table('game_user')->where('user_id', '=', $user->id)->delete();
});
} catch (LockTimeoutException $e) {
Log::channel('matchmaking')->emergency('Queue Cancel: Could not acquire Lock for canceling user '.$user->id.'('.$user->last_known_username.')');
Log::channel('matchmaking')->emergency('Queue Cancel: Could not acquire Lock for canceling user ' . $user->id . '(' . $user->last_known_username . ')');
} finally {
$lock->release();
}
@ -77,7 +79,7 @@ public function matchInfo(string $matchId)
{
$foundGame = Game::find($matchId);
if($foundGame === null)
if ($foundGame === null)
return ['status' => 'Error', 'message' => 'Match not found.'];
$user = Auth::user();
@ -102,24 +104,24 @@ public function register(RegisterMatchRequest $request, string $matchId)
return json_encode(MatchData::fromGame($foundGame));
}
public function close($matchId)
{
$foundGame = Game::find($matchId);
$foundGame->status = MatchStatus::Closed;
$foundGame->save();
public function close($matchId)
{
$foundGame = Game::find($matchId);
$foundGame->status = MatchStatus::Closed;
$foundGame->save();
return json_encode(MatchData::fromGame($foundGame));
}
return json_encode(MatchData::fromGame($foundGame));
}
/*
* Set a game to Quit state, Maybe exists but unsure since it never showed up in the request logs.
*/
public function quit($matchId)
{
$foundGame = Game::find($matchId);
public function quit($matchId)
{
$foundGame = Game::find($matchId);
$user = Auth::user();
if($foundGame->creator === $user) {
if ($foundGame->creator === $user) {
$foundGame->status = MatchStatus::Destroyed;
$foundGame->save();
}
@ -128,17 +130,17 @@ public function quit($matchId)
}
return response(null, 204);
}
}
public function kill($matchId)
{
$foundGame = Game::find($matchId);
public function kill($matchId)
{
$foundGame = Game::find($matchId);
$response = json_encode(MatchData::fromGame($foundGame));
$foundGame->delete();
return $response;
}
return $response;
}
public function seedFileGet(string $gameVersion, string $seed, string $mapName)
{
@ -155,7 +157,7 @@ public function deleteUserFromMatch(Game $match, User $user)
$requestUser = Auth::user();
// Block request if it doesn't come from the host
if($match->creator->id != $requestUser->id)
if ($match->creator->id != $requestUser->id)
return response('Action not allowed, you are not the creator of the match.', 403);
$this->removeUserFromGame($user, $match);
@ -165,13 +167,13 @@ public function deleteUserFromMatch(Game $match, User $user)
public function endOfMatch(EndOfMatchRequest $request)
{
if(ArchivedGame::archivedGameExists($request->matchId))
if (ArchivedGame::archivedGameExists($request->matchId))
return response('Match Already Closed', 209);
$game = Game::find($request->matchId);
$user = Auth::user();
if($game->creator != $user)
if ($game->creator != $user)
return response('you are not the creator of the match.', 403);
$game->status = MatchStatus::Killed;
@ -186,7 +188,7 @@ public function playerEndOfMatch(PlayerEndOfMatchRequest $request)
$user = Auth::user();
$game = Game::find($request->matchId);
if($game === null)
if ($game === null)
return response('Match not found.', 404);
if ($game->creator != $user)
@ -194,21 +196,21 @@ public function playerEndOfMatch(PlayerEndOfMatchRequest $request)
$user = User::find($request->playerId);
if($user === null)
if ($user === null)
return response('User not found.', 404);
$lock = Cache::lock('playerEndOfMatch'.$user->id);
$lock = Cache::lock('playerEndOfMatch' . $user->id);
try {
// Lock the saving of the playerdata and stuff because the game can send multiple calls sometimes
$lock->block(10 ,function () use (&$user, &$request, &$game) {
if(ArchivedPlayerProgression::archivedPlayerExists($game->id, $user->id))
if (ArchivedPlayerProgression::archivedPlayerExists($game->id, $user->id))
return;
$playerData = $user->playerData();
$characterData = $playerData->characterDataForCharacter($request->characterGroup->getCharacter());
if($request->hasQuit)
if ($request->hasQuit)
$playerData->quitterState->addQuitterPenalty();
else
$playerData->quitterState->addStayedMatch($playerData);
@ -277,7 +279,7 @@ protected function checkQueueStatus(QueueRequest $request): QueueResponse
$foundQueuedPlayer = QueuedPlayer::firstWhere('user_id', '=', $user->id);
// If we found a queued Player, return his status
if($foundQueuedPlayer !== null) {
if ($foundQueuedPlayer !== null) {
// Set Last queue call time to remove players that maybe crashed or something
// if they haven't sent a queue request in a long time.
$foundQueuedPlayer->updated_at = Carbon::now();
@ -287,7 +289,7 @@ protected function checkQueueStatus(QueueRequest $request): QueueResponse
$response->status = QueueStatus::Queued;
$response->queueData = new QueueData(
1,
10
static::getETA(),
);
return $response;
@ -297,12 +299,13 @@ protected function checkQueueStatus(QueueRequest $request): QueueResponse
// Only search for Created or open matches
$foundGame = $user->activeGames();
// If they also aren't in a game, add them to the queue again
if($foundGame->count() < 1) {
if ($foundGame->count() < 1) {
$this->addPlayerToQueue($request);
$response = new QueueResponse();
$response->status = QueueStatus::Queued;
$response->queueData = new QueueData(
1,
static::getETA(),
);
return $response;
@ -316,10 +319,10 @@ protected function checkQueueStatus(QueueRequest $request): QueueResponse
$response = new QueueResponse();
if($foundGame->status === MatchStatus::Opened) {
if ($foundGame->status === MatchStatus::Opened) {
$response->queueData = new QueueData(
1,
1
static::getETA(),
);
}
$response->status = QueueStatus::Matched;
@ -334,7 +337,7 @@ protected function addPlayerToQueue(QueueRequest $request)
$user = Auth::user();
// Remove user from any game he has previously joined.
if($user->activeGames()->exists()) {
if ($user->activeGames()->exists()) {
$games = $user->activeGames()->get();
foreach ($games as $game) {
$this->removeUserFromGame($user, $game);
@ -368,10 +371,26 @@ protected function removeUserFromGame(User $user, Game $game)
{
$game->players()->detach($user);
if($game->players->count() !== 0)
// Delete the game if the creator deletes themselfes sice we had one case where the matchmaking broke due to a game being stuck like this.
if ($game->players->count() !== 0 && $game->creator_user_id !== $user->id)
return;
$game->delete();
}
protected static function getETA(): int {
if(Cache::has(ProcessMatchmaking::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY)) {
$matchmakingSettings = MatchmakingSettings::get();
/** @var Carbon $firstAttempt */
$firstAttempt = Cache::get(ProcessMatchmaking::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);
$predictedMatchTime = $firstAttempt->copy();
while ($firstAttempt->diffInSeconds($predictedMatchTime) < $matchmakingSettings->matchWaitingTime) {
$predictedMatchTime->addSeconds(ProcessMatchmaking::$repeatTimeSeconds);
}
return Carbon::now()->diffInSeconds($predictedMatchTime) * 1000;
}
return -1;
}
}

View File

@ -2,19 +2,26 @@
namespace App\Http\Controllers\Api;
use App\Classes\Frontend\ChatMessage;
use App\Enums\Game\Matchmaking\MatchStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Moderation\CheckChatMessageRequest;
use App\Http\Requests\Api\Moderation\ReportPlayerRequest;
use App\Http\Requests\Api\Player\CheckUsernameRequest;
use App\Http\Responses\Api\Player\CheckUsernameResponse;
use App\Models\Admin\BadChatMessage;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\Moderation\PlayerReport;
use App\Models\User\User;
use ConsoleTVs\Profanity\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Response;
class ModerationController extends Controller
{
const CHAT_CACHE_KEY = 'chat-messages-';
public function checkUsername(CheckUsernameRequest $request): mixed
{
@ -25,23 +32,39 @@ public function checkChatMessage(CheckChatMessageRequest $request): mixed
{
// Lobby Host that send the request to check the message. We log him too since only the lobby host checks the messages in teh backend.
$hostUser = Auth::user();
$activeGame = $hostUser->games()->whereNotIn('status', [MatchStatus::Killed])->first();
if($activeGame === null)
// Don't log and check anything if the user is not in a game, we don't care about chatting in the locker room.
return Response::json([]);
// User that send the chat message
$messageUser = User::find($request->userId);
$gameMessages = Cache::get(static::CHAT_CACHE_KEY . $activeGame->id, []);
$gameMessages[] = new ChatMessage(
$activeGame->id,
$messageUser->id,
Carbon::now(),
$request->message,
);
Cache::put(static::CHAT_CACHE_KEY . $activeGame->id, $gameMessages, 1800 /** 30 Minutes */);
$checker = Builder::blocker($request->message);
if($checker->clean())
return response('{}');
$this->logBadMessage($hostUser, $messageUser, $request->message);
$this->logBadMessage($hostUser, $messageUser, $request->message, $activeGame);
return response('Bad Message >:(');
}
public function logBadMessage(User $hostUser, User $messageUser, string $message): void {
public function logBadMessage(User $hostUser, User $messageUser, string $message, Game $match): void {
$badMessage = new BadChatMessage();
$badMessage->hostUser()->associate($hostUser);
$badMessage->user()->associate($messageUser);
$badMessage->message = $message;
$badMessage->match_id = $match->id;
$badMessage->save();
}

View File

@ -22,7 +22,7 @@ public function getFileWithPatchline(string $patchlineName, string $hash) : Bina
if($gameFile === null)
return response()->json('File not found', 404);
$filePath = DIRECTORY_SEPARATOR . str($patchlineName)->lower() . DIRECTORY_SEPARATOR . $gameFile->name;
$filePath = DIRECTORY_SEPARATOR . str($patchlineName)->lower() . DIRECTORY_SEPARATOR . $gameFile->filename;
if (!$disk->exists($filePath)) {
return response()->json('File not found', 404);
@ -35,7 +35,7 @@ function getFile(string $hash) : BinaryFileResponse|JsonResponse {
return $this->getFileWithPatchline(Patchline::LIVE->name, $hash);
}
public function getGameFileList(string $patchlineName = null) : JsonResponse
public function getGameFileList(?string $patchlineName = null) : JsonResponse
{
$patchline = Patchline::tryFromName(str($patchlineName)->upper()) ?? Patchline::LIVE;
@ -49,17 +49,35 @@ public function getGameFileList(string $patchlineName = null) : JsonResponse
return response()->json(['error' => 'Unauthorized'], 401);
}
$gameFiles = GameFile::select(['filename', 'hash', 'game_path', 'action'])
->where('patchline', $patchline->value)
->where('is_additional', 0)->latest()->get();
$gameFiles = GameFile::where('patchline', $patchline->value)
->where('is_additional', 0)
->withFileHistory()
->map(function ($latestFile) {
$processedHashes = [];
$history = collect();
foreach ($latestFile->children as $historyFile) {
if (in_array($historyFile->hash, $processedHashes)) {
continue;
}
$processedHashes[] = $historyFile->hash;
$history->push($historyFile->only(['hash']));
}
$selectedFile = $latestFile->only(['filename', 'hash', 'game_path', 'action']);
$selectedFile['history'] = $history;
return $selectedFile;
});
if (count($gameFiles) <= 0)
return response()->json(['error' => 'No files found'], 404);
return response()->json($gameFiles, 200);
}
public function getGameModFileList(string $patchlineName = null) : JsonResponse
public function getGameModFileList(?string $patchlineName = null) : JsonResponse
{
$patchline = Patchline::tryFromName(str($patchlineName)->upper()) ?? Patchline::LIVE;

View File

@ -2,19 +2,30 @@
namespace App\Http\Controllers\Api\Player;
use App\Enums\Game\RewardType;
use App\Helper\Uuid\UuidHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Player\ExecuteChallengeProgressionBatchRequest;
use App\Http\Requests\Api\Player\GetChallengeProgressionBatchRequest;
use App\Http\Requests\Api\Player\GetChallengesRequest;
use App\Http\Responses\Api\General\Reward;
use App\Http\Responses\Api\Player\Challenges\ChallengeProgressionBatchResponse;
use App\Http\Responses\Api\Player\Challenges\ChallengeProgressionEntry;
use App\Http\Responses\Api\Player\Challenges\GetChallengesEntry;
use App\Models\Game\Challenge;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\PickedChallenge;
use App\Models\Game\TimedChallenge;
use App\Models\User\PlayerData;
use App\Models\User\User;
use Auth;
use Carbon\Carbon;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Str;
use Illuminate\Validation\UnauthorizedException;
use Ramsey\Uuid\Uuid;
@ -26,9 +37,30 @@ class ChallengeController extends Controller
*
* @return false|string
*/
public function getChallenges()
public function getChallenges(GetChallengesRequest $request)
{
return json_encode(['challenges' => []]);
$user = $request->user;
$timedChallenges = TimedChallenge::where('start_time', '<', Carbon::now())
->where('end_time', '>', Carbon::now())
->where('type', $request->type)
->get();
$challenges = [];
$timedChallenges->each(function (TimedChallenge $challenge) use (&$challenges, $user) {
$challenges[] = new GetChallengesEntry(
$challenge->id,
$challenge->start_time,
$challenge->end_time,
$challenge->type,
$challenge->completion_value,
$challenge->faction,
$challenge->blueprint_path,
$challenge->rewards,
$challenge->hasPlayerClaimed($user->playerData()->id),
);
});
return Response::json(['challenges' => $challenges]);
}
public function getProgressionBatch(GetChallengeProgressionBatchRequest $request)
@ -36,12 +68,23 @@ public function getProgressionBatch(GetChallengeProgressionBatchRequest $request
$user = User::findOrFail($request->userId);
$playerData = $user->playerData();
$timedChallengeIds = [];
foreach ($request->challengeIds as $index => $id) {
if(Str::startsWith($id, GetChallengesEntry::ID_PREFIX)) {
$timedChallengeIds[] = Str::remove(GetChallengesEntry::ID_PREFIX, $id);
unset($request->challengeIds[$index]);
}
}
$challengeIdsToCheck = UuidHelper::convertFromHexToUuidCollecton($request->challengeIds, true);
/** @var Challenge[]|Collection $challengesToCheck */
$challengesToCheck = Challenge::findMany($challengeIdsToCheck);
/** @var PickedChallenge[]|Collection $pickedChallengesToCheck */
$pickedChallengesToCheck = PickedChallenge::findMany($challengeIdsToCheck);
/** @var TimedChallenge[]|Collection $timedChallengesToCheck */
$timedChallengesToCheck = TimedChallenge::findMany($timedChallengeIds);
$response = new ChallengeProgressionBatchResponse();
@ -57,6 +100,20 @@ public function getProgressionBatch(GetChallengeProgressionBatchRequest $request
$response->progressionBatch[] = $newEntry;
}
foreach ($timedChallengesToCheck as $challenge) {
$progress = $challenge->getProgressForPlayer($playerData->id);
$entry = new ChallengeProgressionEntry(
GetChallengesEntry::ID_PREFIX . $challenge->id,
$challenge->progress >= $challenge->completion_value,
$progress,
);
$entry->rewardsClaimed = $challenge->rewards;
$response->progressionBatch[] = $entry;
}
foreach ($pickedChallengesToCheck as $challenge) {
$response->progressionBatch[] = new ChallengeProgressionEntry(
Uuid::fromString($challenge->id)->getHex()->toString(),
@ -65,6 +122,8 @@ public function getProgressionBatch(GetChallengeProgressionBatchRequest $request
);
}
foreach ($challengesToCheck as $challenge) {}
return json_encode($response);
}
@ -83,7 +142,11 @@ public function executeChallengeProgressionBatch(ExecuteChallengeProgressionBatc
$processedChallenges = [];
foreach ($request->operations as $operation) {
$challengeId = Uuid::fromString($operation['challengeId'])->toString();
$challengeId = $operation['challengeId'];
if(!Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) {
$challengeId = Uuid::fromString($operation['challengeId'])->toString();
}
// Skip if we already processed this challenge because the completed ones are always first in the array.
if(in_array($challengeId, $processedChallenges))
@ -99,8 +162,13 @@ public function executeChallengeProgressionBatch(ExecuteChallengeProgressionBatc
protected function addProgressToChallenge(string $challengeId, int $newProgress, User $user): void
{
/** @var Challenge|null $foundChallenge */
$foundChallenge = $user->playerData()->challenges()->find($challengeId);
if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) {
$foundChallenge = TimedChallenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId));
}
else {
/** @var Challenge|null $foundChallenge */
$foundChallenge = $user->playerData()->challenges()->find($challengeId);
}
if($foundChallenge !== null) {
$foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, [
@ -109,23 +177,51 @@ protected function addProgressToChallenge(string $challengeId, int $newProgress,
return;
}
/** @var PickedChallenge $foundChallenge */
$foundChallenge = PickedChallenge::find($challengeId);
if($foundChallenge === null)
return;
$newProgress = $newProgress >= MetadataController::reducePickedChallengeCompletionValue($foundChallenge->asset_path, $foundChallenge->completion_value) ? $foundChallenge->completion_value : $newProgress;
$foundChallenge->progress = $newProgress;
$foundChallenge->save();
}
protected function setChallengeAsCompleted(string $challengeId, User $user)
{
/** @var Challenge|null $foundChallenge */
$foundChallenge = $user->playerData()->challenges()->find($challengeId);
$isTimed = false;
// If the Challenge id starts with the Prefix, handle it as a timed challenge.
if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) {
$foundChallenge = TimedChallenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId));
$isTimed = true;
}
else {
/** @var Challenge|null $foundChallenge */
$foundChallenge = $user->playerData()->challenges()->find($challengeId);
}
if($foundChallenge !== null) {
$foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, [
$pivotToEdit = [
'progress' => $foundChallenge->completion_value,
]);
];
if($isTimed) {
$playerData = $user->playerData();
$hasClaimed = $foundChallenge->hasPlayerClaimed($playerData->id);
if(!$hasClaimed) {
$rewardsToAdd = $foundChallenge->getRewards();
foreach($rewardsToAdd as $reward) {
$this->addReward($reward, $playerData);
}
$playerData->save();
$pivotToEdit['claimed'] = true;
}
}
$foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, $pivotToEdit);
$foundChallenge->save();
return;
}
@ -137,4 +233,33 @@ protected function setChallengeAsCompleted(string $challengeId, User $user)
$foundChallenge->progress = $foundChallenge->completion_value;
$foundChallenge->save();
}
protected function addReward(Reward $reward, PlayerData &$playerData): void
{
if ($reward->type === RewardType::Currency) {
switch ($reward->id) {
case 'CurrencyA':
$playerData->currency_a += $reward->amount;
break;
case 'CurrencyB':
$playerData->currency_b += $reward->amount;
break;
case 'CurrencyC':
$playerData->currency_c += $reward->amount;
break;
default:
return;
}
}
else if ($reward->type === RewardType::Inventory) {
$itemUuid = Uuid::fromString($reward->id)->toString();
try {
// Since we can only have one occurrence of an item in the inventory, and an exception gets thrown when we try to add the same item twice
$playerData->inventory()->attach($itemUuid);
} catch (UniqueConstraintViolationException $e) {
}
}
}
}

View File

@ -81,7 +81,7 @@ public function deleteMultiple(Request $request) {
$timestampsToDelete = [];
foreach($messages as $message) {
try {
$timestampsToDelete[] = Carbon::createFromTimestampMs($message['received'])->toDateTimeString();
$timestampsToDelete[] = Carbon::createFromTimestampMs($message['received'], config('app.timezone'))->toDateTimeString();
} catch(\Exception $e) {
$logger = AccessLogger::getSessionLogConfig();
$logger->warning($request->method().' '.$request->getUri().': Something Went Wrong, Messagelist: '.json_encode($messages, JSON_PRETTY_PRINT));
@ -112,7 +112,7 @@ public function markMessages(Request $request) {
foreach($messages as $message) {
try {
$timestampsToSet[] = Carbon::createFromTimestampMs($message['received'])->toDateTimeString();
$timestampsToSet[] = Carbon::createFromTimestampMs($message['received'], config('app.timezone'))->toDateTimeString();
$resultList[] = [
'received' => $message['received'],
'success' => true,
@ -141,7 +141,7 @@ public function claimMessage(ClaimInboxMessageRequest $request)
/** @var InboxMessage $message */
$message = $user->inboxMessages()
->where('user_id', '=', $user->id)
->where('received', '=', Carbon::createFromTimestampMs($request->receivedTimestamp))
->where('received', '=', Carbon::createFromTimestampMs($request->receivedTimestamp, config('app.timezone')))
->first();
if($message === null)

View File

@ -21,6 +21,48 @@
class MetadataController extends Controller
{
/**
* Mapping from
*/
const PICKED_CHALLENGE_REDUCTION_MAPPING = [
'/Game/Challenges/Progression/General/Challenge_Downing_Hunter.Challenge_Downing_Hunter' => 74.49,
'/Game/Challenges/Progression/General/Challenge_Execution_Hunter.Challenge_Execution_Hunter' => 76.19,
'/Game/Challenges/Progression/General/Challenge_Ressources_Runner.Challenge_Ressources_Runner' => 58.05,
'/Game/Challenges/Progression/General/Challenge_Travel_Hunter.Challenge_Travel_Hunter' => 52.38,
'/Game/Challenges/Progression/General/Challenge_Travel_Runner.Challenge_Travel_Runner' => 50.00,
'/Game/Challenges/Progression/General/Challenge_Hacking_Hunter.Challenge_Hacking_Hunter' => 85.71,
'/Game/Challenges/Progression/General/Challenge_Drones_Hunter.Challenge_Drones_Hunter' => 95.24,
'/Game/Challenges/Progression/General/Challenge_TeamActions_Runner.Challenge_TeamActions_Runner' => 47.09,
'/Game/Challenges/Progression/General/Challenge_HunterClose_Runner.Challenge_HunterClose_Runner' => 76.19,
'/Game/Challenges/Progression/General/Challenge_Damage_Hunter.Challenge_Damage_Hunter' => 40.48,
'/Game/Challenges/Progression/General/Challenge_ConstructDefeats_Runner.Challenge_ConstructDefeats_Runner' => 73.54,
'/Game/Challenges/Progression/General/Challenge_Heal_Runner.Challenge_Heal_Runner' => 52.38,
'/Game/Challenges/Progression/General/Challenge_RingOut_Hunter.Challenge_RingOut_Hunter' => 58.33,
'/Game/Challenges/Progression/General/Challenge_Supercharge_Hunter.Challenge_Supercharge_Hunter' => 78.57,
'/Game/Challenges/Progression/General/Challenge_TakeDamage_Runner.Challenge_TakeDamage_Runner' => 57.14,
'/Game/Challenges/Progression/General/Challenge_Evade_Runner.Challenge_Evade_Runner' => 70.24,
'/Game/Challenges/Progression/General/Challenge_Climb_Runner.Challenge_Climb_Runner' => 86.11,
'/Game/Challenges/Challenge_Deliver_Runner.Challenge_Deliver_Runner' => 57.14,
'/Game/Challenges/Progression/General/Challenge_Mark_Runner.Challenge_Mark_Runner' => 71.43,
'/Game/Challenges/Progression/General/Challenge_Reveal_Hunter.Challenge_Reveal_Hunter' => 94.29,
'/Game/Challenges/Progression/General/Challenge_HackCrates_Hunter.Challenge_HackCrates_Hunter' => 42.86,
'/Game/Challenges/Progression/General/Challenge_SpendNPI_Runner.Challenge_SpendNPI_Runner' => 42.86,
'/Game/Challenges/Progression/General/Challenge_CollectAmmo.Challenge_CollectAmmo' => 52.38,
'/Game/Challenges/Progression/General/Challenge_CollectWeaponUpgrades_Runner.Challenge_CollectWeaponUpgrades_Runner' => 52.38,
'/Game/Challenges/Progression/General/Challenge_CollectHealthCrates.Challenge_CollectHealthCrates' => 90.48,
'/Game/Challenges/Progression/General/Challenge_DisableDrones_Runner.Challenge_DisableDrones_Runner' => 61.90,
'/Game/Challenges/Challenge_DroneCharger_Hunter.Challenge_DroneCharger_Hunter' => 66.67,
'/Game/Challenges/Progression/General/Challenge_Stomp_Hunter.Challenge_Stomp_Hunter' => 50.00,
'/Game/Challenges/Progression/General/Challenge_Aim_Hunter.Challenge_Aim_Hunter' => 42.86,
'/Game/Challenges/Progression/General/Challenge_DangerClose_Runner.Challenge_DangerClose_Runner' => 76.19,
'/Game/Challenges/Progression/General/Challenge_SurviveAChase_Runner.Challenge_SurviveAChase_Runner' => 64.29,
'/Game/Challenges/Progression/General/Challenge_AssistAChase_Runner.Challenge_AssistAChase_Runner' => 28.57,
'/Game/Challenges/Progression/General/Challenge_Exit_Runner.Challenge_Exit_Runner' => 42.86,
'/Game/Challenges/Progression/General/Challenge_BloodMode_Runner.Challenge_BloodMode_Runner' => 42.86,
'/Game/Challenges/Progression/General/Challenge_LastMAnStanding_Hunter.Challenge_LastManStanding_Hunter' => 42.86,
];
public function initOrGetGroups(InitOrGetGroupsRequest $request)
{
$response = new InitOrGetGroupsResponse();
@ -56,6 +98,8 @@ public function initOrGetGroups(InitOrGetGroupsRequest $request)
return json_encode($response);
}
public function updateMetadataGroup(UpdateMetadataGroupRequest $request)
{
switch ($request->group) {
@ -96,8 +140,17 @@ private function handleUpdateCharacterMetadata(UpdateMetadataGroupRequest &$requ
$convertedIds = UuidHelper::convertFromHexToUuidCollecton($request->metadata['equippedWeapons'], true);
$characterData->equippedWeapons()->sync($convertedIds);
$convertedIds = UuidHelper::convertFromHexToUuidCollecton($request->metadata['equippedBonuses'], true);
$characterData->equippedBonuses()->sync($convertedIds);
$characterConfig = $characterData->character->getCharacter()->getItemConfigClass();
$defaultCompletedIds = [
...$characterConfig::getDefaultEquippedBonuses(),
...$characterConfig::getDefaultEquippedWeapons(),
...$characterConfig::getDefaultEquipment(),
...$characterConfig::getDefaultPowers(),
...$characterConfig::getDefaultWeapons(),
...$characterConfig::getDefaultEquippedPerks(),
];
$defaultCompletedIds = UuidHelper::convertFromHexToUuidCollecton($defaultCompletedIds, true);
foreach ($request->metadata['pickedChallenges'] as $picked) {
$itemId = Uuid::fromHexadecimal(new Hexadecimal($picked['itemId']));
@ -109,14 +162,19 @@ private function handleUpdateCharacterMetadata(UpdateMetadataGroupRequest &$requ
foreach ($picked['list'] as $challenge) {
$challengeId = Uuid::fromHexadecimal(new Hexadecimal($challenge['challengeId']));
$completionValue = $challenge['challengeCompletionValue'];
$assetPath = $challenge['challengeAsset'];
$completionValue = $challenge['challengeCompletionValue'];
$newPicked = new PickedChallenge([
$attributes = [
'id' => $challengeId->toString(),
'completion_value' => $completionValue,
'asset_path' => $assetPath,
]);
];
if($defaultCompletedIds->has($itemId->toString()))
$attributes['progress'] = $completionValue;
$newPicked = new PickedChallenge($attributes);
$newPicked->characterData()->associate($characterData);
$newPicked->catalogItem()->associate($itemId->toString());
@ -172,4 +230,20 @@ private function handleFactionMetadata(UpdateMetadataGroupRequest &$request): st
throw new \Exception('Handle Update Faction Metadata not implemented yet');
return false;
}
/**
* Reduces the Completion value of the given challenge.
*
* @param string $challengeBlueprint
* @param string $originalAmount
* @return int
*/
public static function reducePickedChallengeCompletionValue(
string $challengeBlueprint,
string $originalAmount,
): int {
if(isset(static::PICKED_CHALLENGE_REDUCTION_MAPPING[$challengeBlueprint]))
return $originalAmount - (static::PICKED_CHALLENGE_REDUCTION_MAPPING[$challengeBlueprint] / 100 * $originalAmount);
return $originalAmount;
}
}

View File

@ -179,6 +179,7 @@ protected function resetCharacter(CharacterData &$characterData): void
...$config::getDefaultEquippedPerks(),
...$config::getDefaultPowers(),
...$config::getDefaultEquippedWeapons(),
...$config::getDefaultWeapons(),
...$config::getDefaultEquippedBonuses(),
], true);
$inventory = $characterData->playerData->inventory();
@ -186,7 +187,7 @@ protected function resetCharacter(CharacterData &$characterData): void
// first detach all character items
$inventory->detach($allItemsToRemove);
//then attach the defaults again
$inventory->attach([...$defaultItems, $config::getCharacterId()->toString()]);
$inventory->syncWithoutDetaching([...$defaultItems, $config::getCharacterId()->toString()]);
// Remove signature challenges
foreach ($allItemsToRemove as $item) {

View File

@ -46,7 +46,7 @@ public function store(Request $request)
$filename = $file->getClientOriginalName();
$filehash = str(hash_file('sha256', $file->getRealPath()))->upper();
$gameFile = GameFile::where('filename', $filename)->where('patchline', $request->input('patchline'))->first() ?? new GameFile;
$gameFile = GameFile::where('filename', $filename)->where('patchline', $request->input('patchline'))->latest()->first() ?? new GameFile;
if ($gameFile->hash == $filehash) {
$duplicateFiles[] = $gameFile->filename;
@ -54,6 +54,11 @@ public function store(Request $request)
}
if (isset($gameFile->id)) {
$gameFile->action = 0;
$gameFile->save();
$gameFile->child_id = $gameFile->id;
$gameFile = $gameFile->replicate();
$overwrittenFiles[] = $gameFile->filename;
}
@ -61,13 +66,20 @@ public function store(Request $request)
$gameFile->hash = $filehash;
$gameFile->patchline = $request->input('patchline');
$gameFile->name = $request->input('game_mod_name')[$i];
$gameFile->description = $request->input('game_mod_description')[$i];
if (filled($request->input('game_mod_name'))) {
$gameFile->name = $request->input('game_mod_name')[$i];
}
if (filled($request->input('game_mod_description'))) {
$gameFile->description = $request->input('game_mod_description')[$i];
}
$gameFile->is_additional = $request->input('is_additional');
GameFile::getDisk()->putFileAs(strtolower($gameFile->patchline->name), $file, $gameFile->filename);
$uploadedFiles[] = $gameFile->filename;
$gameFile->game_path = $request->game_path[$i];
$gameFile->action = $request->file_action[$i];
$gameFile->save();
@ -84,7 +96,7 @@ public function store(Request $request)
}
if (count($duplicateFiles) > 0) {
Session::flash('alert-warning', 'The following files already exist: <br>' . implode('<br>', $duplicateFiles));
Session::flash('alert-warning', 'The following files with the same hash already exist: <br>' . implode('<br>', $duplicateFiles));
//return response()->json(['message' => 'Only some files was uploaded successfully. The following files already exist: ' . implode(', ', $duplicateFiles)]);
}
@ -100,12 +112,15 @@ public function store(Request $request)
public function index(Request $request) : View {
$patchline = Patchline::tryFrom($request->input('patchline')) ?? Patchline::LIVE;
$files = GameFile::latest()->where('patchline', $patchline)->where('is_additional', (bool)$request->input('additional_files'))->get();
$allFiles = GameFile::where('patchline', $patchline)
->where('is_additional', (bool)$request->input('additional_files'))
->withFileHistory();
return view('admin.tools.file-manager', [
'patchline' => $patchline,
'showAdditionalFiles' => (bool)$request->input('additional_files'),
'files' => $files,
'files' => $allFiles,
]);
}

View File

@ -11,7 +11,7 @@ class LauncherMessageController extends AdminToolController
{
protected static string $name = 'Launcher Message';
protected static string $description = 'Set the Launcher message';
protected static string $description = 'Set the Launcher Message';
protected static string $iconComponent = 'icons.patch-exclamation';
protected static Permissions $neededPermission = Permissions::FILE_UPLOAD;
@ -38,4 +38,4 @@ public function saveMessage(SaveLauncherMessageRequest $request) {
return back();
}
}
}

View File

@ -7,10 +7,12 @@
use App\Http\Requests\Api\Admin\Tools\SaveCurrencyConfiguration;
use App\Http\Requests\Api\Admin\Tools\SaveLauncherMessageRequest;
use App\Http\Requests\Api\Admin\Tools\SaveMatchConfigurationRequest;
use App\Http\Requests\Api\Admin\Tools\SaveMatchmakingConfigurationRequest;
use App\Http\Requests\Api\Admin\Tools\SaveVersioningRequest;
use App\Models\Admin\CurrencyMultipliers;
use App\Models\Admin\ExperienceMultipliers;
use App\Models\Admin\LauncherMessage;
use App\Models\Admin\MatchmakingSettings;
use App\Models\Admin\Versioning\CurrentCatalogVersion;
use App\Models\Admin\Versioning\CurrentContentVersion;
use App\Models\Admin\Versioning\CurrentGameVersion;
@ -73,4 +75,20 @@ public function saveCurrency(SaveCurrencyConfiguration $request)
return back();
}
public function saveMatchmaking(SaveMatchmakingConfigurationRequest $request) {
$matchmakingConfig = MatchmakingSettings::get();
try {
$matchmakingConfig->matchWaitingTime = $request->matchmakingWaitingTime;
$matchmakingConfig->save();
Session::flash('alert-success', 'Matchmaking configuration saved successfully.');
}
catch (\Exception $e) {
Session::flash('alert-error', 'Matchmaking configuration could not be saved, something went wrong: ' . $e->getMessage());
}
return back();
}
}

View File

@ -47,6 +47,14 @@ public function downloadLauncher()
abort(404);
}
public function patchNotes(): Factory|Application|View|\Illuminate\Contracts\Foundation\Application
{
static::setTitle('Deathgarden: Rebirth | Patch Notes');
CompView::share('metaKeywords', ['Deathgarden', 'Rebirth', 'patch', 'patchnnotes', 'patch notes']);
CompView::share('metaDescription', 'Stay up to date with all the changes, improvements, and updates in Deathgarden: Rebirth. Below is a complete archive of every patch since the mods release.');
return view('web.patch-notes');
}
public function howToPlay(): Factory|Application|View|\Illuminate\Contracts\Foundation\Application
{
static::setTitle('Deathgarden: Rebirth | How to Play');

View File

@ -3,7 +3,6 @@
namespace App\Http;
use App\Http\Middleware\AccessLogger;
use App\Http\Middleware\HandleInertiaRequests;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
@ -39,7 +38,6 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
HandleInertiaRequests::class,
],
'api' => [

View File

@ -1,43 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that's loaded on the first page visit.
*
* @see https://inertiajs.com/server-side-setup#root-template
* @var string
*/
protected $rootView = 'app';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
* @param \Illuminate\Http\Request $request
* @return string|null
*/
public function version(Request $request): ?string
{
return parent::version($request);
}
/**
* Defines the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
* @param \Illuminate\Http\Request $request
* @return array
*/
public function share(Request $request): array
{
return array_merge(parent::share($request), [
//
]);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests\Api\Admin\Tools;
use Illuminate\Foundation\Http\FormRequest;
class SaveMatchmakingConfigurationRequest extends FormRequest
{
public int $matchmakingWaitingTime;
public function rules(): array {
return [
'matchmakingWaitingTime' => ['required', 'integer'],
];
}
protected function passedValidation()
{
$this->matchmakingWaitingTime = (int)$this->input('matchmakingWaitingTime');
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Api\Player;
use App\Enums\Game\ChallengeType;
use App\Models\User\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class GetChallengesRequest extends FormRequest
{
public User $user;
public ChallengeType $type;
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return \Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'data.userId' => 'required|exists:users,id',
'data.challengeType' => ['required', Rule::enum(ChallengeType::class)],
];
}
public function passedValidation(): void
{
$this->user = User::find($this->input('data.userId'));
$this->type = ChallengeType::tryFrom($this->input('data.challengeType'));
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Http\Responses\Api\Player\Challenges;
use App\Enums\Game\ChallengeType;
use App\Enums\Game\Faction;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
class GetChallengesEntry implements \JsonSerializable
{
const ID_PREFIX = 'Timed:';
public function __construct(
public int $id,
public Carbon $startTime,
public Carbon $endTime,
public ChallengeType $type,
public int $completionValue,
public Faction $faction,
public string $challengeBlueprint,
public array $rewards,
public bool $claimed,
)
{}
public function getChallengeId(): string {
return static::ID_PREFIX . $this->id;
}
public function jsonSerialize(): mixed
{
$json = [
'lifetime' =>
[
'creationTime' => $this->startTime->toIso8601ZuluString(),
'expirationTime' => $this->endTime->toIso8601ZuluString(),
],
'challengeType' => $this->type,
'challengeId' => $this->getChallengeId(),
'challengeCompletionValue' => $this->completionValue,
'faction' => $this->faction,
'challengeBlueprint' => $this->challengeBlueprint,
];
$rewards = [];
foreach ($this->rewards as &$reward) {
$reward['claimed'] = $this->claimed;
$reward['weight'] = 100;
$rewards[] = $reward;
}
$json['rewards'] = $rewards;
return $json;
}
}

View File

@ -78,7 +78,9 @@ public function addCharacterMetadataGroup(Characters $character, User $user): vo
$newGroup->equippedPerks = UuidHelper::convertFromUuidToHexCollection($characterData->equippedPerks()->allRelatedIds())->toArray();
$newGroup->equippedWeapons = UuidHelper::convertFromUuidToHexCollection($characterData->equippedWeapons()->allRelatedIds())->toArray();
$newGroup->equipment = UuidHelper::convertFromUuidToHexCollection($characterData->equipment()->allRelatedIds())->toArray();
$newGroup->equippedBonuses = UuidHelper::convertFromUuidToHexCollection($characterData->equippedBonuses()->allRelatedIds())->toArray();
// Since players cannot change their equipped powers or passives(bonuses), we can just read them from the config.
$newGroup->equippedBonuses = $itemConfigClass::getDefaultEquippedBonuses();
$newGroup->equippedPowers = $itemConfigClass::getDefaultPowers();
$newGroup->prestigeLevel = $characterData->prestige_level;

View File

@ -2,17 +2,23 @@
namespace App\Models\Admin\Archive;
use App\Classes\Frontend\ChatMessage;
use App\Enums\Game\Faction;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
/**
* @mixin IdeHelperArchivedGame
*/
class ArchivedGame extends Model
{
use HasUuids;
protected $casts = [
'dominant_faction' => Faction::class,
'chat_messages' => 'array',
];
public function archivedPlayerProgressions(): HasMany
@ -24,5 +30,21 @@ public static function archivedGameExists(string $matchId) {
return ArchivedGame::where('id', '=', $matchId)->exists();
}
/**
* @return ChatMessage[]
*/
public function getChatMessages(): array {
$result = [];
foreach ($this->chat_messages as $message) {
$result[] = new ChatMessage(
$message['gameId'],
$message['userId'],
Carbon::parse($message['messageTime']),
$message['message'],
);
}
return $result;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Models\Admin;
use App\Models\AbstractFileBasedModel;
class MatchmakingSettings extends AbstractFileBasedModel
{
const FILE_NAME = 'matchmakingSettings';
const CACHE_DURATION = 86400;
/**
* How long the matchmaking should wait when only one 1v4 or 1v5 could be made before actually making it.
*/
public int $matchWaitingTime = 10;
protected static function getDefault(): ?static
{
return new static();
}
}

View File

@ -49,11 +49,6 @@ public function equipment(): BelongsToMany
return $this->belongsToMany(CatalogItem::class,'character_data_equipment');
}
public function equippedBonuses(): BelongsToMany
{
return $this->belongsToMany(CatalogItem::class,'character_data_equipped_bonuses');
}
public function equippedPerks(): BelongsToMany
{
return $this->belongsToMany(CatalogItem::class,'character_data_equipped_perks');
@ -133,10 +128,6 @@ public function validateEquippedItems(): void
$equippedEquipment = $this->equipment()->allRelatedIds();
if($equippedEquipment->count() === 0)
$this->resetEquipment($itemConfigClass);
$equippedBonuses = $this->equippedBonuses()->allRelatedIds();
if($equippedBonuses->count() === 0)
$this->resetEquippedBonuses($itemConfigClass);
}
protected function resetEquippedPerks(string|CharacterItemConfig $itemConfigClass): void
@ -172,17 +163,6 @@ protected function resetEquipment(string|CharacterItemConfig $itemConfigClass):
static::getResetItemsLogger()->warning(sprintf('User %s(%s) had unallowed Equipment Equipped', $user->id, $user->last_known_username));
}
protected function resetEquippedBonuses(string|CharacterItemConfig $itemConfigClass): void
{
// Remove all equipped Weapons and reset to default config
$this->equippedBonuses()->detach();
$defaultBonusIds = UuidHelper::convertFromHexToUuidCollecton($itemConfigClass::getDefaultEquippedBonuses());
$this->equippedBonuses()->attach($defaultBonusIds);
$user = Auth::user();
static::getResetItemsLogger()->warning(sprintf('User %s(%s) had unallowed Bonuses Equipped', $user->id, $user->last_known_username));
}
protected static function getResetItemsLogger(): LoggerInterface
{
if(static::$resetItemsLogger === null) {

View File

@ -8,6 +8,7 @@
use App\Enums\Game\Faction;
use App\Enums\Game\Matchmaking\MatchmakingSide;
use App\Enums\Game\Matchmaking\MatchStatus;
use App\Http\Controllers\Api\ModerationController;
use App\Http\Requests\Api\Matchmaking\PlayerEndOfMatchRequest;
use App\Models\Admin\Archive\ArchivedGame;
use App\Models\Admin\Archive\ArchivedPlayerProgression;
@ -107,6 +108,9 @@ public function archiveGame(Faction $dominantFaction): void
$archived = new ArchivedGame();
$archived->id = $this->id;
$archived->dominant_faction = $dominantFaction;
$archived->chat_messages = Cache::get(ModerationController::CHAT_CACHE_KEY . $this->id, []);
$archived->save();
}
}

View File

@ -44,8 +44,24 @@ public static function getAvailableMatchConfigs(int $runnerCount, int $hunterCou
->get();
}
public static function selectRandomConfigByWeight(Collection &$collection): MatchConfiguration|null
public static function selectMatchConfig(Collection &$collection, int $runnerCount, int $hunterCount): MatchConfiguration|null
{
$filteredConfigs = new Collection();
if ($hunterCount === 1 && $runnerCount >= 6){
$filteredConfigs = $collection->filter(function (MatchConfiguration $config) use ($runnerCount, $hunterCount) {
return $config->runners >= 6 && $config->hunters === $hunterCount;
});
}
else if ($hunterCount === 1 && $runnerCount >= 4){
$filteredConfigs = $collection->filter(function (MatchConfiguration $config) use ($runnerCount, $hunterCount) {
return $config->runners === $runnerCount && $config->hunters === $hunterCount;
});
}
if ($filteredConfigs->isNotEmpty())
$collection = $filteredConfigs;
$weightSum = $collection->sum('weight');
$random = random_int(1, $weightSum);
$selectWalk = 0;

View File

@ -117,7 +117,7 @@ public static function getReward(int $stayMatchStreak): array {
return [
new Reward(
RewardType::Currency,
50,
150,
'CurrencyA',
),
];
@ -126,7 +126,7 @@ public static function getReward(int $stayMatchStreak): array {
return [
new Reward(
RewardType::Currency,
100,
300,
'CurrencyA',
),
new Reward(
@ -145,7 +145,7 @@ public static function getReward(int $stayMatchStreak): array {
return [
new Reward(
RewardType::Currency,
150,
450,
'CurrencyA',
),
new Reward(
@ -164,7 +164,7 @@ public static function getReward(int $stayMatchStreak): array {
return [
new Reward(
RewardType::Currency,
200,
600,
'CurrencyA',
),
new Reward(
@ -183,7 +183,7 @@ public static function getReward(int $stayMatchStreak): array {
return [
new Reward(
RewardType::Currency,
250,
750,
'CurrencyA',
),
new Reward(

78
dist/app/Models/Game/TimedChallenge.php vendored Normal file
View File

@ -0,0 +1,78 @@
<?php
namespace App\Models\Game;
use App\Enums\Game\ChallengeType;
use App\Enums\Game\Faction;
use App\Enums\Game\RewardType;
use App\Http\Responses\Api\General\Reward;
use App\Models\User\PlayerData;
use Carbon\Carbon;
use Eloquent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @mixin IdeHelperTimedChallenge
*/
class TimedChallenge extends Model
{
protected $casts = [
'type' => ChallengeType::class,
'faction' => Faction::class,
'start_time' => 'datetime',
'end_time' => 'datetime',
'rewards' => 'array',
];
public function getProgressForPlayer(int $playerDataId): int
{
$foundProgress = $this->playerData()->where('id', '=', $playerDataId)->first();
if ($foundProgress !== null)
return $foundProgress->pivot->progress;
// We create challange relation and just return 0 since you cannot have progress for the challenge we just linked.
$this->playerData()->attach($playerDataId);
return 0;
}
public function hasPlayerClaimed(int $playerDataId): bool {
$foundPlayerData = $this->playerData()->where('id', '=', $playerDataId)->first();
if ($foundPlayerData !== null)
return $foundPlayerData->pivot->claimed;
return false;
}
/**
* @return Reward[]
*/
public function getRewards(): array {
if ($this->rewards === null)
return [];
$result = [];
foreach ($this->rewards as $reward) {
$result[] = new Reward(
RewardType::tryFrom($reward['type']),
$reward['amount'],
$reward['id'],
);
}
return $result;
}
public function playerData(): BelongsToMany
{
return $this->belongsToMany(PlayerData::class)->withPivot(['progress', 'claimed']);
}
public static function currentChallenges(): Eloquent|Builder {
return static::where('start_time', '>', Carbon::now())
->where('end_time', '<', Carbon::now());
}
}

View File

@ -4,8 +4,10 @@
use App\Enums\Launcher\FileAction;
use App\Enums\Launcher\Patchline;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
@ -35,7 +37,7 @@ protected static function booted()
});
}
public static function getDisk():FileSystemAdapter {
public static function getDisk(): FileSystemAdapter {
return static::$disk ?? static::$disk = Storage::disk('patches');
}
@ -54,4 +56,45 @@ public function fileExists(): bool
public function getFileSize(): int {
return static::getDisk()->size($this->getDiskPath());
}
public function child(): BelongsTo
{
return $this->belongsTo(GameFile::class);
}
public function getTopParent(): GameFile
{
$current = $this;
while ($current->child) {
$current = $current->child;
}
return $current;
}
public function scopeWithFileHistory($query): Collection
{
return $query->latest()->get()->pipe(function ($allFiles) {
$filesById = $allFiles->keyBy('id');
// Find files that are not referenced as child_id (these are the latest in their chains)
$referencedIds = $allFiles->pluck('child_id')->filter();
$latestFiles = $allFiles->whereNotIn('id', $referencedIds);
// Build complete history chains for each latest file
return $latestFiles->map(function ($latestFile) use ($filesById) {
$history = collect();
// Walk backwards through the chain to collect all history
$currentId = $latestFile->child_id;
while ($currentId && isset($filesById[$currentId])) {
$historyFile = $filesById[$currentId];
$history->push($historyFile);
$currentId = $historyFile->child_id;
}
$latestFile->children = $history;
return $latestFile;
})->sortByDesc('updated_at');
});
}
}

View File

@ -12,6 +12,8 @@
use App\Models\Game\Challenge;
use App\Models\Game\CharacterData;
use App\Models\Game\QuitterState;
use Illuminate\Database\Eloquent\Casts\Attribute;
use App\Models\Game\TimedChallenge;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -36,7 +38,7 @@ class PlayerData extends Model
'currency_b' => 500,
'currency_c' => 500,
'last_faction' => Faction::Runner,
'last_hunter' => Hunter::Inquisitor,
'last_hunter' => Hunter::Mass,
'last_runner' => Runner::Smoke,
'readout_version' => 1,
'runner_faction_level' => 1,
@ -53,6 +55,8 @@ class PlayerData extends Model
'has_played_dg_1' => 'boolean',
];
const CURRENCY_CAP = 50000;
protected static function booted(): void
{
static::created(function (PlayerData $playerData) {
@ -66,6 +70,7 @@ protected static function booted(): void
...$configClass::getDefaultEquippedWeapons(),
...$configClass::getDefaultEquipment(),
...$configClass::getDefaultPowers(),
...$configClass::getDefaultWeapons(),
...$configClass::getDefaultEquippedPerks(),
];
@ -88,6 +93,7 @@ protected static function booted(): void
...$configClass::getDefaultEquippedWeapons(),
...$configClass::getDefaultEquipment(),
...$configClass::getDefaultPowers(),
...$configClass::getDefaultWeapons(),
...$configClass::getDefaultEquippedPerks(),
];
@ -108,7 +114,7 @@ protected static function booted(): void
Characters::Smoke->value,
Characters::Ghost->value,
Characters::Sawbones->value,
Characters::Inquisitor->value,
Characters::Mass->value,
]
]);
});
@ -143,6 +149,10 @@ public function challenges(): BelongsToMany
return $this->belongsToMany(Challenge::class)->withPivot(['progress']);
}
public function timedChallenges(): BelongsToMany {
return $this->belongsToMany(TimedChallenge::class)->withPivot(['progress', 'claimed']);
}
public function quitterState(): HasOne
{
return $this->hasOne(QuitterState::class);
@ -192,4 +202,22 @@ public static function getRemainingFactionExperience(int $level): int
return 4;
return 5;
}
public function currencyA(): Attribute {
return Attribute::make(
set: fn (int $value) => min($value, static::CURRENCY_CAP),
);
}
public function currencyB(): Attribute {
return Attribute::make(
set: fn (int $value) => min($value, static::CURRENCY_CAP),
);
}
public function currencyC(): Attribute {
return Attribute::make(
set: fn (int $value) => min($value, static::CURRENCY_CAP),
);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\View\Components\Admin;
use App\Classes\Frontend\ChatMessage;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ChatHistory extends Component
{
/**
* @param ChatMessage[] $messages
*/
public function __construct(
public array $messages
)
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.admin.chat-history');
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\View\Components\Admin;
use App\Models\Admin\Archive\ArchivedGame;
use App\Models\Admin\Archive\ArchivedPlayerProgression;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\View\Component;
class MatchInfo extends Component
{
public Collection $players;
/**
* Create a new component instance.
*/
public function __construct(
public ArchivedGame $game,
)
{
$this->players = ArchivedPlayerProgression::whereArchivedGameId($game->id)->get();
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.admin.match-info');
}
}

6
dist/composer.json vendored
View File

@ -8,8 +8,7 @@
"php": "^8.2",
"consoletvs/profanity": "^3.5",
"guzzlehttp/guzzle": "^7.2",
"inertiajs/inertia-laravel": "^0.6.11",
"laravel/framework": "^10.10",
"laravel/framework": "^11.0",
"laravel/octane": "^2.4",
"laravel/socialite": "^5.12",
"laravel/tinker": "^2.8",
@ -19,12 +18,11 @@
"spatie/laravel-permission": "^6.3"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.14",
"barryvdh/laravel-ide-helper": "^3.2",
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
},

2249
dist/composer.lock generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -44,6 +44,12 @@
'throw' => false,
],
'logs' => [
'driver' => 'local',
'root' => storage_path('logs'),
'throw' => false,
],
'patches' => [
'driver' => 'local',
'root' => storage_path('app/patches'),

View File

@ -59,7 +59,7 @@
],
'single' => [
'driver' => 'single',
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
@ -135,6 +135,13 @@
'replace_placeholders' => true,
],
'challengeCreation' => [
'driver' => 'single',
'path' => storage_path('logs/challengeCreation.log'),
'level' => 'info',
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),

View File

@ -31,7 +31,7 @@
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'lifetime' => (int)env('SESSION_LIFETIME', 120),
'expire_on_close' => false,

View File

@ -25,6 +25,6 @@ public function up(): void
*/
public function down(): void
{
Schema::dropIfExists('catalog_item_challenges');
Schema::dropIfExists('catalog_item_challenge');
}
};

View File

@ -11,8 +11,19 @@
*/
public function up(): void
{
if (Schema::hasIndex('game_files', ['name', 'hash', 'version'])) {
Schema::table('game_files', function (Blueprint $table) {
$table->dropUnique(['name', 'hash', 'version']);
});
}
//Schema doesn't support tinyint
DB::statement("ALTER TABLE `game_files` CHANGE `version` `patchline` TINYINT UNSIGNED NOT NULL DEFAULT '1';");
Schema::table('game_files', function (Blueprint $table) {
$table->unique(['name', 'hash', 'patchline']);
});
}
/**
@ -20,11 +31,19 @@ public function up(): void
*/
public function down(): void
{
if (Schema::hasIndex('game_files', ['name', 'hash', 'patchline'])) {
Schema::table('game_files', function (Blueprint $table) {
$table->dropUnique(['name', 'hash', 'patchline']);
});
}
Schema::table('game_files', function (Blueprint $table) {
$table->integer('patchline')->default(1)->change();
});
Schema::table('game_files', function (Blueprint $table) {
$table->renameColumn('patchline', 'version');
});
Schema::table('game_files', function (Blueprint $table) {
$table->unique(['name', 'hash', 'version']);
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('archived_games', function (Blueprint $table) {
$table->json('chat_messages')->after('dominant_faction');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('archived_games', function (Blueprint $table) {
$table->dropColumn('chat_messages');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('bad_chat_messages', function (Blueprint $table) {
$table->uuid('match_id')->after('id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('bad_chat_messages', function (Blueprint $table) {
$table->dropColumn('match_id');
});
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::dropIfExists('character_data_equipped_bonuses');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::create('character_data_equipped_bonuses', function (Blueprint $table) {
$table->id();
$table->foreignId('character_data_id')->constrained('character_data')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUuid('catalog_item_id')->constrained()->cascadeOnDelete()->cascadeOnDelete();
$table->timestamps();
});
}
};

View File

@ -14,11 +14,11 @@ public function up(): void
//Schema doesn't support tinyint
DB::statement("ALTER TABLE `game_files` ADD COLUMN `is_additional` TINYINT UNSIGNED NOT NULL DEFAULT '0' AFTER `action`;");
if (Schema::hasIndex('game_files', ['name', 'hash', 'version'])) {
if (Schema::hasIndex('game_files', ['name', 'hash', 'patchline'])) {
Schema::table('game_files', function (Blueprint $table) {
$table->dropUnique(['name', 'hash', 'version']);
$table->dropUnique(['name', 'hash', 'patchline']);
});
}
}
Schema::table('game_files', function (Blueprint $table) {
$table->renameColumn('name', 'filename');

View File

@ -0,0 +1,36 @@
<?php
use App\Enums\Game\ChallengeType;
use App\Enums\Game\Faction;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('timed_challenges', function (Blueprint $table) {
$table->id();
$table->enum('type', array_column(ChallengeType::cases(), 'value'));
$table->string('blueprint_path');
$table->enum('faction', array_column(Faction::cases(), 'value'));
$table->integer('completion_value');
$table->datetime('start_time');
$table->datetime('end_time');
$table->json('rewards')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('timed_challenges');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('player_data_timed_challenge', function (Blueprint $table) {
$table->foreignId('timed_challenge_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignId('player_data_id')->constrained('player_data')->cascadeOnDelete()->cascadeOnUpdate();
$table->unsignedInteger('progress')->default(0);
$table->boolean('claimed')->default(false);
$table->timestamps();
$table->unique(['timed_challenge_id', 'player_data_id'], 'unique_primary');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('player_data_timed_challenge');
}
};

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
const USER_MIGRATION_CURRENCY = 10000;
/**
* Run the migrations.
*/
public function up(): void
{
\App\Models\User\PlayerData::query()->update([
'currency_a' => self::USER_MIGRATION_CURRENCY,
'currency_b' => self::USER_MIGRATION_CURRENCY,
'currency_c' => self::USER_MIGRATION_CURRENCY,
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -0,0 +1,54 @@
<?php
use App\Enums\Launcher\FileAction;
use App\Models\GameFile;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (Schema::hasIndex('game_files', ['filename', 'hash', 'patchline'])) {
Schema::table('game_files', function (Blueprint $table) {
$table->dropUnique(['filename', 'hash', 'patchline']);
});
}
if (Schema::hasIndex('game_files', ['name'])) {
Schema::table('game_files', function (Blueprint $table) {
$table->dropUnique(['name']);
});
}
Schema::table('game_files', function (Blueprint $table) {
$table->foreignId('child_id')->nullable()->after('id')->constrained('game_files')->cascadeOnDelete();
$table->unique(['filename', 'hash', 'patchline', 'child_id']);
$table->unique(['name', 'child_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Remove all child records
GameFile::whereNotNull('child_id')->each(function ($gameFile) {
$gameFile->delete();
});
Schema::table('game_files', function (Blueprint $table) {
$table->dropUnique(['filename', 'hash', 'patchline', 'child_id']);
$table->dropUnique(['name', 'child_id']);
$table->dropForeign('game_files_child_id_foreign');
$table->dropColumn('child_id');
$table->unique(['filename', 'hash', 'patchline']);
$table->unique('name');
});
}
};

View File

@ -14,47 +14,54 @@ class MatchConfigSeeder extends Seeder
public function run(): void
{
DB::table('match_configurations')->delete();
MatchConfiguration::Create([
'name' => 'Matchconfig_Dev - 1v1',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'hunters' => 1,
'runners' => 1,
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Matchconfig - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Matchconfig - 1v4',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_2v10_4Needles.MatchConfig_Demo_2v10_4Needles',
'enabled' => true,
'weight' => 40,
'hunters' => 1,
'runners' => 4,
]);
MatchConfiguration::Create([
'name' => 'Matchconfig - 1v6',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_2v8_4Needles.MatchConfig_Demo_2v8_4Needles',
'hunters' => 1,
'runners' => 6,
'enabled' => true,
'weight' => 500,
]);
MatchConfiguration::Create([
'name' => 'Halloween Mode - Survival',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit.MatchConfig_Demo_HarvestYourExit',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Curefew 1v5 - Slums',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_SLU_DownTown.MatchConfig_SLU_DownTown',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Harvest Your Exit - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Harvest Your Exit - 1v4',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'enabled' => false,
'hunters' => 1,
'runners' => 4,
'weight' => 10,
]);
MatchConfiguration::Create([
'name' => 'Harvest Your Exit - 1v6',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'enabled' => false,
'hunters' => 1,
'runners' => 6,
'weight' => 10,
]);
MatchConfiguration::Create([
'name' => 'New Arctic Fortress',
'name' => 'Frosthold Citadel',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_ARC_Fortress.MatchConfig_ARC_Fortress',
'enabled' => true,
'weight' => 50,
]);
MatchConfiguration::Create([
'name' => 'Survival 1v5 - All Maps',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo.MatchConfig_Demo',
'enabled' => false,
'weight' => 50,
]);
MatchConfiguration::Create([
'name' => 'Fire in the Sky - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_ARC_BlastFurnace.MatchConfig_ARC_BlastFurnace',
'enabled' => true,
'enabled' => false,
'weight' => 200,
]);
MatchConfiguration::Create([
@ -100,51 +107,6 @@ public function run(): void
'hunters' => 2,
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'Custom',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Custom.MatchConfig_Custom',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Custom Match',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_CustomMatch.MatchConfig_CustomMatch',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Survival 2v10 - 4 Needles',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_2v10_4Needles.MatchConfig_Demo_2v10_4Needles',
'enabled' => false,
'hunters' => 2,
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'Survival 2v10 - 5 Needles',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_2v10_5Needles.MatchConfig_Demo_2v10_5Needles',
'enabled' => false,
'hunters' => 2,
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'Survival 2v8 - 4 Needles',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_2v8_4Needles.MatchConfig_Demo_2v8_4Needles',
'enabled' => false,
'hunters' => 2,
'runners' => 8,
]);
MatchConfiguration::Create([
'name' => 'Survival 2v8 - 5 Needles',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_2v8_5Needles.MatchConfig_Demo_2v8_5Needles',
'enabled' => false,
'hunters' => 2,
'runners' => 8,
]);
MatchConfiguration::Create([
'name' => 'Harvest Your Exit - 2v10',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit.MatchConfig_Demo_HarvestYourExit',
'enabled' => false,
'hunters' => 2,
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'Barren City - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_DES_City.MatchConfig_DES_City',
@ -160,7 +122,7 @@ public function run(): void
MatchConfiguration::Create([
'name' => 'Legions Rest - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_DES_Fortress.MatchConfig_DES_Fortress',
'enabled' => true,
'enabled' => false,
'weight' => 100,
]);
MatchConfiguration::Create([
@ -186,7 +148,7 @@ public function run(): void
MatchConfiguration::Create([
'name' => 'Dust & Blood - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_DES_Mayan.MatchConfig_DES_Mayan',
'enabled' => true,
'enabled' => false,
'weight' => 100,
]);
MatchConfiguration::Create([
@ -199,7 +161,7 @@ public function run(): void
MatchConfiguration::Create([
'name' => 'Blowout - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_DES_Oilfield.MatchConfig_DES_Oilfield',
'enabled' => true,
'enabled' => false,
'weight' => 100,
]);
MatchConfiguration::Create([
@ -212,7 +174,7 @@ public function run(): void
MatchConfiguration::Create([
'name' => 'Forest Citadel - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_JUN_Fortress.MatchConfig_JUN_Fortress',
'enabled' => true,
'enabled' => false,
'weight' => 50,
]);
MatchConfiguration::Create([
@ -222,20 +184,10 @@ public function run(): void
'hunters' => 2,
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'New Maps',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_NewMaps.MatchConfig_NewMaps',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'All New Arctic Maps',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_PRM_Special.MatchConfig_PRM_Special',
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'First Strike - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_RUI_All.MatchConfig_RUI_All',
'enabled' => true,
'enabled' => false,
'weight' => 100,
]);
MatchConfiguration::Create([
@ -246,13 +198,13 @@ public function run(): void
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'Tombstone - 1v5',
'name' => 'Echoes From The Past - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_WA_Cemetery.MatchConfig_WA_Cemetery',
'enabled' => true,
'enabled' => false,
'weight' => 140,
]);
MatchConfiguration::Create([
'name' => 'Tombstone - 2v10',
'name' => 'Echoes From The Past - 2v10',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_WA_Cemetery_2Hunters.MatchConfig_WA_Cemetery_2Hunters',
'enabled' => false,
'hunters' => 2,
@ -261,7 +213,7 @@ public function run(): void
MatchConfiguration::Create([
'name' => 'Salt Creek - 1v5',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_WA_Rivers.MatchConfig_WA_Rivers',
'enabled' => true,
'enabled' => false,
'weight' => 200,
]);
MatchConfiguration::Create([
@ -271,19 +223,5 @@ public function run(): void
'hunters' => 2,
'runners' => 10,
]);
MatchConfiguration::Create([
'name' => 'Harvest Your Exit - 1v1 (DEV)',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'hunters' => 1,
'runners' => 1,
'enabled' => false,
]);
MatchConfiguration::Create([
'name' => 'Harvest Your Exit - 1v6 (EXPERIMENTAL)',
'asset_path' => '/Game/Configuration/MatchConfig/MatchConfig_Demo_HarvestYourExit_1v5.MatchConfig_Demo_HarvestYourExit_1v5',
'hunters' => 1,
'runners' => 6,
'enabled' => false,
]);
}
}

View File

@ -18,7 +18,8 @@ services:
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
IGNITION_LOCAL_SITES_PATH: '${PWD}'
PHP_IDE_CONFIG: 'serverName=Docker'
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --watch --host=0.0.0.0 --admin-port=2019 --port=80"
# Uncomment if you want to enable octane locally
#SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --watch --host=0.0.0.0 --admin-port=2019 --port=80"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data
volumes:

888
dist/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

2
dist/package.json vendored
View File

@ -17,8 +17,6 @@
"vite": "^5.0.0"
},
"dependencies": {
"@inertiajs/vue3": "^1.0.14",
"@vitejs/plugin-vue": "^5.0.3",
"alpinejs": "^3.13.7",
"dotenv": "^16.4.2",
"jquery": "^3.7.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 965 KiB

After

Width:  |  Height:  |  Size: 900 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 3.3 MiB

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "400AE859-456112F4-EB7516A4-2821AEF3",
"ChallengeCompletionValue": 25.0,
"ChallengeCompletionValue": 20.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_Autocollect_Hunter.Challenge_Autocollect_Hunter",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "8DAE5C09-43DA33C2-8E82FC92-B9644702",
"ChallengeCompletionValue": 63.0,
"ChallengeCompletionValue": 50.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_Autocollect_Hunter.Challenge_Autocollect_Hunter",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "6DFAA855-48F1C349-CE1E6A9F-29188FCF",
"ChallengeCompletionValue": 250.0,
"ChallengeCompletionValue": 100.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_Autocollect_Hunter.Challenge_Autocollect_Hunter",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "590D3041-42028901-1FB868A8-9A43F7CF",
"ChallengeCompletionValue": 15.0,
"ChallengeCompletionValue": 5.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_Hacker_Hunter.Challenge_Hacker_Hunter",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "CF29BA96-4B65E429-DD2B559A-4D335CD3",
"ChallengeCompletionValue": 38.0,
"ChallengeCompletionValue": 15.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_Hacker_Hunter.Challenge_Hacker_Hunter",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "32749AFC-47E7C872-933FC9AB-8E76AD61",
"ChallengeCompletionValue": 150.0,
"ChallengeCompletionValue": 30.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_Hacker_Hunter.Challenge_Hacker_Hunter",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "0EAEAA3C-494F4F47-A35AF885-FDAD8939",
"ChallengeCompletionValue": 120.0,
"ChallengeCompletionValue": 90.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HackedCratesExplode_Hunter.Challenge_HackedCratesExplode_Hunter",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "43AB4B23-4479F000-2C8D798F-583CF6F4",
"ChallengeCompletionValue": 300.0,
"ChallengeCompletionValue": 120.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HackedCratesExplode_Hunter.Challenge_HackedCratesExplode_Hunter",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "0DEBB341-41DD7D71-9EE5C199-707ED8E4",
"ChallengeCompletionValue": 1200.0,
"ChallengeCompletionValue": 150.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HackedCratesExplode_Hunter.Challenge_HackedCratesExplode_Hunter",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "21CBC39E-4B2D8C37-439881A5-879570EC",
"ChallengeCompletionValue": 15.0,
"ChallengeCompletionValue": 10.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_SuddenInsight_Runner.Challenge_SuddenInsight_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "9BB2D0A0-4B6B1683-25E9A080-E84EBD05",
"ChallengeCompletionValue": 38.0,
"ChallengeCompletionValue": 15.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_SuddenInsight_Runner.Challenge_SuddenInsight_Runner",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "CF611386-4077974B-00CD37B5-1736426B",
"ChallengeCompletionValue": 150.0,
"ChallengeCompletionValue": 20.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_SuddenInsight_Runner.Challenge_SuddenInsight_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "B4B156CC-47C8D987-B9BDBEB9-10B12C9E",
"ChallengeCompletionValue": 60.0,
"ChallengeCompletionValue": 50.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_AmmoOpportunist_Runner.Challenge_AmmoOpportunist_Runner",
"SubPathString": ""
@ -115,4 +115,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "15C92554-4AA67E20-214C8AB0-9F5B33E9",
"ChallengeCompletionValue": 150.0,
"ChallengeCompletionValue": 75.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_AmmoOpportunist_Runner.Challenge_AmmoOpportunist_Runner",
"SubPathString": ""
@ -119,4 +119,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "950524C1-44BE794A-ABFFED81-42C119B4",
"ChallengeCompletionValue": 600.0,
"ChallengeCompletionValue": 100.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_AmmoOpportunist_Runner.Challenge_AmmoOpportunist_Runner",
"SubPathString": ""
@ -115,4 +115,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "FD314A69-4D049989-202D01BC-36269F44",
"ChallengeCompletionValue": 150.0,
"ChallengeCompletionValue": 100.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_FleetFeet_Runner.Challenge_FleetFeet_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "0A728AD5-4AF5EAB6-CB4356A0-74F01C1B",
"ChallengeCompletionValue": 375.0,
"ChallengeCompletionValue": 250.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_FleetFeet_Runner.Challenge_FleetFeet_Runner",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "688EC170-4C4EA4BC-B18181AD-E23E2C3D",
"ChallengeCompletionValue": 1500.0,
"ChallengeCompletionValue": 500.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_FleetFeet_Runner.Challenge_FleetFeet_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "4F99C439-49B5CAC8-7BCEB487-7B7FAB64",
"ChallengeCompletionValue": 60.0,
"ChallengeCompletionValue": 50.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HeatOfTheMoment_Runner.Challenge_HeatOfTheMoment_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "2ABDC29A-4D174CDC-114AEBAC-B898927E",
"ChallengeCompletionValue": 150.0,
"ChallengeCompletionValue": 75.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HeatOfTheMoment_Runner.Challenge_HeatOfTheMoment_Runner",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "37B650F1-45BF27B3-B670CB9D-84D58111",
"ChallengeCompletionValue": 600.0,
"ChallengeCompletionValue": 100.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HeatOfTheMoment_Runner.Challenge_HeatOfTheMoment_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "CA2FE47E-45EE792D-FA26FFA3-A30FC0A3",
"ChallengeCompletionValue": 60.0,
"ChallengeCompletionValue": 30.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HealFaster_Runner.Challenge_HealFaster_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -53,7 +53,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "0E514F8A-467DB3E7-DEBF99AB-AC2313A0",
"ChallengeCompletionValue": 150.0,
"ChallengeCompletionValue": 60.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HealFaster_Runner.Challenge_HealFaster_Runner",
"SubPathString": ""
@ -110,4 +110,4 @@
"Version": 1
}
}
]
]

View File

@ -49,7 +49,7 @@
"RequiredChallengesToComplete": [
{
"ChallengeId": "DE925104-43074D9D-B518B7A9-3A3A2FD9",
"ChallengeCompletionValue": 600.0,
"ChallengeCompletionValue": 90.0,
"ChallengeAsset": {
"AssetPathName": "/Game/Challenges/Progression/Challenge_HealFaster_Runner.Challenge_HealFaster_Runner",
"SubPathString": ""
@ -106,4 +106,4 @@
"Version": 1
}
}
]
]

View File

@ -6,81 +6,95 @@
/** @var Patchline $patchline */
@endphp
<x-layouts.admin>
<div class="w-full p-2 md:px-16 bg-inherit container mx-auto">
<h1 class="text-4xl font-semilight py-10">Deathgarden file manager</h1>
<form action="{{ url()->current() }}" method="GET">
<div class="flex items-center my-4 w-1/2">
<div class="flex-auto">
<label for="patchlines" class="mr-4 font-medium text-gray-900 dark:text-white">Select a
patchline:</label>
<x-inputs.dropdown
id="patchlines"
required
name="patchline"
:cases="Patchline::cases()"
:selected="$patchline"
onchange="this.form.submit()" />
</div>
<div class="flex flex-auto">
<label for="additional_files">Show additional mods:</label>
<x-inputs.checkbox
class="w-6 h-6 ml-4"
id="additional_files"
name="additional_files"
:checked="$showAdditionalFiles"
value="1"
onchange="this.form.submit()" />
</div>
<x-layouts.admin>
<div class="w-full p-2 md:px-16 bg-inherit container mx-auto">
<h1 class="text-4xl font-semilight py-10">Deathgarden file manager</h1>
<form action="{{ url()->current() }}" method="GET">
<div class="flex items-center my-4 w-1/2">
<div class="flex-auto">
<label for="patchlines" class="mr-4 font-medium text-gray-900 dark:text-white">
Select a patchline:
</label>
<x-inputs.dropdown
id="patchlines"
name="patchline"
:cases="Patchline::cases()"
:selected="$patchline"
onchange="this.form.submit()"
required />
</div>
</form>
<div class="flex flex-auto">
<label for="additional_files">
Show additional mods:
</label>
<x-inputs.checkbox
class="w-6 h-6 ml-4"
id="additional_files"
name="additional_files"
:checked="$showAdditionalFiles"
value="1"
onchange="this.form.submit()" />
</div>
</div>
</form>
<div class="flex flex-col items-center justify-center">
<table>
<thead>
@if($showAdditionalFiles)
<div class="flex flex-col items-center justify-center">
<table>
<thead>
<th class="w-12"></th>
@if ($showAdditionalFiles)
<th>Name</th>
<th>Description</th>
<th>Filename / Hash</th>
@else
@else
<th>Filename</th>
<th>Hash</th>
@endif
<th class="w-24">Size</th>
<th class="w-32">Last update</th>
<th>Actions</th>
</thead>
<tbody>
@foreach($files as $file)
<tr @class([ 'text-center' , '!bg-green-600 hover:!bg-green-500'=> $file->action === FileAction::ADD,
'!bg-red-600 hover:!bg-red-500' => $file->action === FileAction::DELETE
])
>
@if($showAdditionalFiles)
@endif
<th class="w-24">Size</th>
<th class="w-32">Last update</th>
<th class="w-36">Actions</th>
</thead>
<tbody>
@foreach ($files as $file)
<tr @class([
'text-center',
'!bg-green-600 hover:!bg-green-500' => $file->action === FileAction::ADD,
'!bg-red-600 hover:!bg-red-500' => $file->action === FileAction::DELETE,
])>
<td>
{{ $file->name }}
</td>
<td>
{{ $file->description }}
</td>
<td title="{{$file->game_path}}">
{{ $file->filename }} <br>
{{ $file->hash }}
@if ($file->children && $file->children->count() > 0)
<button class="expand-row" onclick="toggleRow({{ $file->id }})">
<x-icons.plus id="icon-plus-{{ $file->id }}" />
<x-icons.minus id="icon-minus-{{ $file->id }}" class="hidden" />
</button>
@endif
</td>
@if ($showAdditionalFiles)
<td>
{{ $file->name }}
</td>
<td>
{{ $file->description }}
</td>
<td title="{{ $file->game_path }}">
{{ $file->filename }} <br>
{{ $file->hash }}
</td>
@else
<td title="{{$file->game_path}}">
{{ $file->filename }}
</td>
<td>
{{ $file->hash }}
</td>
<td title="{{ $file->game_path }}">
{{ $file->filename }}
{{ $file->parent }}
</td>
<td>
{{ $file->hash }}
</td>
@endif
<td>
@if($file->fileExists())
{{ round($file->getFileSize() / 1000, 1) }} kB
@if ($file->fileExists())
{{ round($file->getFileSize() / 1000, 1) }} kB
@else
Error while fetching file
N/A
@endif
</td>
<td>
@ -91,9 +105,11 @@ class="w-6 h-6 ml-4"
method="POST">
@method('PUT')
@csrf
<button class="">Mark for {{ $file->action->value ? 'delete' : 'add' }}</button>
<button class="">Mark for
{{ $file->action->value ? 'delete' : 'add' }}</button>
</form>
<form action="{{ route('file-manager.destroy', ['file_manager' => $file->id]) }}"
<form
action="{{ route('file-manager.destroy', ['file_manager' => $file->getTopParent()->id]) }}"
method="POST">
@method('DELETE')
@csrf
@ -104,17 +120,58 @@ class="w-6 h-6 ml-4"
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="mx-auto w-full max-w-screen-2xl mt-8">
<form action="{{ route('file-manager.store') }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="patchline" value="{{ request()->input('patchline') ?? '0' }}">
<input type="hidden" name="is_additional" value="{{ request()->input('additional_files') ?? '0' }}">
@csrf
<div id="fileInputsContainer">
@if($showAdditionalFiles)
@if ($file->children && $file->children->count() > 0)
<tr class="expandable-content hidden hover:bg-opacity-60" id="content-{{ $file->id }}">
<td></td>
<td class="p-0" colspan="{{ $showAdditionalFiles ? '7' : '5' }}">
<table class="w-full text-center">
<thead>
<tr class="pointer-events-none">
@if ($showAdditionalFiles)
<th class="p-2 border-b-2 border-inherit w-36">File uploaded</th>
<th class="p-2 border-b-2 border-inherit">Description</th>
@else
<th class="p-2 border-b-2 border-inherit w-64">File uploaded</th>
@endif
<th class="p-2 border-b-2 border-inherit w-72">Hash</th>
</tr>
</thead>
<tbody>
@foreach ($file->children as $child)
<tr @class([
'text-center',
'!bg-red-600 hover:!bg-red-500' => $child->action === FileAction::ADD,
'' => $child->action === FileAction::DELETE,
])>
<td class="p-2">
{{ $child->created_at }}
</td>
@if ($showAdditionalFiles)
<td class="p-2">
{{ $child->description }}
</td>
@endif
<td class="p-2">{{ $child->hash }}</td>
</tr>
@endforeach
</tbody>
</table>
</td>
</tr>
@endif
@endforeach
</tbody>
</table>
<div class="mx-auto w-full max-w-screen-2xl mt-8">
<form action="{{ route('file-manager.store') }}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="patchline" value="{{ request()->input('patchline') ?? '0' }}">
<input type="hidden" name="is_additional"
value="{{ request()->input('additional_files') ?? '0' }}">
@csrf
<div id="fileInputsContainer">
@if ($showAdditionalFiles)
<div class="flex flex-wrap gap-4">
<div class="w-2/12 flex items-center mb-2">
<x-inputs.text-input name="game_mod_name[]" placeholder="Mod name" />
@ -123,35 +180,33 @@ class="w-6 h-6 ml-4"
<x-inputs.text-input name="game_mod_description[]" placeholder="Mod description" />
</div>
</div>
@endif
<div class="flex flex-wrap gap-4">
<div class="w-6/12 flex items-center mb-2">
<x-inputs.text-input name="game_path[]" />
</div>
<select name="file_action[]"
class="w-1/12 rounded-md h-[41.43px] bg-gray-800/75 border border-gray-600 text-white text-sm focus:border-[#6A64F1] focus:shadow-md block px-4 py-2">
<option value="1" selected>Add</option>
<option value="0">Delete</option>
</select>
<div class="flex items-center mb-2">
<x-inputs.file-input name="files[]" />
</div>
@endif
<div class="flex flex-wrap gap-4">
<div class="w-6/12 flex items-center mb-2">
<x-inputs.text-input name="game_path[]" />
</div>
<select name="file_action[]"
class="w-1/12 rounded-md h-[41.43px] bg-gray-800/75 border border-gray-600 text-white text-sm focus:border-[#6A64F1] focus:shadow-md block px-4 py-2">
<option value="1" selected>Add</option>
<option value="0">Delete</option>
</select>
<div class="flex items-center mb-2">
<x-inputs.file-input name="files[]" />
</div>
</div>
<div class="flex justify-end mt-4">
@if (!$showAdditionalFiles)
<div class="flex justify-end mt-4">
@if (!$showAdditionalFiles)
<div class="w-auto mx-2">
<x-inputs.button type="button" id="addFileInput">
Add More Files
</x-inputs.button>
</div>
@endif
<div class="w-auto mx-2">
<x-inputs.button>
Submit
</x-inputs.button>
</div>
@endif
<div class="w-auto mx-2">
<x-inputs.button>
Submit
</x-inputs.button>
</div>
</form>
@ -159,19 +214,34 @@ class="w-1/12 rounded-md h-[41.43px] bg-gray-800/75 border border-gray-600 text-
</div>
</div>
<script>
function addFileInputEventListener(fileInput) {
fileInput.addEventListener('change', function(event) {
let selectedFile = event.target.files[0];
let textInput = event.target.closest('.flex-wrap').querySelector('input[name="game_path[]"]');
if (textInput) {
if (selectedFile) {
var fileName = selectedFile.name;
fileName = examineFilePaths(fileName);
textInput.value = fileName;
} else {
textInput.value = '';
}
<script>
function toggleRow(id) {
const content = document.getElementById('content-' + id);
const iconPlus = document.getElementById('icon-plus-' + id);
const iconMinus = document.getElementById('icon-minus-' + id);
if (content.classList.contains('hidden')) {
content.classList.remove('hidden');
iconMinus.classList.remove('hidden');
iconPlus.classList.add('hidden');
} else {
content.classList.add('hidden');
iconMinus.classList.add('hidden');
iconPlus.classList.remove('hidden');
}
}
function addFileInputEventListener(fileInput) {
fileInput.addEventListener('change', function(event) {
let selectedFile = event.target.files[0];
let textInput = event.target.closest('.flex-wrap').querySelector('input[name="game_path[]"]');
if (textInput) {
if (selectedFile) {
var fileName = selectedFile.name;
fileName = examineFilePaths(fileName);
textInput.value = fileName;
} else {
textInput.value = '';
}
});
}
@ -197,7 +267,7 @@ function examineFilePaths(fileName) {
}
}
@if(!$showAdditionalFiles)
@if (!$showAdditionalFiles)
document.getElementById('addFileInput').addEventListener('click', function() {
let container = document.getElementById('fileInputsContainer');
let newInput = document.createElement('div');
@ -219,11 +289,13 @@ function examineFilePaths(fileName) {
// Add event listener to file input of the newly created input group
var fileInput = newInput.querySelector('input[name="files[]"]');
addFileInputEventListener(fileInput);
});
@endif
// Add event listener to file input of the initial input group
var initialFileInput = document.querySelector('input[name="files[]"]');
addFileInputEventListener(initialFileInput);
</script>
</x-layouts.admin>
window.scrollTo(0, document.body.scrollHeight);
});
@endif
// Add event listener to file input of the initial input group
var initialFileInput = document.querySelector('input[name="files[]"]');
addFileInputEventListener(initialFileInput);
</script>
</x-layouts.admin>

View File

@ -3,14 +3,15 @@
$experienceMultipliers = \App\Models\Admin\ExperienceMultipliers::get();
$currencyMultipliers = \App\Models\Admin\CurrencyMultipliers::get();
$matchmakingSettings = \App\Models\Admin\MatchmakingSettings::get();
@endphp
<x-layouts.admin>
<div class="container mx-auto border bg-slate-800 border-slate-500 rounded-xl p-4 my-6">
<div class="flex flex-col gap-4 px-4 lg:[&>form>div]:px-52 [&>form>div]:flex [&>form>div]:justify-between [&>form>div]:items-center [&>form_label]:text-xl">
<form action="{{ route('match-configuration.save.experience') }}" method="post">
<form action="{{ route('match-configuration.save.experience') }}" method="post" class="[&>div]:m-2">
@csrf
<span class="headline">
<span class="headline mb-2">
Experience Multipliers
</span>
@ -139,7 +140,7 @@
Save
</x-inputs.button>
</form>
<form action="{{ route('match-configuration.save.currency') }}" method="post" class="mt-8">
<form action="{{ route('match-configuration.save.currency') }}" method="post" class="mt-8 [&>div]:m-2">
@csrf
<span class="headline">
Currency Multipliers
@ -190,6 +191,29 @@
Save
</x-inputs.button>
</form>
<form action="{{ route('match-configuration.save.matchmaking') }}" method="post" class="mt-8 [&>div]:m-2">
@csrf
<span class="headline mb-2">
Matchmaking Settings
</span>
<div>
<label for="matchmakingWaitingTime">
1v4 and 1v5 matchmaking wait duration
</label>
<x-inputs.number
id="matchmakingWaitingTime"
name="matchmakingWaitingTime"
value="{{ $matchmakingSettings->matchWaitingTime }}"
step="1"
required
/>
</div>
<x-inputs.button class="save mt-2">
Save
</x-inputs.button>
</form>
</div>
</div>
</x-layouts.admin>

Some files were not shown because too many files have changed in this diff Show More