Loot/Refloot

* fixed Reference Loot's drop chance counting towards the adaptive drop chance of loot within the same loot group.
This commit is contained in:
Sarjuuk
2020-04-04 15:17:17 +02:00
parent 15246ea964
commit c2a1556e8a
2 changed files with 83 additions and 79 deletions

View File

@@ -159,6 +159,7 @@ class DbSimple_Mysqli extends DbSimple_Database
{ {
$this->_lastQuery = $queryMain; $this->_lastQuery = $queryMain;
$this->_expandPlaceholders($queryMain, false); $this->_expandPlaceholders($queryMain, false);
mysqli_ping($this->link);
$result = mysqli_query($this->link, $queryMain[0]); $result = mysqli_query($this->link, $queryMain[0]);
if ($result === false) if ($result === false)
return $this->_setDbError($queryMain[0]); return $this->_setDbError($queryMain[0]);

View File

@@ -25,6 +25,7 @@ class Loot
private $entry = 0; // depending on the lookup itemId oder templateId private $entry = 0; // depending on the lookup itemId oder templateId
private $results = []; private $results = [];
private $chanceMods = [];
private $lootTemplates = array( private $lootTemplates = array(
LOOT_REFERENCE, // internal LOOT_REFERENCE, // internal
LOOT_ITEM, // item LOOT_ITEM, // item
@@ -40,7 +41,7 @@ class Loot
LOOT_SPELL // spell LOOT_SPELL // spell
); );
public function &iterate() public function &iterate() : iterable
{ {
reset($this->results); reset($this->results);
@@ -48,15 +49,15 @@ class Loot
yield $k => $this->results[$k]; yield $k => $this->results[$k];
} }
public function getResult() public function getResult() : array
{ {
return $this->results; return $this->results;
} }
private function createStack($l) // issue: TC always has an equal distribution between min/max private function createStack(array $l) : string // issue: TC always has an equal distribution between min/max
{ {
if (empty($l['min']) || empty($l['max']) || $l['max'] <= $l['min']) if (empty($l['min']) || empty($l['max']) || $l['max'] <= $l['min'])
return null; return '';
$stack = []; $stack = [];
for ($i = $l['min']; $i <= $l['max']; $i++) for ($i = $l['min']; $i <= $l['max']; $i++)
@@ -66,7 +67,7 @@ class Loot
return json_encode($stack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON ! return json_encode($stack, JSON_NUMERIC_CHECK); // do not replace with Util::toJSON !
} }
private function storeJSGlobals($data) private function storeJSGlobals(array $data) : void
{ {
foreach ($data as $type => $jsData) foreach ($data as $type => $jsData)
{ {
@@ -81,7 +82,62 @@ class Loot
} }
} }
private function getByContainerRecursive($tableName, $lootId, &$handledRefs, $groupId = 0, $baseChance = 1.0) private function calcChance(array $refs, array $parents = []) : array
{
$retData = [];
$retKeys = [];
foreach ($refs as $rId => $ref)
{
// check for possible database inconsistencies
if (!$ref['chance'] && !$ref['isGrouped'])
trigger_error('Loot by Item: Ungrouped Item/Ref '.$ref['item'].' has 0% chance assigned!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] > 100)
trigger_error('Loot by Item: Group with Item/Ref '.$ref['item'].' has '.number_format($ref['sumChance'], 2).'% total chance! Some items cannot drop!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] >= 100 && !$ref['chance'])
trigger_error('Loot by Item: Item/Ref '.$ref['item'].' with adaptive chance cannot drop. Group already at 100%!', E_USER_WARNING);
$chance = abs($ref['chance'] ?: (100 - $ref['sumChance']) / $ref['nZeroItems']) / 100;
// apply inherited chanceMods
if (isset($this->chanceMods[$ref['item']]))
{
$chance *= $this->chanceMods[$ref['item']][0];
$chance = 1 - pow(1 - $chance, $this->chanceMods[$ref['item']][1]);
}
// save chance for parent-ref
$this->chanceMods[$rId] = [$chance, $ref['multiplier']];
// refTemplate doesn't point to a new ref -> we are done
if (!in_array($rId, $parents))
{
$data = array(
'percent' => $chance,
'stack' => [$ref['min'], $ref['max']],
'count' => 1 // ..and one for the sort script
);
if ($_ = self::createStack($ref))
$data['pctstack'] = $_;
// sort highest chances first
$i = 0;
for (; $i < count($retData); $i++)
if ($retData[$i]['percent'] < $data['percent'])
break;
array_splice($retData, $i, 0, [$data]);
array_splice($retKeys, $i, 0, [$rId]);
}
}
return array_combine($retKeys, $retData);
}
private function getByContainerRecursive(string $tableName, int $lootId, array &$handledRefs, int $groupId = 0, float $baseChance = 1.0) : ?array
{ {
$loot = []; $loot = [];
$rawItems = []; $rawItems = [];
@@ -101,7 +157,8 @@ class Loot
'quest' => $entry['QuestRequired'], 'quest' => $entry['QuestRequired'],
'group' => $entry['GroupId'], 'group' => $entry['GroupId'],
'parentRef' => $tableName == LOOT_REFERENCE ? $lootId : 0, 'parentRef' => $tableName == LOOT_REFERENCE ? $lootId : 0,
'realChanceMod' => $baseChance 'realChanceMod' => $baseChance,
'groupChance' => 0
); );
// if ($entry['LootMode'] > 1) // if ($entry['LootMode'] > 1)
@@ -167,11 +224,15 @@ class Loot
} }
else if ($entry['GroupId'] && $entry['Chance']) else if ($entry['GroupId'] && $entry['Chance'])
{ {
if (empty($groupChances[$entry['GroupId']]))
$groupChances[$entry['GroupId']] = 0;
$groupChances[$entry['GroupId']] += $entry['Chance'];
$set['groupChance'] = $entry['Chance']; $set['groupChance'] = $entry['Chance'];
if (!$entry['Reference'])
{
if (empty($groupChances[$entry['GroupId']]))
$groupChances[$entry['GroupId']] = 0;
$groupChances[$entry['GroupId']] += $entry['Chance'];
}
} }
else // shouldn't have happened else // shouldn't have happened
{ {
@@ -192,21 +253,19 @@ class Loot
trigger_error('Loot entry '.$lootId.' / group '.$k.' has a total chance of '.number_format($sum, 2).'%. Some items cannot drop!', E_USER_WARNING); trigger_error('Loot entry '.$lootId.' / group '.$k.' has a total chance of '.number_format($sum, 2).'%. Some items cannot drop!', E_USER_WARNING);
$sum = 100; $sum = 100;
} }
// is applied as backReference to items with 0-chance
$cnt = empty($nGroupEquals[$k]) ? 1 : $nGroupEquals[$k]; $groupChances[$k] = (100 - $sum) / ($nGroupEquals[$k] ?: 1);
$groupChances[$k] = (100 - $sum) / $cnt; // is applied as backReference to items with 0-chance
} }
return [$loot, array_unique($rawItems)]; return [$loot, array_unique($rawItems)];
} }
public function getByContainer($table, $entry) public function getByContainer(string $table, int $entry): bool
{ {
$this->entry = intVal($entry); $this->entry = intVal($entry);
if (!in_array($table, $this->lootTemplates) || !$this->entry) if (!in_array($table, $this->lootTemplates) || !$this->entry)
return null; return false;
/* /*
todo (high): implement conditions on loot (and conditions in general) todo (high): implement conditions on loot (and conditions in general)
@@ -321,7 +380,7 @@ class Loot
return true; return true;
} }
public function getByItem($entry, $maxResults = CFG_SQL_LIMIT_DEFAULT, $lootTableList = []) public function getByItem(int $entry, int $maxResults = CFG_SQL_LIMIT_DEFAULT, array $lootTableList = []) : bool
{ {
$this->entry = intVal($entry); $this->entry = intVal($entry);
@@ -350,13 +409,12 @@ class Loot
['achievement', [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []] ['achievement', [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []]
); );
$refResults = []; $refResults = [];
$chanceMods = [];
$query = 'SELECT $query = 'SELECT
lt1.entry AS ARRAY_KEY, lt1.entry AS ARRAY_KEY,
IF(lt1.reference = 0, lt1.item, lt1.reference) AS item, IF(lt1.reference = 0, lt1.item, lt1.reference) AS item,
lt1.chance, lt1.chance,
SUM(IF(lt2.chance = 0, 1, 0)) AS nZeroItems, SUM(IF(lt2.chance = 0, 1, 0)) AS nZeroItems,
SUM(lt2.chance) AS sumChance, SUM(IF(lt2.reference = 0, lt2.chance, 0)) AS sumChance,
IF(lt1.groupid > 0, 1, 0) AS isGrouped, IF(lt1.groupid > 0, 1, 0) AS isGrouped,
IF(lt1.reference = 0, lt1.mincount, 1) AS min, IF(lt1.reference = 0, lt1.mincount, 1) AS min,
IF(lt1.reference = 0, lt1.maxcount, 1) AS max, IF(lt1.reference = 0, lt1.maxcount, 1) AS max,
@@ -369,61 +427,6 @@ class Loot
%s %s
GROUP BY lt2.entry, lt2.groupid'; GROUP BY lt2.entry, lt2.groupid';
$calcChance = function ($refs, $parents = []) use (&$chanceMods)
{
$retData = [];
$retKeys = [];
foreach ($refs as $rId => $ref)
{
// check for possible database inconsistencies
if (!$ref['chance'] && !$ref['isGrouped'])
trigger_error('Loot by Item: Ungrouped Item/Ref '.$ref['item'].' has 0% chance assigned!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] > 100)
trigger_error('Loot by Item: Group with Item/Ref '.$ref['item'].' has '.number_format($ref['sumChance'], 2).'% total chance! Some items cannot drop!', E_USER_WARNING);
if ($ref['isGrouped'] && $ref['sumChance'] >= 100 && !$ref['chance'])
trigger_error('Loot by Item: Item/Ref '.$ref['item'].' with adaptive chance cannot drop. Group already at 100%!', E_USER_WARNING);
$chance = abs($ref['chance'] ?: (100 - $ref['sumChance']) / $ref['nZeroItems']) / 100;
// apply inherited chanceMods
if (isset($chanceMods[$ref['item']]))
{
$chance *= $chanceMods[$ref['item']][0];
$chance = 1 - pow(1 - $chance, $chanceMods[$ref['item']][1]);
}
// save chance for parent-ref
$chanceMods[$rId] = [$chance, $ref['multiplier']];
// refTemplate doesn't point to a new ref -> we are done
if (!in_array($rId, $parents))
{
$data = array(
'percent' => $chance,
'stack' => [$ref['min'], $ref['max']],
'count' => 1 // ..and one for the sort script
);
if ($_ = self::createStack($ref))
$data['pctstack'] = $_;
// sort highest chances first
$i = 0;
for (; $i < count($retData); $i++)
if ($retData[$i]['percent'] < $data['percent'])
break;
array_splice($retData, $i, 0, [$data]);
array_splice($retKeys, $i, 0, [$rId]);
}
}
return array_combine($retKeys, $retData);
};
/* /*
get references containing the item get references containing the item
*/ */
@@ -442,7 +445,7 @@ class Loot
array_keys($curRefs) array_keys($curRefs)
); );
$refResults += $calcChance($curRefs, array_column($newRefs, 'item')); $refResults += $this->calcChance($curRefs, array_column($newRefs, 'item'));
} }
/* /*
@@ -453,7 +456,7 @@ class Loot
if ($lootTableList && !in_array($this->lootTemplates[$i], $lootTableList)) if ($lootTableList && !in_array($this->lootTemplates[$i], $lootTableList))
continue; continue;
$result = $calcChance(DB::World()->select( $result = $this->calcChance(DB::World()->select(
sprintf($query, '{lt1.reference IN (?a) OR }(lt1.reference = 0 AND lt1.item = ?d)'), sprintf($query, '{lt1.reference IN (?a) OR }(lt1.reference = 0 AND lt1.item = ?d)'),
$this->lootTemplates[$i], $this->lootTemplates[$i], $this->lootTemplates[$i], $this->lootTemplates[$i],
$refResults ? array_keys($refResults) : DBSIMPLE_SKIP, $refResults ? array_keys($refResults) : DBSIMPLE_SKIP,
@@ -494,7 +497,7 @@ class Loot
$srcData = $srcObj->getListviewData(); $srcData = $srcObj->getListviewData();
foreach ($srcObj->iterate() as $__id => $curTpl) foreach ($srcObj->iterate() as $curTpl)
{ {
switch ($curTpl['typeCat']) switch ($curTpl['typeCat'])
{ {
@@ -632,4 +635,4 @@ class Loot
} }
} }
?> ?>