/*
* This file is part of the OdinMS Maple Story Server Copyright (C) 2008 ~ 2010
* Patrick Huy <patrick.huy@frz.cc> Matthias Butz <matze@odinms.de> Jan
* Christian Meyer <vimes@odinms.de>
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation. You may not use, modify or distribute this
* program under any other version of the GNU Affero General Public License.
*
* This program 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package javastory.game.quest;
import java.io.Serializable;
import java.util.Map;
import javastory.channel.ChannelCharacter;
import javastory.channel.ChannelServer;
import javastory.channel.client.ISkill;
import javastory.channel.server.InventoryManipulator;
import javastory.game.GameConstants;
import javastory.game.Gender;
import javastory.game.Inventory;
import javastory.game.Jobs;
import javastory.game.Stat;
import javastory.game.data.ItemInfoProvider;
import javastory.game.data.SkillInfoProvider;
import javastory.tools.Randomizer;
import javastory.tools.packets.ChannelPackets;
import javastory.wz.WzData;
import javastory.wz.WzDataTool;
import com.google.common.collect.Maps;
public class QuestAction implements Serializable {
private static final long serialVersionUID = 9179541993413738569L;
private final QuestActionType actionType;
private final WzData data;
private final int questId;
public QuestAction(final int quest, final QuestActionType type, final WzData data) {
this.actionType = type;
this.data = data;
this.questId = quest;
}
private static boolean canGetItem(final WzData item, final ChannelCharacter c) {
if (item.getChildByPath("gender") != null) {
final Gender gender = Gender.fromNumber(WzDataTool.getInt(item.getChildByPath("gender")));
if (!gender.equals(Gender.UNSPECIFIED)) {
return false;
} else if (!gender.equals(c.getGender())) {
return false;
}
}
if (item.getChildByPath("job") != null) {
final int job = WzDataTool.getInt(item.getChildByPath("job"));
if (job < 100) {
final int codec = getJobBy5ByteEncoding(job);
if (codec / 100 != c.getJobId() / 100) {
return false;
}
} else if (job > 3000) {
final int playerjob = c.getJobId();
final int codec = getJobByEncoding(job, playerjob);
if (codec >= 1000) {
if (codec / 1000 != c.getJobId() / 1000) {
return false;
}
} else {
if (codec / 100 != c.getJobId() / 100) {
return false;
}
}
} else {
if (job != c.getJobId()) {
return false;
}
}
}
return true;
}
public final boolean restoreLostItem(final ChannelCharacter c, final int itemid) {
if (this.actionType == QuestActionType.ITEM) {
int retitem;
for (final WzData iEntry : this.data.getChildren()) {
retitem = WzDataTool.getInt(iEntry.getChildByPath("id"), -1);
if (retitem == itemid) {
if (!c.haveItem(retitem, 1, true, false)) {
InventoryManipulator.addById(c.getClient(), retitem, (short) 1);
}
return true;
}
}
}
return false;
}
public void runStart(final ChannelCharacter c, final Integer extSelection) {
QuestStatus status;
switch (this.actionType) {
case EXP:
status = c.getQuestStatus(this.questId);
if (status.getForfeited() > 0) {
break;
}
final int baseExp = WzDataTool.getInt(this.data, 0);
final int exp = this.calculateExpGain(c, baseExp);
c.gainExp(exp, true, true, true);
break;
case ITEM:
// first check for randomness in item selection
final Map<Integer, Integer> props = Maps.newHashMap();
WzData prop;
for (final WzData iEntry : this.data.getChildren()) {
prop = iEntry.getChildByPath("prop");
if (prop != null && WzDataTool.getInt(prop) != -1 && canGetItem(iEntry, c)) {
for (int i = 0; i < WzDataTool.getInt(iEntry.getChildByPath("prop")); i++) {
props.put(props.size(), WzDataTool.getInt(iEntry.getChildByPath("id")));
}
}
}
int selection = 0;
int extNum = 0;
if (props.size() > 0) {
selection = props.get(Randomizer.nextInt(props.size()));
}
for (final WzData iEntry : this.data.getChildren()) {
if (!canGetItem(iEntry, c)) {
continue;
}
final int id = WzDataTool.getInt(iEntry.getChildByPath("id"), -1);
if (iEntry.getChildByPath("prop") != null) {
if (WzDataTool.getInt(iEntry.getChildByPath("prop")) == -1) {
if (extSelection != extNum++) {
continue;
}
} else if (id != selection) {
continue;
}
}
final short count = (short) WzDataTool.getInt(iEntry.getChildByPath("count"), 1);
if (count < 0) { // remove items
// NOTE: This is very wrong: do not use exceptions for expected scenarios.
// They're supposed to be used for EXCEPTIONAL scenarios. Duh.
//try {
final Inventory inventory = c.getInventoryForItem(id);
InventoryManipulator.removeById(c.getClient(), inventory, id, count * -1, true, false);
//} catch (InventoryException ie) {
//System.err.println("[h4x] Completing a quest without meeting the requirements" + ie);
//}
c.getClient().write(ChannelPackets.getShowItemGain(id, count, true));
} else { // add items
// final int period = MapleDataTool.getInt(iEntry.getChildByPath("period"), 0);
InventoryManipulator.addById(c.getClient(), id, count/*, "", -1, 0*/);
c.getClient().write(ChannelPackets.getShowItemGain(id, count, true));
}
}
break;
// case NEXTQUEST:
// int nextquest = MapleDataTool.getInt(data);
// Need to somehow make the chat popup for the next quest...
// break;
case MESO:
status = c.getQuestStatus(this.questId);
if (status.getForfeited() > 0) {
break;
}
c.gainMeso(WzDataTool.getInt(this.data, 0), true, false, true);
break;
case QUEST:
for (final WzData qEntry : this.data) {
final int nextQuestId = WzDataTool.getInt(qEntry.getChildByPath("id"));
final int nextQuestState = WzDataTool.getInt(qEntry.getChildByPath("state"), 0);
//TODO: Hmmmmm.
c.getQuestStatus(nextQuestId).setState((byte) nextQuestState);
}
break;
case SKILL:
//TODO needs gain/lost message?
for (final WzData sEntry : this.data) {
final int skillid = WzDataTool.getInt(sEntry.getChildByPath("id"));
final int skillLevel = WzDataTool.getInt(sEntry.getChildByPath("skillLevel"), 0);
final int masterLevel = WzDataTool.getInt(sEntry.getChildByPath("masterLevel"), 0);
final ISkill skillObject = SkillInfoProvider.getSkill(skillid);
for (final WzData applicableJob : sEntry.getChildByPath("job")) {
if (skillObject.isBeginnerSkill() || c.getJobId() == WzDataTool.getInt(applicableJob)) {
c.changeSkillLevel(skillObject, (byte) Math.max(skillLevel, c.getCurrentSkillLevel(skillObject)), (byte) Math.max(masterLevel, c
.getMasterSkillLevel(skillObject)));
break;
}
}
}
break;
case FAME:
status = c.getQuestStatus(this.questId);
if (status.getForfeited() > 0) {
break;
}
final int fameGain = WzDataTool.getInt(this.data, 0);
c.addFame(fameGain);
c.updateSingleStat(Stat.FAME, c.getFame());
c.getClient().write(ChannelPackets.getShowFameGain(fameGain));
break;
case BUFF_ITEM_ID:
status = c.getQuestStatus(this.questId);
if (status.getForfeited() > 0) {
break;
}
final int tobuff = WzDataTool.getInt(this.data, -1);
if (tobuff == -1) {
break;
}
ItemInfoProvider.getInstance().getItemEffect(tobuff).applyTo(c);
break;
case INFO_NUMBER: {
// System.out.println("quest : "+MapleDataTool.getInt(data, 0)+"");
// MapleQuest.getInstance(MapleDataTool.getInt(data, 0)).forceComplete(c, 0);
// break;
}
default:
break;
}
}
private int calculateExpGain(final ChannelCharacter c, final int baseExp) {
final float expMultiplier;
if (c.getLevel() <= 10) {
expMultiplier = 1.0f;
} else {
expMultiplier = ChannelServer.getInstance().getExpRate();
}
final int exp = Math.round(baseExp * expMultiplier);
return exp;
}
public boolean checkEnd(final ChannelCharacter c, final Integer extSelection) {
switch (this.actionType) {
case ITEM: {
// first check for randomness in item selection
final Map<Integer, Integer> props = Maps.newHashMap();
for (final WzData iEntry : this.data.getChildren()) {
final WzData prop = iEntry.getChildByPath("prop");
if (prop != null && WzDataTool.getInt(prop) != -1 && canGetItem(iEntry, c)) {
for (int i = 0; i < WzDataTool.getInt(iEntry.getChildByPath("prop")); i++) {
props.put(props.size(), WzDataTool.getInt(iEntry.getChildByPath("id")));
}
}
}
int selection = 0;
int extNum = 0;
if (props.size() > 0) {
selection = props.get(Randomizer.nextInt(props.size()));
}
byte eq = 0, use = 0, setup = 0, etc = 0, cash = 0;
for (final WzData iEntry : this.data.getChildren()) {
if (!canGetItem(iEntry, c)) {
continue;
}
final int id = WzDataTool.getInt(iEntry.getChildByPath("id"), -1);
if (iEntry.getChildByPath("prop") != null) {
if (WzDataTool.getInt(iEntry.getChildByPath("prop")) == -1) {
if (extSelection != extNum++) {
continue;
}
} else if (id != selection) {
continue;
}
}
final short count = (short) WzDataTool.getInt(iEntry.getChildByPath("count"), 1);
if (count < 0) { // remove items
if (!c.haveItem(id, count, false, true)) {
c.sendNotice(1, "You are short of some item to complete quest.");
return false;
}
} else { // add items
switch (GameConstants.getInventoryType(id)) {
case EQUIP:
eq++;
break;
case USE:
use++;
break;
case SETUP:
setup++;
break;
case ETC:
etc++;
break;
case CASH:
cash++;
break;
}
}
}
if (c.getEquipInventory().getNumFreeSlot() <= eq) {
c.sendNotice(1, "Plaase make space for your Equip inventory.");
return false;
} else if (c.getUseInventory().getNumFreeSlot() <= use) {
c.sendNotice(1, "Plaase make space for your Use inventory.");
return false;
} else if (c.getSetupInventory().getNumFreeSlot() <= setup) {
c.sendNotice(1, "Plaase make space for your Setup inventory.");
return false;
} else if (c.getEtcInventory().getNumFreeSlot() <= etc) {
c.sendNotice(1, "Plaase make space for your Etc inventory.");
return false;
} else if (c.getCashInventory().getNumFreeSlot() <= cash) {
c.sendNotice(1, "Plaase make space for your Cash inventory.");
return false;
}
return true;
}
case MESO: {
final int meso = WzDataTool.getInt(this.data, 0);
if (c.getMeso() + meso < 0) { // Giving, overflow
c.sendNotice(1, "Meso exceed the max amount, 2147483647.");
return false;
} else if (c.getMeso() < meso) {
c.sendNotice(1, "Insufficient meso.");
return false;
}
return true;
}
}
return true;
}
public void runEnd(final ChannelCharacter c, final Integer extSelection) {
switch (this.actionType) {
case EXP: {
final int baseExp = WzDataTool.getInt(this.data, 0);
final int exp = this.calculateExpGain(c, baseExp);
c.gainExp(exp, true, true, true);
break;
}
case ITEM: {
// first check for randomness in item selection
final Map<Integer, Integer> props = Maps.newHashMap();
for (final WzData iEntry : this.data.getChildren()) {
final WzData prop = iEntry.getChildByPath("prop");
if (prop != null && WzDataTool.getInt(prop) != -1 && canGetItem(iEntry, c)) {
for (int i = 0; i < WzDataTool.getInt(iEntry.getChildByPath("prop")); i++) {
props.put(props.size(), WzDataTool.getInt(iEntry.getChildByPath("id")));
}
}
}
int selection = 0;
int extNum = 0;
if (props.size() > 0) {
selection = props.get(Randomizer.nextInt(props.size()));
}
for (final WzData iEntry : this.data.getChildren()) {
if (!canGetItem(iEntry, c)) {
continue;
}
final int id = WzDataTool.getInt(iEntry.getChildByPath("id"), -1);
if (iEntry.getChildByPath("prop") != null) {
if (WzDataTool.getInt(iEntry.getChildByPath("prop")) == -1) {
if (extSelection != extNum++) {
continue;
}
} else if (id != selection) {
continue;
}
}
final short count = (short) WzDataTool.getInt(iEntry.getChildByPath("count"), 1);
if (count < 0) { // remove items
InventoryManipulator.removeById(c.getClient(), c.getInventoryForItem(id), id, count * -1, true, false);
c.getClient().write(ChannelPackets.getShowItemGain(id, count, true));
} else { // add items
// final int period = MapleDataTool.getInt(iEntry.getChildByPath("period"), 0);
InventoryManipulator.addById(c.getClient(), id, count, ""/*, -1, period * 60 * 1000*/);
c.getClient().write(ChannelPackets.getShowItemGain(id, count, true));
}
}
break;
}
// case NEXTQUEST:
// int nextquest = MapleDataTool.getInt(data);
// Need to somehow make the chat popup for the next quest...
// break;
case MESO: {
c.gainMeso(WzDataTool.getInt(this.data, 0), true, false, true);
break;
}
case QUEST: {
for (final WzData qEntry : this.data) {
final int nextQuestId = WzDataTool.getInt(qEntry.getChildByPath("id"));
final int nextQuestState = WzDataTool.getInt(qEntry.getChildByPath("state"), 0);
//TODO: Hmmmmm.
c.getQuestStatus(nextQuestId).setState((byte) nextQuestState);
}
break;
}
case SKILL: {
for (final WzData sEntry : this.data) {
final int skillid = WzDataTool.getInt(sEntry.getChildByPath("id"));
final int skillLevel = WzDataTool.getInt(sEntry.getChildByPath("skillLevel"), 0);
final int masterLevel = WzDataTool.getInt(sEntry.getChildByPath("masterLevel"), 0);
final ISkill skillObject = SkillInfoProvider.getSkill(skillid);
for (final WzData applicableJob : sEntry.getChildByPath("job")) {
if (skillObject.isBeginnerSkill() || c.getJobId() == WzDataTool.getInt(applicableJob)) {
c.changeSkillLevel(skillObject, (byte) Math.max(skillLevel, c.getCurrentSkillLevel(skillObject)), (byte) Math.max(masterLevel, c
.getMasterSkillLevel(skillObject)));
break;
}
}
}
break;
}
case FAME: {
final int fameGain = WzDataTool.getInt(this.data, 0);
c.addFame(fameGain);
c.updateSingleStat(Stat.FAME, c.getFame());
c.getClient().write(ChannelPackets.getShowFameGain(fameGain));
break;
}
case BUFF_ITEM_ID: {
final int tobuff = WzDataTool.getInt(this.data, -1);
if (tobuff == -1) {
break;
}
ItemInfoProvider.getInstance().getItemEffect(tobuff).applyTo(c);
break;
}
case INFO_NUMBER: {
// System.out.println("quest : "+MapleDataTool.getInt(data, 0)+"");
// MapleQuest.getInstance(MapleDataTool.getInt(data, 0)).forceComplete(c, 0);
// break;
}
default:
break;
}
}
private static int getJobBy5ByteEncoding(final int encoded) {
switch (encoded) {
case 2:
case 3:
return 100;
case 4:
return 200;
case 8:
return 300;
case 16:
return 400;
case 32:
case 63:
return 500;
default:
return 0;
}
}
private static int getJobByEncoding(final int encoded, final int playerjob) {
switch (encoded) {
case 1049601:
if (Jobs.isCygnus(playerjob)) {
return 1000;
}
return 0;
case 2099202:
if (Jobs.isCygnus(playerjob)) {
return 1100;
} else if (Jobs.isAran(playerjob)) {
return 2000;
}
return 100;
case 4100:
if (Jobs.isCygnus(playerjob)) {
return 1200;
}
return 200;
case 8200:
if (Jobs.isCygnus(playerjob)) {
return 1300;
}
return 300;
case 16400:
if (Jobs.isCygnus(playerjob)) {
return 1400;
}
return 400;
case 32800:
if (Jobs.isCygnus(playerjob)) {
return 1500;
}
return 500;
default:
return 0;
}
}
public QuestActionType getType() {
return this.actionType;
}
@Override
public String toString() {
return this.actionType + ": " + this.data;
}
}