| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| NpcBunnies | Cranky | Mutator | N/A | |||||
namespace npcBunniesDialogue
{
string NpcQuestEventName(int eventID)
{
if(eventID == OBJECT::XMASNORMTURTLE) return "xmasTurtles";
if(eventID == OBJECT::NORMTURTLE) return "turtles";
if(eventID == OBJECT::XMASLIZARD) return "xmasLizards";
if(eventID == OBJECT::LIZARD) return "lizards";
if(eventID == OBJECT::BAT) return "bats";
if(eventID == OBJECT::BEE) return "bees";
if(eventID == OBJECT::BEEBOY) return "bee boys";
if(eventID == OBJECT::BEES) return "bees";
if(eventID == OBJECT::BUTTERFLY) return "butterflies";
if(eventID == OBJECT::CRAB) return "crabs";
if(eventID == OBJECT::DEMON) return "demons";
if(eventID == OBJECT::DOGGYDOGG) return "dogs";
if(eventID == OBJECT::DRAGON) return "dragons";
if(eventID == OBJECT::DRAGONFLY) return "dragonflies";
if(eventID == OBJECT::FATCHICK) return "fat chicks";
if(eventID == OBJECT::FENCER) return "fencers";
if(eventID == OBJECT::FISH) return "fish";
if(eventID == OBJECT::HATTER) return "hatters";
if(eventID == OBJECT::HELMUT) return "helmuts";
if(eventID == OBJECT::LABRAT) return "lab rats";
if(eventID == OBJECT::MONKEY) return "monkeys";
if(eventID == OBJECT::STANDMONKEY) return "stand monkeys";
if(eventID == OBJECT::TUBETURTLE) return "tube turtles";
if(eventID == OBJECT::TUFTURT) return "tuf turtles";
if(eventID == OBJECT::RAPIER) return "rapiers";
if(eventID == OBJECT::RAVEN) return "ravens";
if(eventID == OBJECT::SKELETON) return "skeletons";
if(eventID == OBJECT::SPARK) return "sparks";
if(eventID == OBJECT::SUCKER) return "suckers";
if(eventID == OBJECT::FLOATSUCKER) return "float suckers";
if(eventID == OBJECT::CAT) return "cats";
if(eventID == OBJECT::PACMANGHOST) return "pacman ghosts";
if(eventID == OBJECT::MOTH) return "moths";
if(eventID == OBJECT::WITCH) return "witches";
return "object " + eventID;
}
string NpcQuestLineText(npcBunniesQuests::NpcQuest@ quest, jjPLAYER@ play, int npcId, int setIndex)
{
if(quest is null) return "";
int progress = 0;
string name = "quest";
if(quest.questType == npcBunniesQuests::QuestType::Coins) { progress = play.coins; name = "coins"; }
else if(quest.questType == npcBunniesQuests::QuestType::Gems) { progress = play.gems[quest.gemType]; name = "gems"; }
else if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster || quest.questType == npcBunniesQuests::QuestType::DefeatBoss) { progress = npcBunniesQuests::GetQuestDefeatedObjectProgress(quest, play, npcId, setIndex); name = NpcQuestEventName(quest.objectType); }
if(progress > quest.amount) progress = quest.amount;
return name + ": " + progress + "/" + quest.amount;
}
void CollectNpcQuestGroup(jjPLAYER@ play, int wantedGroup, string &out npcName, array<string> &out names, array<int> &out progress, array<int> &out amounts)
{
npcName = "";
names.resize(0);
progress.resize(0);
amounts.resize(0);
if(play is null) return;
string playerName = play.nameUnformatted;
int group = 0;
for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
{
npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
array<string> keys;
array<string> groupNames;
array<int> groupProgress;
array<int> groupAmounts;
for(uint setIndex = 0; setIndex < npc.dialogueSets.length; setIndex++)
{
if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
DialogueSet@ set = npc.dialogueSets[setIndex];
if(set.choicesAnswerMethod != npcBunniesCore::DialogueAction::Quest) continue;
for(uint q = 0; q < set.quests.length; q++)
{
npcBunniesQuests::NpcQuest@ quest = set.quests[q];
if(quest is null) continue;
string name = "quest";
int current = 0;
if(quest.questType == npcBunniesQuests::QuestType::Coins) { name = "coins"; current = play.coins; }
else if(quest.questType == npcBunniesQuests::QuestType::Gems) { name = "gems"; current = play.gems[quest.gemType]; }
else if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster || quest.questType == npcBunniesQuests::QuestType::DefeatBoss) { name = NpcQuestEventName(quest.objectType); current = npcBunniesQuests::GetQuestDefeatedObjectProgress(quest, play, int(npcId), int(setIndex)); }
string key = quest.questType + "|" + quest.objectType + "|" + quest.gemType;
int found = keys.find(key);
if(found >= 0) groupAmounts[found] += quest.amount;
else
{
keys.insertLast(key);
groupNames.insertLast(name);
groupProgress.insertLast(current);
groupAmounts.insertLast(quest.amount);
}
}
}
if(keys.length > 0)
{
if(group == wantedGroup)
{
npcName = npc.name;
names = groupNames;
progress = groupProgress;
amounts = groupAmounts;
npcBunniesCore::npcBunnies[npcId] = npc;
return;
}
group++;
}
npcBunniesCore::npcBunnies[npcId] = npc;
}
}
void CollectNpcQuestGroupDetails(jjPLAYER@ play, int wantedGroup, string &out npcName, array<npcBunniesQuests::NpcQuest@> &out quests, array<int> &out progress)
{
npcName = "";
quests.resize(0);
progress.resize(0);
if(play is null) return;
string playerName = play.nameUnformatted;
int group = 0;
for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
{
npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
array<npcBunniesQuests::NpcQuest@> groupQuests;
array<int> groupProgress;
for(uint setIndex = 0; setIndex < npc.dialogueSets.length; setIndex++)
{
if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
DialogueSet@ set = npc.dialogueSets[setIndex];
if(set.choicesAnswerMethod != npcBunniesCore::DialogueAction::Quest) continue;
for(uint q = 0; q < set.quests.length; q++)
{
npcBunniesQuests::NpcQuest@ quest = set.quests[q];
if(quest is null) continue;
int current = 0;
if(quest.questType == npcBunniesQuests::QuestType::Coins) current = play.coins;
else if(quest.questType == npcBunniesQuests::QuestType::Gems) current = play.gems[quest.gemType];
else if(quest.questType == npcBunniesQuests::QuestType::DefeatMonster || quest.questType == npcBunniesQuests::QuestType::DefeatBoss) current = npcBunniesQuests::GetQuestDefeatedObjectProgress(quest, play, int(npcId), int(setIndex));
if(current > quest.amount) current = quest.amount;
groupQuests.insertLast(quest);
groupProgress.insertLast(current);
}
}
if(groupQuests.length > 0)
{
if(group == wantedGroup)
{
npcName = npc.name;
quests = groupQuests;
progress = groupProgress;
npcBunniesCore::npcBunnies[npcId] = npc;
return;
}
group++;
}
npcBunniesCore::npcBunnies[npcId] = npc;
}
}
string NpcQuestSetTrackerText(DialogueSet@ set)
{
if(set is null) return "";
if(set.questInProgressLine != "") return set.questInProgressLine;
if(set.dialogue.length > 0 && set.dialogue[0] !is null) return set.dialogue[0].text;
return "";
}
void CollectNpcQuestGroupText(jjPLAYER@ play, int wantedGroup, string &out npcName, array<string> &out lines)
{
npcName = "";
lines.resize(0);
if(play is null) return;
string playerName = play.nameUnformatted;
int group = 0;
for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
{
npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
array<string> groupLines;
for(uint setIndex = 0; setIndex < npc.dialogueSets.length; setIndex++)
{
if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
DialogueSet@ set = npc.dialogueSets[setIndex];
if(set.choicesAnswerMethod != npcBunniesCore::DialogueAction::Quest || set.quests.length == 0) continue;
string line = NpcQuestSetTrackerText(set);
if(line != "" && groupLines.find(line) < 0) groupLines.insertLast(line);
}
if(groupLines.length > 0)
{
if(group == wantedGroup)
{
npcName = npc.name;
lines = groupLines;
npcBunniesCore::npcBunnies[npcId] = npc;
return;
}
group++;
}
else
{
bool hasQuest = false;
for(uint setIndex = 0; setIndex < npc.dialogueSets.length && !hasQuest; setIndex++)
hasQuest = npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex)) && npc.dialogueSets[setIndex].choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest && npc.dialogueSets[setIndex].quests.length > 0;
if(hasQuest) group++;
}
npcBunniesCore::npcBunnies[npcId] = npc;
}
}
int CountNpcQuestGroups(jjPLAYER@ play)
{
if(play is null) return 0;
string playerName = play.nameUnformatted;
int groups = 0;
for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
{
npcBunniesCore::npcBunny npc = npcBunniesCore::EnsureNpcQuestState(npcBunniesCore::npcBunnies[npcId]);
bool hasGroup = false;
for(uint setIndex = 0; setIndex < npc.dialogueSets.length && !hasGroup; setIndex++)
{
if(!npcBunniesCore::HasPlayerSeenQuestOffer(playerName, int(npcId), int(setIndex))) continue;
DialogueSet@ set = npc.dialogueSets[setIndex];
if(set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest && set.quests.length > 0) hasGroup = true;
}
if(hasGroup) groups++;
npcBunniesCore::npcBunnies[npcId] = npc;
}
return groups;
}
string NpcQuestDialogText(jjPLAYER@ play, int page)
{
string textNpcName;
array<string> textLines;
CollectNpcQuestGroupText(play, page, textNpcName, textLines);
if(textNpcName != "" && textLines.length > 0)
{
string text = textNpcName + ":";
for(uint i = 0; i < textLines.length; i++)
text += "\n" + textLines[i];
return text;
}
string npcName;
array<string> names;
array<int> progress;
array<int> amounts;
CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
if(npcName == "") return "No active quests.";
string text = npcName + ":";
for(uint i = 0; i < names.length; i++)
{
int shownProgress = progress[i] > amounts[i] ? amounts[i] : progress[i];
text += "\n" + names[i] + ": " + shownProgress + "/" + amounts[i];
}
return text;
}
string NpcQuestBasicDialogText(jjPLAYER@ play, int page)
{
string npcName;
array<string> names;
array<int> progress;
array<int> amounts;
CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
if(npcName == "") return "No active quests.";
string text = npcName + ":";
for(uint i = 0; i < names.length; i++)
{
int shownProgress = progress[i] > amounts[i] ? amounts[i] : progress[i];
text += "\n" + names[i] + ": " + shownProgress + "/" + amounts[i];
}
return text;
}
int CountNpcQuestDialogLines(jjPLAYER@ play, int page)
{
return DbWrappedSmallLineCount(NpcQuestDialogText(play, page), 160);
}
int CountNpcQuestBasicDialogLines(jjPLAYER@ play, int page)
{
string npcName;
array<string> names;
array<int> progress;
array<int> amounts;
CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
if(npcName == "") return 1;
return int(names.length) + 1;
}
const int NPC_QUEST_DIALOG_CACHE_TICKS = 15;
int cachedNpcQuestDialogTick = -999999;
int cachedNpcQuestDialogPlayerId = -1;
int cachedNpcQuestDialogPage = -1;
bool cachedNpcQuestDialogShowInfo = false;
int cachedNpcQuestDialogTextW = -1;
int cachedNpcQuestDialogVisibleHeight = -1;
int cachedNpcQuestDialogGroups = 0;
int cachedNpcQuestDialogBasicLines = 1;
int cachedNpcQuestDialogMaxScroll = 0;
string cachedNpcQuestDialogText = "No active quests.";
array<string> cachedNpcQuestDialogWrappedLines;
array<string> cachedNpcQuestDialogBasicDrawLines;
bool cachedNpcQuestLevelQuestDefinitionsKnown = false;
bool cachedNpcQuestLevelHasQuestDefinitions = false;
void InvalidateNpcQuestDialogCache()
{
cachedNpcQuestDialogTick = -999999;
cachedNpcQuestLevelQuestDefinitionsKnown = false;
}
bool NpcLevelHasQuestDefinitions()
{
if(cachedNpcQuestLevelQuestDefinitionsKnown) return cachedNpcQuestLevelHasQuestDefinitions;
cachedNpcQuestLevelQuestDefinitionsKnown = true;
cachedNpcQuestLevelHasQuestDefinitions = false;
for(uint npcId = 0; npcId < npcBunniesCore::npcBunnies.length; npcId++)
{
for(uint setIndex = 0; setIndex < npcBunniesCore::npcBunnies[npcId].dialogueSets.length; setIndex++)
{
DialogueSet@ set = npcBunniesCore::npcBunnies[npcId].dialogueSets[setIndex];
if(set !is null && set.choicesAnswerMethod == npcBunniesCore::DialogueAction::Quest && set.quests.length > 0)
{
cachedNpcQuestLevelHasQuestDefinitions = true;
return true;
}
}
}
return false;
}
void EnsureNpcQuestDialogCache(jjPLAYER@ play, int page, bool showInfo, int textW, int visibleHeight, bool force = false)
{
int playerId = play is null ? -1 : play.playerID;
if(textW < 20) textW = 20;
if(visibleHeight < 14) visibleHeight = 14;
if(!force
&& cachedNpcQuestDialogPlayerId == playerId
&& cachedNpcQuestDialogPage == page
&& cachedNpcQuestDialogShowInfo == showInfo
&& cachedNpcQuestDialogTextW == textW
&& cachedNpcQuestDialogVisibleHeight == visibleHeight
&& jjGameTicks < cachedNpcQuestDialogTick + NPC_QUEST_DIALOG_CACHE_TICKS)
return;
cachedNpcQuestDialogTick = jjGameTicks;
cachedNpcQuestDialogPlayerId = playerId;
cachedNpcQuestDialogPage = page;
cachedNpcQuestDialogShowInfo = showInfo;
cachedNpcQuestDialogTextW = textW;
cachedNpcQuestDialogVisibleHeight = visibleHeight;
cachedNpcQuestDialogGroups = NpcLevelHasQuestDefinitions() ? CountNpcQuestGroups(play) : 0;
if(cachedNpcQuestDialogGroups <= 0)
{
cachedNpcQuestDialogBasicLines = 1;
cachedNpcQuestDialogText = "No active quests.";
cachedNpcQuestDialogBasicDrawLines.resize(0);
cachedNpcQuestDialogBasicDrawLines.insertLast(cachedNpcQuestDialogText);
}
else
{
string basicText = NpcQuestBasicDialogText(play, page);
cachedNpcQuestDialogBasicDrawLines = basicText.split("\n");
if(cachedNpcQuestDialogBasicDrawLines.length == 0) cachedNpcQuestDialogBasicDrawLines.insertLast("");
cachedNpcQuestDialogBasicLines = int(cachedNpcQuestDialogBasicDrawLines.length);
cachedNpcQuestDialogText = showInfo ? NpcQuestDialogText(play, page) : basicText;
}
if(showInfo)
{
cachedNpcQuestDialogWrappedLines = DbWrappedSmallLines(cachedNpcQuestDialogText, textW);
cachedNpcQuestDialogMaxScroll = int(cachedNpcQuestDialogWrappedLines.length) - DbVisibleSmallLines(visibleHeight);
if(cachedNpcQuestDialogMaxScroll < 0) cachedNpcQuestDialogMaxScroll = 0;
}
else
{
cachedNpcQuestDialogWrappedLines.resize(0);
cachedNpcQuestDialogMaxScroll = 0;
}
}
int DrawCachedNpcQuestDialogWrappedText(jjCANVAS@ canvas, int x, int y, int maxY, int scrollLine, int lineHeight = 14)
{
for(uint i = 0; i < cachedNpcQuestDialogWrappedLines.length; i++)
{
if(int(i) < scrollLine) continue;
if(y + lineHeight > maxY) break;
canvas.drawString(x, y, cachedNpcQuestDialogWrappedLines[i], STRING::SMALL, STRING::NORMAL);
y += lineHeight;
}
return y;
}
int DrawCachedNpcQuestBasicLines(jjCANVAS@ canvas, int x, int y)
{
for(uint i = 0; i < cachedNpcQuestDialogBasicDrawLines.length; i++)
canvas.drawString(x + (i == 0 ? 0 : 12), y + int(i) * 18, cachedNpcQuestDialogBasicDrawLines[i], STRING::SMALL, STRING::NORMAL);
return int(cachedNpcQuestDialogBasicDrawLines.length);
}
int DrawNpcQuestLines(jjCANVAS@ canvas, jjPLAYER@ play, int x, int y, int page)
{
string npcName;
array<string> names;
array<int> progress;
array<int> amounts;
CollectNpcQuestGroup(play, page, npcName, names, progress, amounts);
if(npcName == "")
{
canvas.drawString(x, y, "No active quests.", STRING::SMALL, STRING::NORMAL);
return 1;
}
canvas.drawString(x, y, npcName + ":", STRING::SMALL, STRING::NORMAL);
int lines = 1;
for(uint i = 0; i < names.length; i++)
{
int shownProgress = progress[i] > amounts[i] ? amounts[i] : progress[i];
canvas.drawString(x + 12, y + lines * 18, names[i] + ": " + shownProgress + "/" + amounts[i], STRING::SMALL, STRING::NORMAL);
lines++;
}
return lines;
}
void SlowNpcUiAnimation(npcBunniesAnimations::NpcUiAnimation &inout icon, int ticksPerFrame = 16)
{
if(icon.frameCount <= 1) return;
icon.firstFrame = jjGameTicks / ticksPerFrame;
icon.ticksPerFrame = ticksPerFrame;
}
void GetNpcQuestTrackerUiAnimation(npcBunniesQuests::NpcQuest@ quest, npcBunniesAnimations::NpcUiAnimation &inout icon)
{
if(quest is null) { npcBunniesAnimations::SetNpcUiAnimation(icon, 0, 0, 0, 1, 6); icon.hidden = true; return; }
if(quest.iconHidden) { npcBunniesAnimations::CopyNpcUiAnimation(quest.icon, icon); icon.hidden = true; return; }
npcBunniesAnimations::GetQuestUiAnimation(quest, icon);
SlowNpcUiAnimation(icon, 16);
}
void GetNpcRewardTrackerUiAnimation(npcBunniesQuests::NpcReward@ reward, npcBunniesAnimations::NpcUiAnimation &inout icon)
{
if(reward is null) { npcBunniesAnimations::SetNpcUiAnimation(icon, 0, 0, 0, 1, 6); icon.hidden = true; return; }
if(reward.iconHidden) { npcBunniesAnimations::CopyNpcUiAnimation(reward.icon, icon); icon.hidden = true; return; }
npcBunniesAnimations::GetRewardUiAnimation(reward, icon);
SlowNpcUiAnimation(icon, 16);
}
void DrawNpcQuestObjectives(jjCANVAS@ canvas, jjPLAYER@ play, int x, int y, int w, int h, int page)
{
string npcName;
array<npcBunniesQuests::NpcQuest@> quests;
array<int> progress;
CollectNpcQuestGroupDetails(play, page, npcName, quests, progress);
if(quests.length == 0) return;
array<string> keys;
array<npcBunniesQuests::NpcQuest@> iconQuests;
array<int> amounts;
for(uint i = 0; i < quests.length; i++)
{
npcBunniesQuests::NpcQuest@ quest = quests[i];
if(quest is null || quest.iconHidden) continue;
string key = quest.questType + "|" + quest.objectType + "|" + quest.gemType;
int found = keys.find(key);
if(found >= 0) amounts[found] += quest.amount;
else
{
keys.insertLast(key);
iconQuests.insertLast(quest);
amounts.insertLast(quest.amount);
}
}
int rowX = x;
int rowY = y;
for(uint i = 0; i < iconQuests.length; i++)
{
npcBunniesQuests::NpcQuest@ quest = iconQuests[i];
int amount = amounts[i];
int itemW = npcBunniesAnimations::GetNpcItemAmountWidth(amount);
if(rowX > x && rowX + itemW > x + w)
{
rowX = x;
rowY += 24;
}
if(rowY + 20 > y + h) break;
npcBunniesAnimations::NpcUiAnimation icon;
GetNpcQuestTrackerUiAnimation(quest, icon);
npcBunniesAnimations::DrawNpcItemAmount(canvas, rowX, rowY, amount, icon);
rowX += itemW + 16;
}
}
void DrawNpcQuestSetObjectives(jjCANVAS@ canvas, DialogueSet@ set, int x, int y, int w, int h, int ticksPerFrame = 20)
{
if(set is null || (set.quests.length == 0 && set.rewards.length == 0)) return;
array<string> keys;
array<npcBunniesQuests::NpcQuest@> iconQuests;
array<int> amounts;
for(uint i = 0; i < set.quests.length; i++)
{
npcBunniesQuests::NpcQuest@ quest = set.quests[i];
if(quest is null || quest.iconHidden) continue;
string key = quest.questType + "|" + quest.objectType + "|" + quest.gemType;
int found = keys.find(key);
if(found >= 0) amounts[found] += quest.amount;
else
{
keys.insertLast(key);
iconQuests.insertLast(quest);
amounts.insertLast(quest.amount);
}
}
array<string> rewardKeys;
array<npcBunniesQuests::NpcReward@> iconRewards;
array<int> rewardAmounts;
for(uint i = 0; i < set.rewards.length; i++)
{
npcBunniesQuests::NpcReward@ reward = set.rewards[i];
if(reward is null || reward.iconHidden) continue;
string key = reward.rewardType + "|" + reward.gemType + "|" + reward.weaponType + "|" + reward.guidePathIndex + "|" + reward.triggerId + "|" + int(reward.x) + "|" + int(reward.y);
int found = rewardKeys.find(key);
int amount = npcBunniesAnimations::GetRewardDisplayAmount(reward);
if(found >= 0) rewardAmounts[found] += amount;
else
{
rewardKeys.insertLast(key);
iconRewards.insertLast(reward);
rewardAmounts.insertLast(amount);
}
}
int questRowY = y;
int rewardRowY = y + 24;
int labelW = 18;
canvas.drawString(x, questRowY, "Q:", STRING::SMALL, STRING::NORMAL);
canvas.drawString(x, rewardRowY, "R:", STRING::SMALL, STRING::NORMAL);
int rowX = x + labelW;
for(uint i = 0; i < iconQuests.length; i++)
{
npcBunniesQuests::NpcQuest@ quest = iconQuests[i];
int amount = amounts[i];
int itemW = npcBunniesAnimations::GetNpcItemAmountWidth(amount);
if(rowX > x + labelW && rowX + itemW > x + w) break;
if(questRowY + 20 > y + h) break;
npcBunniesAnimations::NpcUiAnimation icon;
GetNpcQuestTrackerUiAnimation(quest, icon);
SlowNpcUiAnimation(icon, ticksPerFrame);
npcBunniesAnimations::DrawNpcItemAmount(canvas, rowX, questRowY, amount, icon);
rowX += itemW + 16;
}
rowX = x + labelW;
for(uint i = 0; i < iconRewards.length; i++)
{
npcBunniesQuests::NpcReward@ reward = iconRewards[i];
int amount = rewardAmounts[i];
int itemW = npcBunniesAnimations::GetNpcItemAmountWidth(amount);
if(rowX > x + labelW && rowX + itemW > x + w) break;
if(rewardRowY + 20 > y + h) break;
npcBunniesAnimations::NpcUiAnimation icon;
GetNpcRewardTrackerUiAnimation(reward, icon);
SlowNpcUiAnimation(icon, ticksPerFrame);
npcBunniesAnimations::DrawNpcItemAmount(canvas, rowX, rewardRowY, amount, icon);
rowX += itemW + 16;
}
}
int cachedNpcQuestHudKey = -1;
string cachedNpcQuestHudText = "";
int cachedNpcQuestHudWidth = 0;
void EnsureNpcQuestHudCache()
{
if(cachedNpcQuestHudKey == npcBunniesCore::NPC_QUEST_DIALOG_KEY) return;
cachedNpcQuestHudKey = npcBunniesCore::NPC_QUEST_DIALOG_KEY;
cachedNpcQuestHudText = "Quests (" + npcBunniesCore::NpcQuestKeyName(npcBunniesCore::NPC_QUEST_DIALOG_KEY) + ")";
cachedNpcQuestHudWidth = jjGetStringWidth(cachedNpcQuestHudText, STRING::SMALL, STRING::NORMAL);
}
string NpcQuestHudText()
{
EnsureNpcQuestHudCache();
return cachedNpcQuestHudText;
}
int NpcQuestHudWidth()
{
EnsureNpcQuestHudCache();
return cachedNpcQuestHudWidth;
}
bool NpcQuestHotkeyEnabled()
{
return jjGameMode == GAME::SP;
}
void DrawNpcQuestHud(jjCANVAS@ canvas)
{
if(!NpcQuestHotkeyEnabled()) return;
int x = npcBunniesCore::HUD_COUNTS_X;
int y = npcBunniesCore::HUD_COUNTS_Y + 32;
canvas.drawString(x, y, NpcQuestHudText(), STRING::SMALL, STRING::NORMAL);
}
jjPLAYER@ NpcQuestLocalPlayer()
{
for(int i = 0; i < jjLocalPlayerCount; i++)
{
jjPLAYER@ play = jjLocalPlayers[i];
if(play.isInGame) return play;
}
return jjLocalPlayers[0];
}
void EnsureNpcQuestDialogPosition(int w = 315, int h = 180)
{
if(npcBunniesCore::npcQuestDialogX < 0) npcBunniesCore::npcQuestDialogX = npcBunniesCore::HUD_COUNTS_X;
if(npcBunniesCore::npcQuestDialogY < 0) npcBunniesCore::npcQuestDialogY = npcBunniesCore::HUD_COUNTS_Y + 52;
if(npcBunniesCore::npcQuestDialogX + w > jjResolutionWidth) npcBunniesCore::npcQuestDialogX = jjResolutionWidth - w;
if(npcBunniesCore::npcQuestDialogY + h > jjResolutionHeight) npcBunniesCore::npcQuestDialogY = jjResolutionHeight - h;
if(npcBunniesCore::npcQuestDialogX < 0) npcBunniesCore::npcQuestDialogX = 0;
if(npcBunniesCore::npcQuestDialogY < 0) npcBunniesCore::npcQuestDialogY = 0;
}
void ClampNpcQuestDialogPage(jjPLAYER@ play)
{
int groups = CountNpcQuestGroups(play);
if(groups <= 0) { npcBunniesCore::npcQuestDialogPage = 0; return; }
if(npcBunniesCore::npcQuestDialogPage < 0) npcBunniesCore::npcQuestDialogPage = 0;
if(npcBunniesCore::npcQuestDialogPage >= groups) npcBunniesCore::npcQuestDialogPage = groups - 1;
}
void DrawNpcQuestDialog(jjCANVAS@ canvas, jjPLAYER@ play)
{
if(!npcBunniesCore::npcQuestDialogOpen) return;
int w = 315;
int textW = npcBunniesCore::npcQuestDialogShowInfo ? w - 20 - 62 : w - 20;
if(textW < 20) textW = w - 20;
int visibleHeight = npcBunniesCore::npcQuestDialogShowInfo ? 69 : 14;
EnsureNpcQuestDialogCache(play, npcBunniesCore::npcQuestDialogPage, npcBunniesCore::npcQuestDialogShowInfo, textW, visibleHeight);
if(cachedNpcQuestDialogGroups <= 0) npcBunniesCore::npcQuestDialogPage = 0;
else if(npcBunniesCore::npcQuestDialogPage >= cachedNpcQuestDialogGroups)
{
npcBunniesCore::npcQuestDialogPage = cachedNpcQuestDialogGroups - 1;
EnsureNpcQuestDialogCache(play, npcBunniesCore::npcQuestDialogPage, npcBunniesCore::npcQuestDialogShowInfo, textW, visibleHeight, true);
}
else if(npcBunniesCore::npcQuestDialogPage < 0)
{
npcBunniesCore::npcQuestDialogPage = 0;
EnsureNpcQuestDialogCache(play, npcBunniesCore::npcQuestDialogPage, npcBunniesCore::npcQuestDialogShowInfo, textW, visibleHeight, true);
}
int h = npcBunniesCore::npcQuestDialogShowInfo ? 180 : 58 + cachedNpcQuestDialogBasicLines * 18;
if(h < 110) h = 110;
EnsureNpcQuestDialogPosition(w, h);
npcBunniesCore::npcQuestDialogWindow.preferencesFile = "npcBunniesDialogueBox.asdat";
DbBegin(@npcBunniesCore::npcQuestDialogWindow, "npcQuestTracker", "Active quests", npcBunniesCore::npcQuestDialogX, npcBunniesCore::npcQuestDialogY, w, h);
npcBunniesCore::npcQuestDialogWindow.closeButton = true;
npcBunniesCore::npcQuestDialogWindow.draggable = true;
npcBunniesCore::npcQuestDialogWindow.titleBar = true;
npcBunniesCore::npcQuestDialogWindow.pad = 10;
DbDrawFrame(canvas, @npcBunniesCore::npcQuestDialogWindow);
int x = npcBunniesCore::npcQuestDialogWindow.x;
int y = npcBunniesCore::npcQuestDialogWindow.y;
npcBunniesCore::npcQuestDialogX = x;
npcBunniesCore::npcQuestDialogY = y;
int textX = x + 10;
int textY = y + 33;
int controlsY = y + h - 22;
if(npcBunniesCore::npcQuestDialogShowInfo)
{
int objectivesY = controlsY - 50;
int textMaxY = objectivesY - 6;
if(npcBunniesCore::npcQuestDialogScroll < 0) npcBunniesCore::npcQuestDialogScroll = 0;
if(npcBunniesCore::npcQuestDialogScroll > cachedNpcQuestDialogMaxScroll) npcBunniesCore::npcQuestDialogScroll = cachedNpcQuestDialogMaxScroll;
if(cachedNpcQuestDialogMaxScroll > 0)
{
if(npcBunniesCore::npcQuestDialogScroll > 0) DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, textX + w - 20 - 46, textY, 42, 14, npcBunniesCore::DB_NPC_QUEST_SCROLL_UP, 0, -1, "UP");
if(npcBunniesCore::npcQuestDialogScroll < cachedNpcQuestDialogMaxScroll) DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, textX + w - 20 - 58, textMaxY - 12, 54, 14, npcBunniesCore::DB_NPC_QUEST_SCROLL_DOWN, 0, 1, "DOWN");
}
DrawCachedNpcQuestDialogWrappedText(canvas, textX, textY, textMaxY, npcBunniesCore::npcQuestDialogScroll);
if(cachedNpcQuestDialogGroups > 0) DrawNpcQuestObjectives(canvas, play, textX, objectivesY, w - 20, 44, npcBunniesCore::npcQuestDialogPage);
}
else
{
npcBunniesCore::npcQuestDialogScroll = 0;
DrawCachedNpcQuestBasicLines(canvas, textX, textY);
}
if(NpcQuestHotkeyEnabled())
{
string keyText = npcBunniesCore::npcQuestDialogCaptureKey ? "Press a key" : "Key: " + npcBunniesCore::NpcQuestKeyName(npcBunniesCore::NPC_QUEST_DIALOG_KEY);
DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 10, controlsY, 90, 14, npcBunniesCore::DB_NPC_QUEST_KEY, 0, 0, keyText);
}
DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 112, controlsY - 1, 16, 14, npcBunniesCore::DB_NPC_QUEST_PREV, 0, -1, "<");
DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 136, controlsY - 1, 16, 14, npcBunniesCore::DB_NPC_QUEST_NEXT, 0, 1, ">");
DbDrawButton(canvas, @npcBunniesCore::npcQuestDialogWindow, x + 164, controlsY, 130, 14, npcBunniesCore::DB_NPC_QUEST_INFO, 0, 0, npcBunniesCore::npcQuestDialogShowInfo ? "Show info: ON" : "Show info: OFF");
DbDrawFocus(canvas, @npcBunniesCore::npcQuestDialogWindow);
}
bool loadKeyFrame = false;
int loadKey = 0;
int highlightChoice = 0;
int highlightChoiceTicks = 0;
string answerText = "";
int lastKey = -1;
array<string> npcMenuOptions = {"Exit", "Talk", "Follow", "Shop", "Guide", "Fighting", "Return"};
uint npcMenuOptionsLength = 4;
int chosenMenuOption = 0;
int chosenMenuTicksUp = 0;
int chosenMenuTicksDown = 0;
int GetNpcMenuOptionCount(npcBunniesCore::npcBunny npc)
{
int count = npcBunniesAnimations::IsNpcStaticType(npc.type) ? 2 : 3; // Talk, Follow, Shop; Exit is drawn separately
if(!npcBunniesAnimations::IsNpcStaticType(npc.type) && (npc.guideEnabled || npc.guidePaths.length > 0)) count++;
if(!npcBunniesAnimations::IsNpcStaticType(npc.type) && npc.fightingAvailable) count++;
if(!npcBunniesAnimations::IsNpcStaticType(npc.type)) count++;
return count;
}
int GetNpcMenuActionAt(npcBunniesCore::npcBunny npc, int index)
{
if(index == 0) return npcBunniesCore::NpcAction::Talk;
if(npcBunniesAnimations::IsNpcStaticType(npc.type))
{
if(index == 1) return npcBunniesCore::NpcAction::Shop;
return npcBunniesCore::NpcAction::Menu;
}
if(index == 1) return npcBunniesCore::NpcAction::Follow;
if(index == 2) return npcBunniesCore::NpcAction::Shop;
int current = 3;
if(npc.guideEnabled || npc.guidePaths.length > 0)
{
if(index == current) return npcBunniesCore::NpcAction::Guide;
current++;
}
if(npc.fightingAvailable)
{
if(index == current) return npcBunniesCore::NpcAction::Fighting;
current++;
}
if(index == current) return npcBunniesCore::NpcAction::Return;
return npcBunniesCore::NpcAction::Menu;
}
void ReturnNpcToOrigin(int npcId)
{
if(npcId < 0 || npcId >= int(npcBunniesCore::npcBunnies.length)) return;
if(npcBunniesCore::npcBunnies[npcId].followingTrainIndex >= 0 && npcBunniesCore::npcBunnies[npcId].playerFollowing >= 0)
npcBunniesMovement::recalculateFollowingTrainIndexes(npcBunniesCore::npcBunnies[npcId].followingTrainIndex, npcBunniesCore::npcBunnies[npcId].playerFollowing);
npcBunniesCore::npcBunnies[npcId].playerFollowing = -1;
npcBunniesCore::npcBunnies[npcId].followingTrainIndex = -1;
npcBunniesCore::npcBunnies[npcId].guideActive = false;
npcBunniesCore::npcBunnies[npcId].guideActivePathIndex = -1;
npcBunniesCore::npcBunnies[npcId].guidePendingAction = 0;
npcBunniesCore::npcBunnies[npcId].guidePendingPathIndex = -1;
npcBunniesCore::npcBunnies[npcId].fightingTargetObjectID = -1;
npcBunniesCore::npcBunnies[npcId].tpTargetX = npcBunniesCore::npcBunnies[npcId].xTile * 32;
npcBunniesCore::npcBunnies[npcId].tpTargetY = npcBunniesCore::npcBunnies[npcId].yTile * 32;
npcBunniesCore::npcBunnies[npcId].tpAnim = 0;
npcBunniesHooks::sendNpcSync(npcId, -1, -1, 0);
}
void RequestNpcReturnToOrigin(int npcId)
{
if(jjIsServer || !npcBunniesCore::ENABLE_NPC_SYNC)
{
ReturnNpcToOrigin(npcId);
return;
}
jjSTREAM npcPacket;
npcPacket.push("npcReturn " + npcId);
jjSendPacket(npcPacket);
}
string GetNpcMenuLabel(npcBunniesCore::npcBunny npc, jjPLAYER@ play, int index)
{
int action = GetNpcMenuActionAt(npc, index);
if(action == npcBunniesCore::NpcAction::Follow && play !is null && npc.playerFollowing == play.playerID) return "Stop Following";
if(action == npcBunniesCore::NpcAction::Fighting) return npc.fightingEnabled ? "Fighting: ON" : "Fighting: OFF";
if(action >= 0 && action < int(npcMenuOptions.length)) return npcMenuOptions[action];
return npcMenuOptions[0];
}
void getMenuOptionControl(jjPLAYER@ play)
{
npcBunniesCore::npcBunny npc = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn];
npcMenuOptionsLength = GetNpcMenuOptionCount(npc);
if(chosenMenuOption > int(npcMenuOptionsLength)) chosenMenuOption = int(npcMenuOptionsLength);
if(chosenMenuOption < 0) chosenMenuOption = 0;
if(play.keyUp && chosenMenuTicksUp + 30 < jjGameTicks)
{
chosenMenuOption--;
if(chosenMenuOption < 0) chosenMenuOption = int(npcMenuOptionsLength);
chosenMenuTicksUp = jjGameTicks;
}
else if(play.keyDown && chosenMenuTicksDown + 30 < jjGameTicks)
{
chosenMenuOption++;
if(chosenMenuOption > int(npcMenuOptionsLength)) chosenMenuOption = 0;
chosenMenuTicksDown = jjGameTicks;
}
else if(jjKey[0x0D] && npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].action == npcBunniesCore::NpcAction::Menu) //ENTER
{
int selectedAction = chosenMenuOption == int(npcMenuOptionsLength) ? npcBunniesCore::NpcAction::Menu : GetNpcMenuActionAt(npc, chosenMenuOption);
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].action = selectedAction;
npcBunniesMovement::lastKeyFire = jjGameTicks;
switch(selectedAction)
{
case npcBunniesCore::NpcAction::Menu:
npcBunniesHooks::exitConv();
break;
case npcBunniesCore::NpcAction::Talk:
if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].dialogueSets.length > 0)
{
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].convIndex = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].getDialogueSetNumber();
npcBunniesCore::RouteActiveQuestBeforeDialogue(npcBunniesMovement::convOn, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerTalking);
}
else
{
npcBunniesHooks::exitConv();
}
break;
case npcBunniesCore::NpcAction::Follow:
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideActive = false;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideActivePathIndex = -1;
if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing == -1) //Follow
{
int nextFollowingTrainIndex = npcBunniesMovement::getFollowingTrainIndex(play.playerID);
if(npcBunniesCore::NPC_FOLLOW_LIMIT == -1 || nextFollowingTrainIndex <= npcBunniesCore::NPC_FOLLOW_LIMIT)
{
if(jjIsServer || !npcBunniesCore::ENABLE_NPC_SYNC)
{
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex = nextFollowingTrainIndex;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing = play.playerID;
npcBunniesHooks::sendNpcSync(npcBunniesMovement::convOn, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex, 0);
}
else
{
npcBunniesHooks::sendNpcSync(npcBunniesMovement::convOn, play.playerID, nextFollowingTrainIndex, 0);
}
}
}
else if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing == play.playerID) //Disconnect
{
if(jjIsServer || !npcBunniesCore::ENABLE_NPC_SYNC)
{
npcBunniesMovement::recalculateFollowingTrainIndexes(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing);
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing = -1;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex = -1;
npcBunniesHooks::sendNpcSync(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].id, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].playerFollowing, npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].followingTrainIndex, 0);
}
else
{
npcBunniesHooks::sendNpcSync(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].id, -1, -1, 0);
}
}
npcBunniesHooks::exitConv();
break;
case npcBunniesCore::NpcAction::Shop:
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopSelectedIndex = 0;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopConfirmOpen = false;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopConfirmYes = true;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopMessage = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].shopItems.length == 0 ? "This NPC sells no items." : "";
npcBunniesShop::shopBuyTicks = jjGameTicks;
npcBunniesShop::shopInputReleased = false;
npcBunniesMovement::keyTicks[13] = jjGameTicks;
break;
case npcBunniesCore::NpcAction::Fighting:
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].fightingEnabled = !npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].fightingEnabled;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].fightingTargetObjectID = -1;
npcBunniesFighting::MarkNpcFightingStateDirty();
npcBunniesFighting::SendNpcFightingSync(npcBunniesMovement::convOn, 0);
npcBunniesHooks::exitConv();
break;
case npcBunniesCore::NpcAction::Return:
RequestNpcReturnToOrigin(npcBunniesMovement::convOn);
npcBunniesHooks::exitConv();
break;
case npcBunniesCore::NpcAction::Guide:
{
if(!npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideEnabled || npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePaths.length == 0)
{
npcBunniesHooks::exitConv();
break;
}
string guidePlayerName = jjPlayers[play.playerID].nameUnformatted;
int availableGuidePaths = npcBunniesQuests::GetAvailableGuidePathCountForPlayer(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn], guidePlayerName);
if(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideNextPathIndex >= availableGuidePaths)
{
if(availableGuidePaths >= int(npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePaths.length) && !npcBunniesCore::HasPlayerSeenGuideEndLine(guidePlayerName, npcBunniesMovement::convOn) && npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideEndLine != "")
{
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingLine = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideEndLine;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingAction = 2;
}
else
{
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingLine = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideAlreadyAtDestinationLine;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingAction = 3;
}
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingPathIndex = -1;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePlayerId = play.playerID;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].dialogueIndex = 0;
break;
}
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingPathIndex = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guideNextPathIndex;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePlayerId = play.playerID;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingAction = 1;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingLine = npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePaths[npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].guidePendingPathIndex].line;
npcBunniesCore::npcBunnies[npcBunniesMovement::convOn].dialogueIndex = 0;
break;
}
}
}
}
class DialogueSet
{
array<DialogueLine@> dialogue;
array<Choice@> choices;
array<npcBunniesQuests::NpcQuest@> quests;
array<npcBunniesQuests::NpcReward@> rewards;
int choicesAnswerMethod = 0;
int goToSet = -1;
int autoContinueSet = -2;
int questInProgressSet = -1;
int questCompletedSet = -1;
string questInProgressLine = "";
string questCompletedLine = "";
bool respawnMonsters = false;
bool conditionsExist = false;
bool rewardsGranted = false;
int goToSetAfterChoice = -1;
int bigFrame = -1;
int speakerNpcIndex = -1;
DialogueSet(array<DialogueLine@> dialogue, array<Choice@> choices = null, int choicesAnswerMethod = 0)
{
this.dialogue = dialogue;
this.choices = choices;
this.choicesAnswerMethod = choicesAnswerMethod;
}
}
class DialogueLine
{
int personId = 0;
int isNpc = 0; //0 = NPC, 1 = Player
string text;
DialogueLine(int personId = 0, int isNpc = 0, string text = "")
{
this.personId = personId;
this.isNpc = isNpc;
this.text = text;
}
}
class Choice
{
string choiceText;
int goToSet = -1;
Choice(string choiceText, int goToSet = -1)
{
this.choiceText = choiceText;
this.goToSet = goToSet;
}
}
}
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.