/*
* Copyright © 2004-2021 L2J Server
*
* This file is part of L2J Server.
*
* L2J Server is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* L2J Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.l2jserver.gameserver.model.drops.strategy;
import com.l2jserver.commons.util.Rnd;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.drops.GeneralDropItem;
import com.l2jserver.gameserver.model.drops.GroupedGeneralDropItem;
import com.l2jserver.gameserver.model.drops.IDropItem;
import com.l2jserver.gameserver.model.holders.ItemHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.l2jserver.gameserver.config.Configuration.general;
/**
* @author Battlecruiser
*/
public interface IGroupedItemDropCalculationStrategy {
Logger LOG = LoggerFactory.getLogger(IGroupedItemDropCalculationStrategy.class);
/**
* The default strategy used in L2J to calculate drops. When the group's chance raises over 100% and group has precise calculation, the dropped item's amount increases.
*/
IGroupedItemDropCalculationStrategy DEFAULT_STRATEGY = new IGroupedItemDropCalculationStrategy() {
private final Map singleItemCache = new ConcurrentHashMap<>();
private GeneralDropItem getSingleItem(GroupedGeneralDropItem dropItem) {
final GeneralDropItem item1 = dropItem.getItems().iterator().next();
singleItemCache.putIfAbsent(dropItem, new GeneralDropItem(item1.getItemId(), item1.getMin(), item1.getMax(), (item1.getChance() * dropItem.getChance()) / 100, //
item1.getAmountStrategy(), item1.getChanceStrategy(), dropItem.getPreciseStrategy(), dropItem.getKillerChanceModifierStrategy(), item1.getDropCalculationStrategy()));
return singleItemCache.get(dropItem);
}
@Override
public List calculateDrops(GroupedGeneralDropItem dropItem, L2Character victim, L2Character killer) {
if (dropItem.getItems().size() == 1) {
return getSingleItem(dropItem).calculateDrops(victim, killer);
}
GroupedGeneralDropItem normalized = dropItem.normalizeMe(victim, killer);
if (normalized.getChance() > (Rnd.nextDouble() * 100)) {
final double random = (Rnd.nextDouble() * 100);
double totalChance = 0;
for (GeneralDropItem item2 : normalized.getItems()) {
// Grouped item chance rates should not be modified (the whole magic was already done by normalizing thus the items' chance sum is always 100%).
totalChance += item2.getChance();
if (totalChance > random) {
int amountMultiply = 1;
if (dropItem.isPreciseCalculated() && (normalized.getChance() >= 100)) {
amountMultiply = (int) (normalized.getChance()) / 100;
if ((normalized.getChance() % 100) > (Rnd.nextDouble() * 100)) {
amountMultiply++;
}
}
return Collections.singletonList(new ItemHolder(item2.getItemId(), Rnd.get(item2.getMin(victim), item2.getMax(victim)) * amountMultiply));
}
}
}
return null;
}
};
/**
* This strategy calculates a group's drop by calculating drops of its individual items and merging its results.
*/
IGroupedItemDropCalculationStrategy DISBAND_GROUP = (item, victim, killer) -> {
List dropped = new ArrayList<>();
for (IDropItem dropItem : item.extractMe()) {
dropped.addAll(dropItem.calculateDrops(victim, killer));
}
return dropped.isEmpty() ? null : dropped;
};
/**
* This strategy when group has precise calculation rolls multiple times over group to determine drops when group's chance raises over 100% instead of just multiplying the dropped item's amount. Thus it can produce different items from group at once.
*/
IGroupedItemDropCalculationStrategy PRECISE_MULTIPLE_GROUP_ROLLS = (item, victim, killer) -> {
if (!item.isPreciseCalculated()) {
// if item hasn't precise calculation there's no change from DEFAULT_STRATEGY
return DEFAULT_STRATEGY.calculateDrops(item, victim, victim);
}
GroupedGeneralDropItem newItem = new GroupedGeneralDropItem(item.getChance(), DEFAULT_STRATEGY, item.getKillerChanceModifierStrategy(), IPreciseDeterminationStrategy.NEVER);
newItem.setItems(item.getItems());
GroupedGeneralDropItem normalized = newItem.normalizeMe(victim, killer);
// Let's determine the number of rolls.
int rolls = (int) (normalized.getChance() / 100);
if ((Rnd.nextDouble() * 100) < (normalized.getChance() % 100)) {
rolls++;
}
List dropped = new ArrayList<>(rolls);
for (int i = 0; i < rolls; i++) {
// As further normalizing on already normalized drop group does nothing, we can just pass the calculation to DEFAULT_STRATEGY with precise calculation disabled as we handle it.
List drops = normalized.calculateDrops(victim, killer);
if (drops != null) {
dropped.addAll(drops);
}
}
if (general().preciseDropMultipleRollsAggregateDrops()) {
Map countByItemId = new HashMap<>();
for (ItemHolder drop : dropped) {
Long currentCount = countByItemId.getOrDefault(drop.getId(), 0L);
countByItemId.put(drop.getId(), currentCount + drop.getCount());
}
dropped.clear();
for (Map.Entry entry : countByItemId.entrySet()) {
dropped.add(new ItemHolder(entry.getKey(), entry.getValue()));
}
}
return dropped.isEmpty() ? null : dropped;
};
List calculateDrops(GroupedGeneralDropItem item, L2Character victim, L2Character killer);
}