| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Characters | Cranky | Mutator | N/A | |||||
namespace XmasHats
{
bool LOAD_LEDGES_J2A = false;
const bool DEBUG_YOSHI_ON = false;
bool EDITING_MODE = false;
bool SHOW_HAT_INSTRUCTIONS = true;
array<int> hats(32,0);
const string Filename2 = "hatSave.asdat";
const int FRAMES_PER_CHARACTER = 858;
const int MAX_ANIM_SETS = 256;
const int ANIMS_PER_SET = 90;
const int HANG_LEDGE_FRAME_INDEX_START = FRAMES_PER_CHARACTER - 10;
int HANG_LEDGE_RUNTIME_ACCESSORY_Y_OFFSET_JAZZ = 10;
int HANG_LEDGE_RUNTIME_ACCESSORY_Y_OFFSET_SPAZ = 6;
int DEVAN_RUNTIME_ACCESSORY_X_OFFSET = 10;
int DEVAN_RUNTIME_ACCESSORY_Y_OFFSET = -16;
// Normal Yoshi only: hat X correction for running animation 56.
// Change -5 here later if you need to adjust it.
const int NORMAL_YOSHI_RUNNING_56_HAT_X_OFFSET = -5;
const int ACCESSORY_COLOR_GROUP_RED = 0;
const int ACCESSORY_COLOR_GROUP_YELLOW = 1;
const int ACCESSORY_COLOR_GROUP_BLACK = 2;
const int ACCESSORY_COLOR_GROUP_BLUE = 3;
const int ACCESSORY_COLOR_GROUP_GREEN = 4;
const int ACCESSORY_COLOR_GROUP_COUNT = 5;
array<int> HAT_COLOR_OFFSET_RED_COLORS = {24, 25, 26, 27, 28, 29, 30, 31};
array<int> HAT_COLOR_OFFSET_YELLOW_COLORS = {40, 41, 42, 43, 44, 45, 46, 47};
array<int> HAT_COLOR_OFFSET_BLACK_COLORS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 23};
array<int> HAT_COLOR_OFFSET_BLUE_COLORS = {32, 33, 34, 35, 36, 37, 38, 39};
array<int> HAT_COLOR_OFFSET_GREEN_COLORS = {16, 17, 18, 19, 20, 21, 22};
int HAT_COLOR_OFFSET_BLACK_PALSHIFT_BASE = 24;
const bool USE_PRESET_COORDINATES_HAT = true;
const bool USE_PRESET_COORDINATES_SCARFS = false;
const bool USE_PRESET_COORDINATES_GLASSES = false;
int characterSlotCount = 32;
const float accessoryBaseMultiplier = 1;
enum AccessoryCategory
{
HATS = 0,
SCARFS = 1,
GLASSES = 2
}
array<string> AccessoryCoordinateFilenames = {"xmasHats.asdat", "xmasScarfs.asdat", "xmasGlasses.asdat"};
array<string> AccessoryCategoryNames = {"Hats", "Scarfs", "Glasses"};
int animation = ANIM::JAZZ;
int character = CHAR::JAZZ;
array<int> adjustX(0, 0);
array<int> adjustY(0, 0);
array<int> rotation(0, 0);
array<int> using(0, 0);
array<float> scaleX(0, 0);
array<float> scaleY(0, 0);
array<int> edited(0, 0);
array<int> categoryAdjustX(0, 0);
array<int> categoryAdjustY(0, 0);
array<int> categoryRotation(0, 0);
array<int> categoryUsing(0, 0);
array<float> categoryScaleX(0, 0);
array<float> categoryScaleY(0, 0);
array<int> categoryEdited(0, 0);
array<int> accessoryFrames(96, 0);
array<int> accessoryColorOffsets(96 * ACCESSORY_COLOR_GROUP_COUNT, 0);
array<bool> noAccessory(96, false);
array<bool> nohat(32, false);
bool transmitServerHats = false;
bool hatAnimLoaded = false;
bool accessoryColorMaskAnimLoaded = false;
bool ledgesJazzLoaded = false;
bool ledgesSpazLoaded = false;
bool ledgesSingleSetMode = false;
uint empty = 0;
uint accessoryColorMaskSet = 0;
uint ledgesJazzSet = 0;
uint ledgesSpazSet = 0;
uint nr = 0;
int hatFrameCount = 1;
int editCategory = AccessoryCategory::HATS;
bool currentCategoryLoaded = false;
array<int> accessoryColorMaskFrameStart(3, 0);
int accessoryColorMaskFramesPerGroup = 0;
int characterJ2AIndex(int charSlot)
{
if(charSlot == 0) return 1;
if(charSlot == 7) return 2;
if(charSlot == 8) return 3;
if(charSlot == 9) return 4;
if(charSlot == 10) return 5;
if(charSlot == 11) return 6;
if(charSlot == 12) return 7;
if(charSlot == 13) return 8;
return -1;
}
int positionArrayLength()
{
return FRAMES_PER_CHARACTER * characterSlotCount + 1;
}
void resizePositionArrays(int newCharacterSlotCount)
{
if(newCharacterSlotCount < 5) newCharacterSlotCount = 5;
characterSlotCount = newCharacterSlotCount;
int length = positionArrayLength();
uint oldLength = scaleX.length;
uint oldCategoryLength = categoryScaleX.length;
adjustX.resize(length);
adjustY.resize(length);
rotation.resize(length);
using.resize(length);
scaleX.resize(length);
scaleY.resize(length);
edited.resize(length);
int categoryLength = int(AccessoryCategoryNames.length) * length;
categoryAdjustX.resize(categoryLength);
categoryAdjustY.resize(categoryLength);
categoryRotation.resize(categoryLength);
categoryUsing.resize(categoryLength);
categoryScaleX.resize(categoryLength);
categoryScaleY.resize(categoryLength);
categoryEdited.resize(categoryLength);
for(uint i = oldLength; i < scaleX.length; i++){
scaleX[i] = 1;
scaleY[i] = 1;
}
for(uint i = oldCategoryLength; i < categoryScaleX.length; i++){
categoryScaleX[i] = 1;
categoryScaleY[i] = 1;
}
}
void onLevelLoad(){
resizePositionArrays(characterSlotCount);
//find an unused slot in ANIM::CUSTOM
while (jjAnimSets[ANIM::CUSTOM[empty]].firstAnim != 0)
++empty;
jjANIMSET@ anim = jjAnimSets[ANIM::CUSTOM[empty]];
anim.load(0, "XmasHats.j2a");
hatAnimLoaded = true;
hatFrameCount = accessoryFrameCount(AccessoryCategory::HATS);
loadLedgesEditorAnimations();
buildRabbit123PackedFrameCache();
for(int i=0; i<78; i++){
nr += jjAnimations[jjAnimSets[animation].firstAnim + anim_id].frameCount;
}
loadHatPreferences();
readAllCategoryFiles();
}
uint nextFreeCustomAnimSet(uint start)
{
while(jjAnimSets[ANIM::CUSTOM[start]].firstAnim != 0)
start++;
return start;
}
void loadLedgesEditorAnimations()
{
ledgesJazzLoaded = false;
ledgesSpazLoaded = false;
ledgesSingleSetMode = false;
if(!LOAD_LEDGES_J2A) return;
ledgesJazzSet = nextFreeCustomAnimSet(accessoryColorMaskAnimLoaded ? accessoryColorMaskSet + 1 : empty + 1);
jjAnimSets[ANIM::CUSTOM[ledgesJazzSet]].load(0, "Ledges.j2a");
ledgesJazzLoaded = jjAnimSets[ANIM::CUSTOM[ledgesJazzSet]].firstAnim != 0;
ledgesSpazSet = nextFreeCustomAnimSet(ledgesJazzSet + 1);
jjAnimSets[ANIM::CUSTOM[ledgesSpazSet]].load(1, "Ledges.j2a");
ledgesSpazLoaded = jjAnimSets[ANIM::CUSTOM[ledgesSpazSet]].firstAnim != 0;
if(ledgesJazzLoaded && !ledgesSpazLoaded)
{
int firstAnim = jjAnimSets[ANIM::CUSTOM[ledgesJazzSet]].firstAnim;
ledgesSingleSetMode = jjAnimations[firstAnim + 1].frameCount > 0;
}
}
void sendHatState(int playerID)
{
if(playerID < 0 || playerID >= 32) return;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
int index = accessoryPlayerIndex(category, playerID);
jjSTREAM mySTREAM;
mySTREAM.push("!accessory " + category + " " + accessoryFrames[index] + " " + (noAccessory[index] ? 1 : 0) + " " + playerID);
jjSendPacket(mySTREAM);
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++){
mySTREAM.clear();
mySTREAM.push("!accessoryColor " + category + " " + colorGroup + " " + getAccessoryColorOffset(category, playerID, colorGroup) + " " + playerID);
jjSendPacket(mySTREAM);
}
if(jjIsServer){
mySTREAM.clear();
mySTREAM.push("!accessories " + category + " " + accessoryFrames[index] + " " + (noAccessory[index] ? 1 : 0) + " " + playerID);
jjSendPacket(mySTREAM);
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++){
mySTREAM.clear();
mySTREAM.push("!accessoryColors " + category + " " + colorGroup + " " + getAccessoryColorOffset(category, playerID, colorGroup) + " " + playerID);
jjSendPacket(mySTREAM);
}
}
}
}
void sendHatStateToClient(int subjectID, int toClientID)
{
if(subjectID < 0 || subjectID >= 32 || toClientID <= 0) return;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
int index = accessoryPlayerIndex(category, subjectID);
jjSTREAM packet;
packet.push("!accessories " + category + " " + accessoryFrames[index] + " " + (noAccessory[index] ? 1 : 0) + " " + subjectID);
jjSendPacket(packet, toClientID);
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++){
packet.clear();
packet.push("!accessoryColors " + category + " " + colorGroup + " " + getAccessoryColorOffset(category, subjectID, colorGroup) + " " + subjectID);
jjSendPacket(packet, toClientID);
}
}
}
void transmitAllHatsToClient(int toClientID)
{
if(toClientID <= 0) return;
for(int subjectID = 0; subjectID < 32; subjectID++){
if(jjPlayers[subjectID].isActive)
sendHatStateToClient(subjectID, toClientID);
}
}
void transmitServerHatsRequest()
{
if(transmitServerHats || jjIsServer) return;
jjSTREAM myStream;
myStream.push("!allHats");
jjSendPacket(myStream);
transmitServerHats = true;
}
void saveHatPreferences()
{
jjSTREAM saveFile(Filename2);
saveFile.clear();
for(int i = 0; i < jjLocalPlayerCount; i++){
int playerID = jjLocalPlayers[i].playerID;
saveFile.write((i + 1) + " " + hats[playerID] + " " + (nohat[playerID] ? 1 : 0));
for(int category = 1; category < int(AccessoryCategoryNames.length); category++){
int index = accessoryPlayerIndex(category, playerID);
saveFile.write(" " + accessoryFrames[index] + " " + (noAccessory[index] ? 1 : 0));
}
for(int category = 0; category < int(AccessoryCategoryNames.length); category++)
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++)
saveFile.write(" " + getAccessoryColorOffset(category, playerID, colorGroup));
saveFile.write("\n");
}
saveFile.save(Filename2);
}
void loadHatPreferences()
{
for(int i = 0; i < jjLocalPlayerCount; i++){
int playerID = jjLocalPlayers[i].playerID;
hats[playerID] = 0;
nohat[playerID] = false;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
int index = accessoryPlayerIndex(category, playerID);
accessoryFrames[index] = 0;
noAccessory[index] = category == AccessoryCategory::HATS ? false : true;
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++)
setAccessoryColorOffset(category, playerID, colorGroup, 0);
}
}
jjSTREAM loadFile(Filename2);
if(!loadFile.isEmpty()){
bool readLegacy = false;
while(!loadFile.isEmpty()){
string line = "";
loadFile.getLine(line, '\n');
array<string> string_parts = line.split(" ");
if(string_parts.length >= 3 && string_parts[2] != ""){
int localIndex = parseInt(string_parts[0]) - 1;
if(localIndex >= 0 && localIndex < jjLocalPlayerCount){
int playerID = jjLocalPlayers[localIndex].playerID;
setAccessoryFrame(AccessoryCategory::HATS, playerID, parseInt(string_parts[1]));
if(parseInt(string_parts[2]) == 1) removeAccessory(AccessoryCategory::HATS, playerID);
for(int category = 1; category < int(AccessoryCategoryNames.length); category++){
int partIndex = 3 + (category - 1) * 2;
if(int(string_parts.length) > partIndex + 1){
setAccessoryFrame(category, playerID, parseInt(string_parts[partIndex]));
if(parseInt(string_parts[partIndex + 1]) == 1) removeAccessory(category, playerID);
}
}
int colorStartIndex = 7;
int expandedColorCount = int(AccessoryCategoryNames.length) * ACCESSORY_COLOR_GROUP_COUNT;
if(int(string_parts.length) >= colorStartIndex + expandedColorCount)
{
for(int category = 0; category < int(AccessoryCategoryNames.length); category++)
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++){
int partIndex = colorStartIndex + category * ACCESSORY_COLOR_GROUP_COUNT + colorGroup;
setAccessoryColorOffset(category, playerID, colorGroup, parseInt(string_parts[partIndex]));
}
}
else
{
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
int partIndex = colorStartIndex + category;
if(int(string_parts.length) > partIndex)
setAccessoryColorOffset(category, playerID, ACCESSORY_COLOR_GROUP_RED, parseInt(string_parts[partIndex]));
}
}
}
}else if(!readLegacy && string_parts.length >= 1 && jjLocalPlayerCount > 0){
int playerID = jjLocalPlayers[0].playerID;
setAccessoryFrame(AccessoryCategory::HATS, playerID, parseInt(string_parts[0]));
if(string_parts.length > 1 && parseInt(string_parts[1]) == 1) removeAccessory(AccessoryCategory::HATS, playerID);
readLegacy = true;
}
}
}
if(jjLocalPlayerCount > 0)
use = hats[jjLocalPlayers[0].playerID];
for(int i = 0; i < jjLocalPlayerCount; i++)
sendHatState(jjLocalPlayers[i].playerID);
transmitServerHatsRequest();
}
int chosen_frame = 0, adjX = 0, adjY = 0, rot = 0, anim_id = 0;
int additionalAccessory = -1;
float adjScaleX = 1, adjScaleY = 1;
uint frame_id = 0, use = 0;
bool pressedUp = false, pressedDown = false, pressed = false, pressedJump = false, pressedKey = false;
bool hatMouseMode = false, hatMouseWasDown = false, hatEditFresh = false;
int hatEditField = -1, hatEditLastKey = -1;
string hatEditValue = "";
uint choice = 0;
DbWindow hatEditorWindow;
DbResult hatEditorResult;
const int HAT_ACTION_CATEGORY = 1000;
const int HAT_ACTION_INCREMENT = 1001;
const int HAT_ACTION_ANIM = 1002;
const int HAT_ACTION_FRAME = 1003;
const int HAT_ACTION_CHAR = 1004;
const int HAT_ACTION_POS_X = 1005;
const int HAT_ACTION_POS_Y = 1006;
const int HAT_ACTION_ROTATION = 1007;
const int HAT_ACTION_SCALE_X = 1008;
const int HAT_ACTION_SCALE_Y = 1009;
const int HAT_ACTION_MOUSE = 1010;
const int HAT_ACTION_EDIT_FIELD = 1011;
const int HAT_ACTION_ADDITIONAL_HAT = 1012;
const int HAT_ACTION_ROT_INCREMENT = 1013;
int selectedCharacterSlot = 1;
string currentCoordinateFilename()
{
if(editCategory < 0 || editCategory >= int(AccessoryCoordinateFilenames.length))
return AccessoryCoordinateFilenames[AccessoryCategory::HATS];
return AccessoryCoordinateFilenames[editCategory];
}
string currentCategoryName()
{
if(editCategory < 0 || editCategory >= int(AccessoryCategoryNames.length))
return AccessoryCategoryNames[AccessoryCategory::HATS];
return AccessoryCategoryNames[editCategory];
}
int accessoryFrameCount(int category)
{
if(!hatAnimLoaded || category < 0 || category >= int(AccessoryCategoryNames.length))
return 1;
int firstAnim = jjAnimSets[ANIM::CUSTOM[empty]].firstAnim;
int count = jjAnimations[firstAnim + category].frameCount;
return count > 0 ? count : 1;
}
bool accessoryColorListContains(uint8 color, const array<int> &in colors)
{
for(uint i = 0; i < colors.length; i++)
if(int(color) == colors[i])
return true;
return false;
}
int accessoryColorListIndex(uint8 color, const array<int> &in colors)
{
for(uint i = 0; i < colors.length; i++)
if(int(color) == colors[i])
return int(i);
return -1;
}
bool isAccessoryColorGroupPixel(uint8 color, int colorGroup)
{
if(color == 0) return false;
if(colorGroup == ACCESSORY_COLOR_GROUP_RED) return accessoryColorListContains(color, HAT_COLOR_OFFSET_RED_COLORS);
if(colorGroup == ACCESSORY_COLOR_GROUP_YELLOW) return accessoryColorListContains(color, HAT_COLOR_OFFSET_YELLOW_COLORS);
if(colorGroup == ACCESSORY_COLOR_GROUP_BLACK) return accessoryColorListContains(color, HAT_COLOR_OFFSET_BLACK_COLORS);
if(colorGroup == ACCESSORY_COLOR_GROUP_BLUE) return accessoryColorListContains(color, HAT_COLOR_OFFSET_BLUE_COLORS);
if(colorGroup == ACCESSORY_COLOR_GROUP_GREEN) return accessoryColorListContains(color, HAT_COLOR_OFFSET_GREEN_COLORS);
return false;
}
uint8 accessoryPalshiftMaskColor(uint8 color, int colorGroup)
{
if(colorGroup != ACCESSORY_COLOR_GROUP_BLACK)
return color;
int index = accessoryColorListIndex(color, HAT_COLOR_OFFSET_BLACK_COLORS);
if(index < 0)
return color;
int count = int(HAT_COLOR_OFFSET_BLACK_COLORS.length);
int shade = count <= 1 ? 0 : (index * 6) / (count - 1);
int mapped = HAT_COLOR_OFFSET_BLACK_PALSHIFT_BASE + shade;
if(mapped < 1) mapped = 1;
if(mapped > 255) mapped = 255;
return uint8(mapped);
}
void copyAccessoryFrameMetadata(int sourceFrameID, int targetFrameID)
{
jjANIMFRAME@ sourceFrame = jjAnimFrames[sourceFrameID];
jjANIMFRAME@ targetFrame = jjAnimFrames[targetFrameID];
targetFrame.hotSpotX = sourceFrame.hotSpotX;
targetFrame.hotSpotY = sourceFrame.hotSpotY;
targetFrame.gunSpotX = sourceFrame.gunSpotX;
targetFrame.gunSpotY = sourceFrame.gunSpotY;
targetFrame.coldSpotX = sourceFrame.coldSpotX;
targetFrame.coldSpotY = sourceFrame.coldSpotY;
targetFrame.transparent = sourceFrame.transparent;
}
void buildAccessoryColorMaskAnimation()
{
accessoryColorMaskAnimLoaded = false;
accessoryColorMaskFramesPerGroup = 0;
if(!hatAnimLoaded) return;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
accessoryColorMaskFrameStart[category] = accessoryColorMaskFramesPerGroup;
accessoryColorMaskFramesPerGroup += accessoryFrameCount(category);
}
if(accessoryColorMaskFramesPerGroup <= 0) return;
accessoryColorMaskSet = nextFreeCustomAnimSet(empty + 1);
array<uint> animSizes(1);
animSizes[0] = uint(accessoryColorMaskFramesPerGroup * ACCESSORY_COLOR_GROUP_COUNT);
jjAnimSets[ANIM::CUSTOM[accessoryColorMaskSet]].allocate(animSizes);
int sourceFirstAnim = jjAnimSets[ANIM::CUSTOM[empty]].firstAnim;
int maskFirstFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[accessoryColorMaskSet]].firstAnim].firstFrame;
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++){
int groupFrameOffset = colorGroup * accessoryColorMaskFramesPerGroup;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
int frameCount = accessoryFrameCount(category);
int sourceFirstFrame = jjAnimations[sourceFirstAnim + category].firstFrame;
for(int frame = 0; frame < frameCount; frame++){
int sourceFrameID = sourceFirstFrame + frame;
int maskFrameID = maskFirstFrame + groupFrameOffset + accessoryColorMaskFrameStart[category] + frame;
jjPIXELMAP image(jjAnimFrames[sourceFrameID]);
for(int x = image.width - 1; x >= 0; x--)
for(int y = image.height - 1; y >= 0; y--)
if(isAccessoryColorGroupPixel(image[x,y], colorGroup))
image[x,y] = accessoryPalshiftMaskColor(image[x,y], colorGroup);
else
image[x,y] = 0;
image.save(jjAnimFrames[maskFrameID]);
copyAccessoryFrameMetadata(sourceFrameID, maskFrameID);
}
}
}
accessoryColorMaskAnimLoaded = true;
}
void ensureAccessoryColorMaskAnimation()
{
if(!accessoryColorMaskAnimLoaded)
buildAccessoryColorMaskAnimation();
}
int accessoryColorMaskFrame(int colorGroup, int category, int frame)
{
if(!accessoryColorMaskAnimLoaded
|| colorGroup < 0 || colorGroup >= ACCESSORY_COLOR_GROUP_COUNT
|| category < 0 || category >= int(AccessoryCategoryNames.length))
return -1;
int count = accessoryFrameCount(category);
if(count <= 0 || accessoryColorMaskFramesPerGroup <= 0) return -1;
frame %= count;
if(frame < 0) frame += count;
return colorGroup * accessoryColorMaskFramesPerGroup + accessoryColorMaskFrameStart[category] + frame;
}
void resetPositionArrays()
{
for(uint i = 0; i < adjustX.length; i++){
adjustX[i] = 0;
adjustY[i] = 0;
rotation[i] = 0;
using[i] = 0;
scaleX[i] = 1;
scaleY[i] = 1;
edited[i] = 0;
}
adjX = 0;
adjY = 0;
rot = 0;
adjScaleX = 1;
adjScaleY = 1;
chosen_frame = getPositionIndex(coordinateSlotForEditor(selectedCharacterSlot), 0);
}
void resetCategoryPositionArrays(int category)
{
for(int i = 0; i < positionArrayLength(); i++){
int index = category * positionArrayLength() + i;
categoryAdjustX[index] = 0;
categoryAdjustY[index] = 0;
categoryRotation[index] = 0;
categoryUsing[index] = 0;
categoryScaleX[index] = 1;
categoryScaleY[index] = 1;
categoryEdited[index] = 0;
}
}
void copyCategoryToCurrent(int category)
{
for(int i = 0; i < positionArrayLength(); i++){
int index = category * positionArrayLength() + i;
adjustX[i] = categoryAdjustX[index];
adjustY[i] = categoryAdjustY[index];
rotation[i] = categoryRotation[index];
using[i] = categoryUsing[index];
scaleX[i] = categoryScaleX[index];
scaleY[i] = categoryScaleY[index];
edited[i] = categoryEdited[index];
}
}
void copyCurrentToCategory(int category)
{
for(int i = 0; i < positionArrayLength(); i++){
int index = category * positionArrayLength() + i;
categoryAdjustX[index] = adjustX[i];
categoryAdjustY[index] = adjustY[i];
categoryRotation[index] = rotation[i];
categoryUsing[index] = using[i];
categoryScaleX[index] = scaleX[i];
categoryScaleY[index] = scaleY[i];
categoryEdited[index] = edited[i];
}
}
void updateCurrentPositionFromFirstEdited(bool makeCurrent)
{
if(!makeCurrent) return;
int firstIndex = getPositionIndex(1, 0);
if(edited[firstIndex] == 1){
chosen_frame = firstIndex;
adjX = adjustX[firstIndex];
adjY = adjustY[firstIndex];
rot = rotation[firstIndex];
adjScaleX = scaleX[firstIndex];
adjScaleY = scaleY[firstIndex];
}
}
int getPositionIndex(int characterSlot, int frameIndex)
{
if(characterSlot < 0) characterSlot = 0;
if(characterSlot >= characterSlotCount) characterSlot = characterSlotCount - 1;
if(frameIndex < 0) frameIndex = 0;
if(frameIndex >= FRAMES_PER_CHARACTER) frameIndex = FRAMES_PER_CHARACTER - 1;
return characterSlot * FRAMES_PER_CHARACTER + frameIndex;
}
int coordinateSlotForEditor(int charSlot)
{
return charSlot == 0 ? CharsNew::CharAnim::JAZZ : charSlot;
}
int getCategoryPositionIndex(int category, int characterSlot, int frameIndex)
{
if(category < 0) category = 0;
if(category >= int(AccessoryCategoryNames.length)) category = int(AccessoryCategoryNames.length) - 1;
return category * positionArrayLength() + getPositionIndex(characterSlot, frameIndex);
}
int accessoryPlayerIndex(int category, int playerID)
{
if(category < 0) category = 0;
if(category >= int(AccessoryCategoryNames.length)) category = int(AccessoryCategoryNames.length) - 1;
if(playerID < 0) playerID = 0;
if(playerID >= 32) playerID = 31;
return category * 32 + playerID;
}
int accessoryColorPlayerIndex(int category, int playerID, int colorGroup)
{
if(colorGroup < 0) colorGroup = ACCESSORY_COLOR_GROUP_RED;
if(colorGroup >= ACCESSORY_COLOR_GROUP_COUNT) colorGroup = ACCESSORY_COLOR_GROUP_COUNT - 1;
return colorGroup * 96 + accessoryPlayerIndex(category, playerID);
}
int getAccessoryColorOffset(int category, int playerID, int colorGroup)
{
return accessoryColorOffsets[accessoryColorPlayerIndex(category, playerID, colorGroup)];
}
bool hasAccessoryColorOffset(int category, int playerID)
{
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++)
if(getAccessoryColorOffset(category, playerID, colorGroup) != 0)
return true;
return false;
}
void setAccessoryFrame(int category, int playerID, int frame)
{
int count = accessoryFrameCount(category);
if(count <= 0) count = 1;
int index = accessoryPlayerIndex(category, playerID);
accessoryFrames[index] = frame % count;
if(accessoryFrames[index] < 0) accessoryFrames[index] += count;
noAccessory[index] = false;
if(category == AccessoryCategory::HATS)
{
hats[playerID] = accessoryFrames[index];
nohat[playerID] = false;
}
}
void setAccessoryColorOffset(int category, int playerID, int colorGroup, int offset)
{
accessoryColorOffsets[accessoryColorPlayerIndex(category, playerID, colorGroup)] = offset;
if(offset != 0)
ensureAccessoryColorMaskAnimation();
}
void setAccessoryColorOffset(int category, int playerID, int offset)
{
setAccessoryColorOffset(category, playerID, ACCESSORY_COLOR_GROUP_RED, offset);
}
void removeAccessory(int category, int playerID)
{
int index = accessoryPlayerIndex(category, playerID);
noAccessory[index] = true;
if(category == AccessoryCategory::HATS)
nohat[playerID] = true;
}
bool isAccessoryHidden(int category, int playerID)
{
if(category == AccessoryCategory::HATS)
return nohat[playerID] || noAccessory[accessoryPlayerIndex(category, playerID)];
return noAccessory[accessoryPlayerIndex(category, playerID)];
}
void debugHatFrameColors(int playerID)
{
if(playerID < 0 || playerID >= 32) return;
if(!hatAnimLoaded)
{
jjAlert("Hat color debug: XmasHats.j2a is not loaded.", false);
return;
}
int index = accessoryPlayerIndex(AccessoryCategory::HATS, playerID);
int frame = accessoryFrames[index] % accessoryFrameCount(AccessoryCategory::HATS);
if(frame < 0) frame += accessoryFrameCount(AccessoryCategory::HATS);
int firstAnim = jjAnimSets[ANIM::CUSTOM[empty]].firstAnim;
int firstFrame = jjAnimations[firstAnim + AccessoryCategory::HATS].firstFrame;
int frameID = firstFrame + frame;
jjPIXELMAP image(jjAnimFrames[frameID]);
array<bool> seen(256, false);
for(int x = image.width - 1; x >= 0; x--)
for(int y = image.height - 1; y >= 0; y--)
seen[image[x,y]] = true;
jjAlert("Hat color debug: player " + playerID + ", hat frame " + frame + (isAccessoryHidden(AccessoryCategory::HATS, playerID) ? " (hidden)" : ""), false);
string line = "Colors:";
int count = 0;
for(int color = 0; color < 256; color++)
{
if(!seen[color]) continue;
string next = (count == 0 ? " " : ", ") + color;
if(jjGetStringWidth(line + next, STRING::SMALL, STRING::NORMAL) > jjResolutionWidth - 32)
{
jjAlert(line, false);
line = "Colors:";
next = " " + color;
}
line += next;
count++;
}
if(count == 0)
jjAlert("Colors: none", false);
else
jjAlert(line, false);
}
bool hasVisibleAccessory(int playerID)
{
if(playerID < 0 || playerID >= 32) return false;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++)
if(!isAccessoryHidden(category, playerID))
return true;
return false;
}
int legacyCharacterSlotFromBlock(int block)
{
if(block == 0) return 1; // Jazz
if(block == 1) return 2; // Spaz
if(block == 2) return 4; // Frog
if(block == 3) return 3; // Lori
return block;
}
bool usePresetCoordinatesForCategory(int category)
{
if(category == AccessoryCategory::HATS) return USE_PRESET_COORDINATES_HAT;
if(category == AccessoryCategory::SCARFS) return USE_PRESET_COORDINATES_SCARFS;
if(category == AccessoryCategory::GLASSES) return USE_PRESET_COORDINATES_GLASSES;
return false;
}
bool loadPresetCoordinatesForCategory(int category, bool makeCurrent)
{
if(category == AccessoryCategory::HATS) return loadPresetHatCoordinates(makeCurrent);
if(category == AccessoryCategory::SCARFS) return loadPresetScarfCoordinates(makeCurrent);
if(category == AccessoryCategory::GLASSES) return loadPresetGlassesCoordinates(makeCurrent);
return false;
}
int baseCharacterSlot(jjPLAYER@ play)
{
if(CHARS_ENABLED && CharsNew::characters[play.playerID] > 0){
return CharsNew::characters[play.playerID];
}
if(play.charCurr == CHAR::JAZZ) return 1;
if(play.charCurr == CHAR::SPAZ) return 2;
if(play.charCurr == CHAR::FROG) return 4;
if(play.charCurr == CHAR::BIRD) return 5;
if(play.charCurr == CHAR::BIRD2) return 6;
if(play.charCurr == CHAR::LORI) return 3;
return 0;
}
int defaultCharacterSlotForCustom(int charSlot)
{
if(charSlot == 1 || charSlot == 11) return 1;
if(charSlot == 3) return 3;
if(charSlot == 4) return 4;
if(charSlot == 5 || charSlot == 6) return charSlot;
return 2;
}
int autoPositionIndex(jjPLAYER@ play, int frameIndex, int animRelIndex)
{
int charSlot = baseCharacterSlot(play);
return autoPositionIndexForSlot(charSlot, frameIndex, animRelIndex);
}
int autoPositionIndex(jjPLAYER@ play, int frameIndex, int animRelIndex, int fallbackFrameIndex)
{
int charSlot = baseCharacterSlot(play);
return autoPositionIndexForSlot(charSlot, frameIndex, animRelIndex, fallbackFrameIndex);
}
int drawAdjustX = 0;
int drawAdjustY = 0;
int drawRotation = 0;
int drawUsing = 0;
float drawScaleX = 1;
float drawScaleY = 1;
void setDrawPositionFromIndex(int category, int index)
{
int categoryIndex = category * positionArrayLength() + index;
drawAdjustX = categoryAdjustX[categoryIndex];
drawAdjustY = categoryAdjustY[categoryIndex];
drawRotation = categoryRotation[categoryIndex];
drawUsing = categoryUsing[categoryIndex];
drawScaleX = categoryScaleX[categoryIndex];
drawScaleY = categoryScaleY[categoryIndex];
}
int autoPositionIndexForSlot(int charSlot, int frameIndex, int animRelIndex, int fallbackFrameIndex = -1, int category = -1)
{
if(category < 0) category = editCategory;
int k = getPositionIndex(charSlot, frameIndex);
if(categoryEdited[category * positionArrayLength() + k] == 1) {
setDrawPositionFromIndex(category, k);
return k;
}
if(fallbackFrameIndex < 0)
fallbackFrameIndex = frameIndex;
int fallback = getPositionIndex(defaultCharacterSlotForCustom(charSlot), fallbackFrameIndex);
setDrawPositionFromIndex(category, fallback);
if(CHARS_ENABLED
&& charSlot >= 0
&& charSlot < int(CharsNew::chars.length))
{
if(animRelIndex < 0)
animRelIndex = 0;
if(animRelIndex >= ANIMS_PER_SET)
animRelIndex = ANIMS_PER_SET - 1;
int animRel = animRelIndex;
if(animRelIndex < int(CharsNew::chars[charSlot].animRel.length))
animRel = CharsNew::chars[charSlot].animRel[animRelIndex];
if(animRel >= 0 && animRel < FRAMES_PER_CHARACTER)
{
fallback = getPositionIndex(
defaultCharacterSlotForCustom(charSlot),
fallbackFrameIndex
);
int offsetX = 0;
int offsetY = 0;
if(animRelIndex < int(CharsNew::chars[charSlot].adjustX.length))
offsetX = CharsNew::chars[charSlot].adjustX[animRelIndex];
if(animRelIndex < int(CharsNew::chars[charSlot].adjustY.length))
offsetY = CharsNew::chars[charSlot].adjustY[animRelIndex];
int categoryFallback = category * positionArrayLength() + fallback;
drawAdjustX = categoryAdjustX[categoryFallback] + offsetX;
drawAdjustY = categoryAdjustY[categoryFallback] + offsetY;
drawRotation = categoryRotation[categoryFallback];
drawUsing = categoryUsing[categoryFallback];
drawScaleX = categoryScaleX[categoryFallback];
drawScaleY = categoryScaleY[categoryFallback];
return k;
}
}
return fallback;
}
int baseAnimationForSlot(int charSlot)
{
int baseSlot = defaultCharacterSlotForCustom(charSlot);
if(baseSlot == 1) return ANIM::JAZZ;
if(baseSlot == 3) return ANIM::LORI;
if(baseSlot == 4) return ANIM::FROG;
if(baseSlot == 5 || baseSlot == 6) return ANIM::BIRD;
return ANIM::SPAZ;
}
bool isHangLedgeAccessorySlot(int charSlot)
{
return charSlot == 0 || charSlot == CharsNew::CharAnim::JAZZ || charSlot == CharsNew::CharAnim::SPAZ;
}
int hangLedgeAnimSetForSlot(int charSlot)
{
return charSlot == CharsNew::CharAnim::SPAZ ? ANIM::SPAZ : ANIM::JAZZ;
}
int ledgesEditorAnimSetForSlot(int charSlot)
{
if(charSlot == CharsNew::CharAnim::SPAZ)
{
if(ledgesSpazLoaded) return ANIM::CUSTOM[ledgesSpazSet];
if(ledgesSingleSetMode) return ANIM::CUSTOM[ledgesJazzSet];
return hangLedgeAnimSetForSlot(charSlot);
}
if(ledgesJazzLoaded) return ANIM::CUSTOM[ledgesJazzSet];
return hangLedgeAnimSetForSlot(charSlot);
}
int ledgesEditorAnimIDForSlot(int charSlot)
{
if(charSlot == CharsNew::CharAnim::SPAZ)
{
if(ledgesSpazLoaded) return 0;
if(ledgesSingleSetMode) return 1;
return CharsNew::HangLedgeDrawAnim();
}
return ledgesJazzLoaded ? 0 : CharsNew::HangLedgeDrawAnim();
}
bool ledgesEditorUsesCustomAnim(int charSlot)
{
if(charSlot == CharsNew::CharAnim::SPAZ)
return ledgesSpazLoaded || ledgesSingleSetMode;
return ledgesJazzLoaded;
}
int hangLedgeEditorFrameIndex(int frameID, bool wrapFrame = false)
{
int count = jjAnimations[jjAnimSets[ledgesEditorAnimSetForSlot(selectedCharacterSlot)].firstAnim + ledgesEditorAnimIDForSlot(selectedCharacterSlot)].frameCount;
if(count <= 0) count = 1;
int drawFrame = frameID;
if(drawFrame >= count)
drawFrame = wrapFrame ? drawFrame % count : count - 1;
if(drawFrame < 0) drawFrame = 0;
int maxFrame = FRAMES_PER_CHARACTER - HANG_LEDGE_FRAME_INDEX_START - 1;
if(drawFrame > maxFrame) drawFrame = maxFrame;
return HANG_LEDGE_FRAME_INDEX_START + drawFrame;
}
int animationForPlayer(jjPLAYER@ play)
{
if(CHARS_ENABLED && CharsNew::forceFrogVisual[play.playerID]) return ANIM::FROG;
if(play.charCurr == CHAR::JAZZ) return ANIM::JAZZ;
if(play.charCurr == CHAR::SPAZ) return ANIM::SPAZ;
if(play.charCurr == CHAR::FROG) return ANIM::FROG;
if(play.charCurr == CHAR::LORI) return ANIM::LORI;
if(play.charCurr == CHAR::BIRD || play.charCurr == CHAR::BIRD2) return ANIM::BIRD;
return -1;
}
int headSourceCharacterSlot(int charSlot)
{
if(charSlot == CharsNew::CharAnim::HEAD_MARIO) return CharsNew::CharAnim::MARIO;
if(charSlot == CharsNew::CharAnim::HEAD_SONIC || charSlot == CharsNew::CharAnim::HEAD_EGGMAN) return CharsNew::CharAnim::SONIC;
if(charSlot == CharsNew::CharAnim::HEAD_TAILS) return CharsNew::CharAnim::TAILS;
if(charSlot == CharsNew::CharAnim::HEAD_DEVAN) return CharsNew::CharAnim::DEVAN;
if(charSlot == CharsNew::CharAnim::HEAD_YOSHI) return CharsNew::CharAnim::YOSHI;
if(charSlot == CharsNew::CharAnim::HEAD_LORI && !jjIsTSF) return CharsNew::CharAnim::LORI1;
return charSlot;
}
int sourceSetForFrameSlot(int charSlot)
{
int sourceCharSlot = headSourceCharacterSlot(charSlot);
if(sourceCharSlot >= 0 && sourceCharSlot < int(CharsNew::chars.length))
return CharsNew::chars[sourceCharSlot].CharactersMinSourceSet;
return -1;
}
int coordinateSourceSetForFrameSlot(int charSlot)
{
int sourceCharSlot = headSourceCharacterSlot(charSlot);
int sourceSet = sourceSetForFrameSlot(charSlot);
if(sourceCharSlot == CharsNew::CharAnim::YOSHI && sourceSet == CharactersMinAnim::SET_BLUEYOSHI)
return CharactersMinAnim::SET_YOSHI;
return sourceSet;
}
bool isHeadSlot(int charSlot)
{
return CharsNew::isPlayerHead(charSlot) || CharsNew::isCustomHead(charSlot);
}
int editorAnimationForSlot(int charSlot)
{
if(charSlot == CharsNew::CharAnim::WITCH) return ANIM::WITCH;
if(charSlot == CharsNew::CharAnim::EVA) return ANIM::EVA;
if(isHeadSlot(charSlot)
&& charSlot >= 0
&& charSlot < int(CharsNew::chars.length))
return CharsNew::chars[charSlot].HeadAnimSet;
if(CHARS_ENABLED
&& charSlot >= 0
&& charSlot < int(CharsNew::chars.length)
&& CharsNew::chars[charSlot].Set != -1)
return ANIM::CUSTOM[CharsNew::chars[charSlot].Set];
return baseAnimationForSlot(charSlot);
}
int editorAnimationForAnimID(int charSlot, int animID)
{
if(charSlot == CharsNew::CharAnim::EVA && (animID == 5 || animID == 6))
return ANIM::FLAG;
return editorAnimationForSlot(charSlot);
}
bool isSpecialAccessorySlot(int charSlot)
{
return charSlot == CharsNew::CharAnim::WITCH
|| charSlot == CharsNew::CharAnim::EVA
|| isHeadSlot(charSlot);
}
int headOffsetXForDirection(int charSlot, int direction)
{
if(charSlot == CharsNew::CharAnim::HEAD_JAZZ || charSlot == CharsNew::CharAnim::HEAD_SPAZ || charSlot == CharsNew::CharAnim::HEAD_LORI)
return direction == 1 ? 16 : -24;
if(charSlot == CharsNew::CharAnim::HEAD_FROG)
return direction == 1 ? 14 : -18;
if(charSlot == CharsNew::CharAnim::HEAD_BIRD || charSlot == CharsNew::CharAnim::HEAD_BIRD2)
return direction == 1 ? 8 : -16;
if(charSlot == CharsNew::CharAnim::HEAD_MARIO || charSlot == CharsNew::CharAnim::HEAD_SONIC)
return direction == 1 ? 14 : -18;
if(charSlot == CharsNew::CharAnim::HEAD_TAILS || charSlot == CharsNew::CharAnim::HEAD_DEVAN)
return direction == 1 ? 15 : -20;
if(charSlot == CharsNew::CharAnim::HEAD_YOSHI)
return direction == 1 ? 12 : -12;
return 0;
}
float headScaleForSlot(int charSlot)
{
if(charSlot == CharsNew::CharAnim::HEAD_TAILS || charSlot == CharsNew::CharAnim::HEAD_YOSHI)
return 1.5;
return 1;
}
int playerAnimRelIndex(jjPLAYER@ play)
{
return CharsNew::PlayerRabbitAnimRel(play);
}
// The real Spaz bird animations are stored separately from the ordinary
// rabbit list. 78 and 79 are real coordinate targets; do not reserve them
// for the ledge-editor preview.
const int SPAZ_BIRD_ANIM_FIRST = 78;
const int SPAZ_BIRD_ANIM_LAST = 79;
array<int> rabbit123JazzPackedOffset(128, -1);
array<int> rabbit123JazzFrameCount(128, 0);
array<int> rabbit123SpazPackedOffset(128, -1);
array<int> rabbit123SpazFrameCount(128, 0);
bool isSpazBirdEditorAnim(int charSlot, int animID)
{
return charSlot == CharsNew::CharAnim::SPAZ
&& animID >= SPAZ_BIRD_ANIM_FIRST
&& animID <= SPAZ_BIRD_ANIM_LAST;
}
int spazBirdLocalAnimID(int animID)
{
return animID - SPAZ_BIRD_ANIM_FIRST;
}
int rabbit123NativeAnimFor124(int anim124)
{
if(anim124 >= 0 && anim124 < int(CharsNew::animIndex.length))
return CharsNew::animIndex[anim124];
// These are the two native 1.23 replacement animations for Spaz's bird.
if(anim124 == 78) return 104;
if(anim124 == 79) return 105;
return -1;
}
void buildRabbit123PackedFrameCacheForSet(int animSet, array<int>@ offsets, array<int>@ counts)
{
for(uint i = 0; i < offsets.length; i++)
{
offsets[i] = -1;
counts[i] = 0;
}
if(jjIsTSF || (animSet != ANIM::JAZZ && animSet != ANIM::SPAZ)) return;
int firstAnim = jjAnimSets[animSet].firstAnim;
int packedFrame = 0;
for(int anim124 = 0; anim124 < int(offsets.length); anim124++)
{
int nativeAnim = rabbit123NativeAnimFor124(anim124);
if(nativeAnim < 0) continue;
int count = jjAnimations[firstAnim + nativeAnim].frameCount;
if(count <= 0) continue;
offsets[anim124] = packedFrame;
counts[anim124] = count;
packedFrame += count;
}
}
void buildRabbit123PackedFrameCache()
{
buildRabbit123PackedFrameCacheForSet(ANIM::JAZZ, @rabbit123JazzPackedOffset, @rabbit123JazzFrameCount);
buildRabbit123PackedFrameCacheForSet(ANIM::SPAZ, @rabbit123SpazPackedOffset, @rabbit123SpazFrameCount);
}
int rabbit123PackedFrameFor124(int animSet, int anim124, int frameInAnimation)
{
if(animSet != ANIM::JAZZ && animSet != ANIM::SPAZ)
return -1;
int nativeAnim = rabbit123NativeAnimFor124(anim124);
if(nativeAnim < 0)
return -1;
if(!jjIsTSF && anim124 >= 0 && anim124 < 128)
{
int offset = animSet == ANIM::JAZZ ? rabbit123JazzPackedOffset[anim124] : rabbit123SpazPackedOffset[anim124];
int count = animSet == ANIM::JAZZ ? rabbit123JazzFrameCount[anim124] : rabbit123SpazFrameCount[anim124];
if(count <= 0 || offset < 0) return -1;
if(frameInAnimation < 0) frameInAnimation = 0;
if(frameInAnimation >= count) frameInAnimation = count - 1;
return offset + frameInAnimation;
}
int firstAnim = jjAnimSets[animSet].firstAnim;
int packedFrame = 0;
for(int i = 0; i < anim124; i++)
{
int nativeBefore = rabbit123NativeAnimFor124(i);
if(nativeBefore < 0)
return -1;
int count = jjAnimations[firstAnim + nativeBefore].frameCount;
if(count > 0)
packedFrame += count;
}
int count = jjAnimations[firstAnim + nativeAnim].frameCount;
if(count <= 0)
return -1;
if(frameInAnimation < 0) frameInAnimation = 0;
if(frameInAnimation >= count) frameInAnimation = count - 1;
return packedFrame + frameInAnimation;
}
int spazBirdEditorFrameIndex(int animID, int frameID, bool wrapFrame = false)
{
if(!isSpazBirdEditorAnim(CharsNew::CharAnim::SPAZ, animID))
return -1;
int localAnim = spazBirdLocalAnimID(animID);
int specialSet = ANIM::SPAZ2;
int count = jjAnimations[jjAnimSets[specialSet].firstAnim + localAnim].frameCount;
if(count <= 0)
return -1;
int drawFrame = frameID;
if(drawFrame < 0) drawFrame = 0;
else if(drawFrame >= count)
drawFrame = wrapFrame ? drawFrame % count : count - 1;
// 1.23 coordinate files use the canonical 1.24 packed rows.
if(!jjIsTSF)
return rabbit123PackedFrameFor124(ANIM::SPAZ, animID, drawFrame);
// In TSF the separate SPAZ2 frames are physically packed after the
// normal SPAZ frames, which is the same coordinate numbering used in
// the hat data.
return jjAnimations[jjAnimSets[specialSet].firstAnim + localAnim].firstFrame
+ drawFrame
- jjAnimations[jjAnimSets[ANIM::SPAZ].firstAnim].firstFrame;
}
// Convert a native 1.23 Jazz/Spaz frame to the packed 1.24 coordinate row.
// This includes native 104/105, the two separate Spaz bird animations.
int rabbit123PackedFrameIndex(int animSet, int nativeAnim, jjPLAYER@ play)
{
if(jjIsTSF || play is null)
return -1;
int anim124 = CharsNew::revertIndex(nativeAnim);
if(anim124 < 0 || rabbit123NativeAnimFor124(anim124) != nativeAnim)
return -1;
int currentCount = jjAnimations[jjAnimSets[animSet].firstAnim + nativeAnim].frameCount;
if(currentCount <= 0)
return -1;
int frameInAnimation = play.frameID;
if(frameInAnimation < 0 || frameInAnimation >= currentCount)
frameInAnimation = play.curFrame
- jjAnimations[jjAnimSets[animSet].firstAnim + nativeAnim].firstFrame;
return rabbit123PackedFrameFor124(animSet, anim124, frameInAnimation);
}
int animationForSlot(int charSlot, jjPLAYER@ play)
{
if(CHARS_ENABLED
&& charSlot >= 0
&& uint(charSlot) < CharsNew::chars.length
&& CharsNew::chars[charSlot].Set != -1)
return ANIM::CUSTOM[CharsNew::chars[charSlot].Set];
return animationForPlayer(play);
}
int frameIndexForAnimFrame(int animSet, int animID, int frameID, int charSlot = -1, bool wrapFrame = false)
{
if(animSet == -1 || animID < 0)
return -1;
int sourceCharSlot = charSlot >= 0 ? charSlot : selectedCharacterSlot;
if(isSpazBirdEditorAnim(sourceCharSlot, animID))
return spazBirdEditorFrameIndex(animID, frameID, wrapFrame);
if(animID == CharsNew::HANG_LEDGE_EDITOR_ANIM && isHangLedgeAccessorySlot(sourceCharSlot))
return hangLedgeEditorFrameIndex(frameID, wrapFrame);
if(sourceCharSlot == CharsNew::CharAnim::DEVAN)
{
if(animID < 0 || animID >= int(CharsNew::DevanAnim::WARP_OUT_GUN) + 1)
return -1;
int firstAnim = jjAnimSets[ANIM::DEVILDEVAN].firstAnim;
int frameCount = jjAnimations[firstAnim + animID].frameCount;
int drawFrame = frameID;
if(frameCount <= 0)
drawFrame = 0;
else if(drawFrame >= frameCount)
drawFrame = wrapFrame ? drawFrame % frameCount : frameCount - 1;
return jjAnimations[firstAnim + animID].firstFrame + drawFrame
- jjAnimations[firstAnim].firstFrame;
}
int sourceSet = coordinateSourceSetForFrameSlot(sourceCharSlot);
int mappedAnim = 0;
int mappedFrame = 0;
if(!CharsNew::RemapCharactersMinFrame(animSet, sourceSet, animID, frameID, mappedAnim, mappedFrame))
return -1;
int firstAnim = jjAnimSets[animSet].firstAnim;
int frameCount = jjAnimations[firstAnim + mappedAnim].frameCount;
int drawFrame = mappedFrame;
if(frameCount <= 0)
drawFrame = 0;
else if(drawFrame >= frameCount)
drawFrame = wrapFrame ? drawFrame % frameCount : frameCount - 1;
return jjAnimations[firstAnim + mappedAnim].firstFrame + drawFrame
- jjAnimations[firstAnim].firstFrame;
}
int add = 0;
void readFileForCategory(int category, bool makeCurrent)
{
bool loadedPreset = usePresetCoordinatesForCategory(category) && loadPresetCoordinatesForCategory(category, makeCurrent);
if(!loadedPreset)
{
resetCategoryPositionArrays(category);
if(makeCurrent) resetPositionArrays();
}
add = 0;
string filename = category >= 0 && category < int(AccessoryCoordinateFilenames.length)
? AccessoryCoordinateFilenames[category]
: AccessoryCoordinateFilenames[AccessoryCategory::HATS];
jjSTREAM load(filename);
while(!load.isEmpty()){
string str = "";
load.getLine(str);
array<string> string_parts = str.split(" ");
if(string_parts.length < 5) continue;
int frameIndex = parseInt(string_parts[0]);
int slot = legacyCharacterSlotFromBlock(add);
bool hasSavedSlot = false;
if(string_parts.length > 5) {
int savedSlot = parseInt(string_parts[5]);
if(savedSlot > 0) {
slot = savedSlot;
hasSavedSlot = true;
}
}
int index = getPositionIndex(slot, frameIndex);
int categoryIndex = getCategoryPositionIndex(category, slot, frameIndex);
categoryAdjustX[categoryIndex] = parseInt(string_parts[1]);
categoryAdjustY[categoryIndex] = parseInt(string_parts[2]);
categoryRotation[categoryIndex] = parseInt(string_parts[3]);
categoryUsing[categoryIndex] = parseInt(string_parts[4]);
categoryScaleX[categoryIndex] = 1;
categoryScaleY[categoryIndex] = 1;
if(string_parts.length > 7 && string_parts[6] != "" && string_parts[7] != ""){
categoryScaleX[categoryIndex] = parseFloat(string_parts[6]);
categoryScaleY[categoryIndex] = parseFloat(string_parts[7]);
}
categoryEdited[categoryIndex] = 1;
if(frameIndex == FRAMES_PER_CHARACTER - 1 && !hasSavedSlot){
add+=1;
//jjAlert("added "+add, false);
}
}
if(makeCurrent) copyCategoryToCurrent(category);
updateCurrentPositionFromFirstEdited(makeCurrent);
}
void readFile(){
readFileForCategory(editCategory, true);
currentCategoryLoaded = true;
}
void readAllCategoryFiles()
{
for(int category = 0; category < int(AccessoryCategoryNames.length); category++)
readFileForCategory(category, category == editCategory);
currentCategoryLoaded = true;
}
bool read = false;
array<int> yoshiDebugUntil(32, 0);
array<bool> yoshiDebugJumpWasDown(32, false);
array<string> yoshiDebugLast(32, "");
void updateYoshiDebugTrigger(jjPLAYER@ play, int charSlot)
{
if(!DEBUG_YOSHI_ON || play.playerID < 0 || play.playerID >= 32) return;
bool isYoshi = charSlot == CharsNew::CharAnim::YOSHI;
bool jumpDown = isYoshi && play.keyJump;
if(jumpDown && !yoshiDebugJumpWasDown[play.playerID]){
yoshiDebugUntil[play.playerID] = jjGameTicks + 140;
yoshiDebugLast[play.playerID] = "";
}
yoshiDebugJumpWasDown[play.playerID] = jumpDown;
}
void alertYoshiDebug(jjPLAYER@ play, int charSlot, int animSet, int animID, int frameID, int frameIndex, int positionIndex)
{
if(!DEBUG_YOSHI_ON || charSlot != CharsNew::CharAnim::YOSHI) return;
if(play.playerID < 0 || play.playerID >= 32) return;
if(jjGameTicks > yoshiDebugUntil[play.playerID]) return;
string key = animSet + ":" + animID + ":" + frameID + ":" + frameIndex + ":" + drawAdjustX + ":" + drawAdjustY + ":" + drawRotation + ":" + drawScaleX + ":" + drawScaleY;
if(key == yoshiDebugLast[play.playerID]) return;
yoshiDebugLast[play.playerID] = key;
jjAlert(
"Yoshi set " + animSet
+ " anim " + animID
+ " frame " + frameID
+ " index " + frameIndex
+ " pos " + positionIndex
+ " hat x " + drawAdjustX
+ " y " + drawAdjustY
+ " rot " + drawRotation
+ " sx " + drawScaleX
+ " sy " + drawScaleY,
false
);
}
int effectiveAccessoryMappedAnim(jjPLAYER@ play, int charSlot, int animRelIndex, int mappedAnim)
{
if(charSlot == CharsNew::CharAnim::SONIC || charSlot == CharsNew::CharAnim::MARIO)
{
int rawAnim = play.curAnim - jjAnimSets[play.setID].firstAnim;
if(play.setID == ANIM::SPAZ && rawAnim > 77)
return jjIsTSF ? 33 : 30;
else if(play.setID == ANIM::SPAZ2 && rawAnim <= 3)
return mappedAnim + (jjIsTSF ? 78 : 104);
}
return mappedAnim;
}
int effectiveAccessoryFrame(jjPLAYER@ play, int charSlot, int animRelIndex, int mappedAnim)
{
if(charSlot == CharsNew::CharAnim::YOSHI)
{
if(mappedAnim == 13 || mappedAnim == 48) return 0;
if(animRelIndex == 5 || animRelIndex == 44) return 1;
if(animRelIndex == 14) return jjGameTicks / 10;
return play.frameID;
}
if(charSlot == CharsNew::CharAnim::JJ1)
{
if(mappedAnim == 11) return jjGameTicks / 10;
if(animRelIndex < 78 && animRelIndex >= 0)
return animRelIndex == 20 ? jjGameTicks / 10 : play.frameID;
return play.frameID;
}
if(charSlot == CharsNew::CharAnim::TAILS)
{
if(mappedAnim == 16) return jjGameTicks / 10;
if(mappedAnim == 12) return 0;
if(mappedAnim == 6) return (jjGameTicks / 10) % 8;
return jjGameTicks / 10;
}
return play.frameID;
}
bool specialAccessoryFrame(jjPLAYER@ play, int charSlot, int &out animSet, int &out animID, int &out frameID, int &out frameIndex)
{
if(isHangLedgeAccessorySlot(charSlot) && CharsNew::IsHangLedgeActive(play.playerID))
{
animSet = baseAnimationForSlot(charSlot);
animID = CharsNew::HANG_LEDGE_EDITOR_ANIM;
frameID = CharsNew::HangLedgeFrame(play.playerID);
frameIndex = frameIndexForAnimFrame(animSet, animID, frameID, charSlot, true);
return frameIndex >= 0;
}
if(charSlot == CharsNew::CharAnim::WITCH)
{
animSet = ANIM::WITCH;
animID = play.health > 0 ? (play.keyFire ? 0 : 2) : 1;
frameID = play.health > 0 ? jjGameTicks / 10 : jjGameTicks / 3;
frameIndex = frameIndexForAnimFrame(animSet, animID, frameID, charSlot, true);
return frameIndex >= 0;
}
if(charSlot == CharsNew::CharAnim::EVA)
{
int ind = CharsNew::PlayerRabbitAnimRelForSet(play, ANIM::SPAZ);
int currentEvaFrame = CharsNew::evaFrame[play.playerID];
if(currentEvaFrame == -1 && (play.specialMove > 0 || (62 <= ind && ind <= 64)))
currentEvaFrame = 0;
if(currentEvaFrame == -1)
{
if(48 <= ind && ind <= 50){
animSet = ANIM::FLAG;
animID = 5;
frameID = jjGameTicks / 10;
}else if(ind == 11 || (51 <= ind && ind <= 53) || (37 <= ind && ind <= 39) || ind == 15 || ind == 44){
animSet = ANIM::FLAG;
animID = 6;
frameID = jjGameTicks / 10;
}else{
animSet = ANIM::EVA;
animID = 0;
frameID = jjGameTicks / 20;
}
}else{
animSet = ANIM::EVA;
animID = currentEvaFrame > 13 ? 3 : 2;
frameID = currentEvaFrame > 13 ? (currentEvaFrame - 14) : currentEvaFrame;
}
frameIndex = frameIndexForAnimFrame(animSet, animID, frameID, charSlot, true);
return frameIndex >= 0;
}
if(isHeadSlot(charSlot)
&& charSlot >= 0
&& charSlot < int(CharsNew::chars.length)
&& CharsNew::chars[charSlot].LivesAnim >= 0)
{
animSet = CharsNew::chars[charSlot].HeadAnimSet;
animID = CharsNew::chars[charSlot].LivesAnim;
frameID = jjGameTicks / 10;
frameIndex = frameIndexForAnimFrame(animSet, animID, frameID, charSlot, true);
return frameIndex >= 0;
}
return false;
}
void onMain(){
if(!currentCategoryLoaded){
//jjAlert("read");
readAllCategoryFiles();
read = true;
}
for(uint i=0; i<32; i++){
if(jjPlayers[i].isActive){
jjPLAYER@ play = jjPlayers[i];
if(play.isInGame){
if(!play.isLocal && !CharsNew::isPlayerVisible(play.playerID, 128))
continue;
if(!hasVisibleAccessory(play.playerID))
continue;
// Nyan Rush owns the complete player visual while active.
// Skip every runtime accessory category (hat, scarf, glasses, etc.)
// without changing the player's saved accessory selections.
if(CharsNyanRush::ShouldSuppressVisuals(play.playerID))
continue;
if(MONSTERS_ON && CharsMonsters::ShouldSuppressHats(play.playerID))
continue;
//Draw Hat
{//Draw hats for normal, custom, frog, and bird morphs.
int charSlot = baseCharacterSlot(play);
updateYoshiDebugTrigger(play, charSlot);
int animSet = animationForPlayer(play);
if(animSet == -1)
continue;
int animRelIndex = play.curAnim - jjAnimSets[animSet].firstAnim;
int frameIndex = play.curFrame - jjAnimations[jjAnimSets[animSet].firstAnim].firstFrame;
if(CHARS_ENABLED && CharsNew::forceFrogVisual[play.playerID])
{
animRelIndex = CharsNew::ForcedFrogAnimID(play);
frameIndex = CharsNew::ForcedFrogFrameIndex(play);
}
// Ordinary 1.23 Jazz/Spaz hats use rows packed in 1.24 order.
// Convert both the native animation and the packed-frame position.
// Hanging uses its dedicated branch below and replaces these values.
if(!jjIsTSF
&& (charSlot == CharsNew::CharAnim::JAZZ
|| charSlot == CharsNew::CharAnim::SPAZ))
{
int convertedFrameIndex = rabbit123PackedFrameIndex(
animSet,
animRelIndex,
play
);
if(convertedFrameIndex >= 0)
{
animRelIndex = CharsNew::revertIndex(animRelIndex);
frameIndex = convertedFrameIndex;
}
}
int fallbackFrameIndex = frameIndex;
int debugAnimSet = animSet;
int debugAnimID = animRelIndex;
int debugFrameID = play.frameID;
int accessoryBaseOffsetX = 0;
float accessoryBaseOffsetY = 0;
float accessoryBaseScale = 1;
if(CHARS_ENABLED
&& play.fly == FLIGHT::AIRBOARD
&& (charSlot == CharsNew::CharAnim::JAZZ
|| charSlot == CharsNew::CharAnim::SPAZ
|| charSlot == CharsNew::CharAnim::LORI
|| charSlot == CharsNew::CharAnim::LORI1))
{
accessoryBaseOffsetY = CharsNew::AirboardWobbleYOffset(play);
}
bool hangingOnLedge = CHARS_ENABLED
&& isHangLedgeAccessorySlot(charSlot)
&& CharsNew::IsHangLedgeActive(play.playerID)
&& !(MONSTERS_ON && CharsMonsters::IsActive(play.playerID));
if(hangingOnLedge)
{
int specialAnimSet = animSet;
int specialAnimID = animRelIndex;
int specialFrameID = play.frameID;
int specialFrameIndex = frameIndex;
if(specialAccessoryFrame(play, charSlot, specialAnimSet, specialAnimID, specialFrameID, specialFrameIndex))
{
animRelIndex = specialAnimID;
frameIndex = specialFrameIndex;
fallbackFrameIndex = specialFrameIndex;
debugAnimSet = specialAnimSet;
debugAnimID = specialAnimID;
debugFrameID = specialFrameID;
}
accessoryBaseOffsetY = charSlot == CharsNew::CharAnim::SPAZ
? HANG_LEDGE_RUNTIME_ACCESSORY_Y_OFFSET_SPAZ
: HANG_LEDGE_RUNTIME_ACCESSORY_Y_OFFSET_JAZZ;
}
else if(CHARS_ENABLED && isSpecialAccessorySlot(charSlot))
{
int specialAnimSet = animSet;
int specialAnimID = animRelIndex;
int specialFrameID = play.frameID;
int specialFrameIndex = frameIndex;
if(specialAccessoryFrame(play, charSlot, specialAnimSet, specialAnimID, specialFrameID, specialFrameIndex))
{
animRelIndex = specialAnimID;
frameIndex = specialFrameIndex;
fallbackFrameIndex = specialFrameIndex;
debugAnimSet = specialAnimSet;
debugAnimID = specialAnimID;
debugFrameID = specialFrameID;
}
if(charSlot == CharsNew::CharAnim::EVA)
accessoryBaseOffsetY = -4;
else if(isHeadSlot(charSlot)
&& charSlot >= 0
&& charSlot < int(CharsNew::chars.length))
{
accessoryBaseOffsetX = -headOffsetXForDirection(charSlot, play.direction);
accessoryBaseOffsetY = CharsNew::chars[charSlot].HeadOffsetY;
accessoryBaseScale = headScaleForSlot(charSlot);
}
}
else if(CHARS_ENABLED && charSlot == CharsNew::CharAnim::DEVAN)
{
animRelIndex = int(CharsNew::var[play.playerID].anim);
int devanFrame = int(CharsNew::var[play.playerID].frame);
frameIndex = frameIndexForAnimFrame(ANIM::DEVILDEVAN, animRelIndex, devanFrame, charSlot);
fallbackFrameIndex = frameIndex;
debugAnimSet = ANIM::DEVILDEVAN;
debugAnimID = animRelIndex;
debugFrameID = devanFrame;
}
else if(charSlot > 4 && CHARS_ENABLED
&& charSlot >= 0
&& uint(charSlot) < CharsNew::chars.length
&& CharsNew::chars[charSlot].Set != -1)
{
animRelIndex = playerAnimRelIndex(play);
if(animRelIndex < 0 || animRelIndex >= ANIMS_PER_SET)
continue;
int mappedAnim = getMappedEditAnimID(charSlot, animRelIndex);
mappedAnim = effectiveAccessoryMappedAnim(play, charSlot, animRelIndex, mappedAnim);
int effectiveFrame = effectiveAccessoryFrame(play, charSlot, animRelIndex, mappedAnim);
frameIndex = frameIndexForAnimFrame(ANIM::CUSTOM[CharsNew::chars[charSlot].Set], mappedAnim, effectiveFrame, charSlot);
fallbackFrameIndex = frameIndexForAnimFrame(animSet, play.curAnim - jjAnimSets[animSet].firstAnim, play.frameID, defaultCharacterSlotForCustom(charSlot));
debugAnimSet = ANIM::CUSTOM[CharsNew::chars[charSlot].Set];
debugAnimID = mappedAnim;
debugFrameID = effectiveFrame;
}
if(CHARS_ENABLED && charSlot == CharsNew::CharAnim::DEVAN)
{
accessoryBaseOffsetX += play.direction > 0 ? DEVAN_RUNTIME_ACCESSORY_X_OFFSET : -DEVAN_RUNTIME_ACCESSORY_X_OFFSET;
accessoryBaseOffsetY += DEVAN_RUNTIME_ACCESSORY_Y_OFFSET;
}
//if(jjGameTicks%70 == 0 && jjIsServer) jjAlert("k="+k, false);
if(frameIndex >= 0 && frameIndex < FRAMES_PER_CHARACTER){
int dir = play.direction;
int grav = play.antiGrav ? -1 : 1;
if(CharsNew::charsNewHideActive[play.playerID])
continue;
float charScale = CHARS_ENABLED ? CharsNew::PlayerSizeScale(play.playerID) : 1;
float scaledPlayerYOffset = (1 - charScale) * 23 * grav;
for(int category = 0; category < int(AccessoryCategoryNames.length); category++){
if(isAccessoryHidden(category, play.playerID))
continue;
int k = autoPositionIndexForSlot(charSlot, frameIndex, animRelIndex, fallbackFrameIndex, category);
if(category == AccessoryCategory::HATS)
alertYoshiDebug(play, charSlot, debugAnimSet, debugAnimID, debugFrameID, frameIndex, k);
// The ledge body uses these CharsNew offsets after its normal
// tiny/giant feet anchor. Apply the exact same extra correction
// to hats so changing the core value also moves the hat.
float hangLedgeHatSizeYOffset = 0;
if(category == AccessoryCategory::HATS && hangingOnLedge)
{
if(charScale < 1)
hangLedgeHatSizeYOffset = CharsNew::HANG_LEDGE_TINY_Y_OFFSET;
else if(charScale > 1)
hangLedgeHatSizeYOffset = CharsNew::HANG_LEDGE_GIANT_Y_OFFSET;
}
int accessoryIndex = accessoryPlayerIndex(category, play.playerID);
int accessoryFrame = accessoryFrames[accessoryIndex] % accessoryFrameCount(category);
// This is a local-X adjustment, so multiplying by dir makes it
// mirror correctly when Yoshi faces left.
float normalYoshiRunningHatOffsetX = 0;
if(category == AccessoryCategory::HATS
&& charSlot == CharsNew::CharAnim::YOSHI
&& animRelIndex == 56
&& sourceSetForFrameSlot(charSlot) != CharactersMinAnim::SET_BLUEYOSHI)
{
normalYoshiRunningHatOffsetX =
NORMAL_YOSHI_RUNNING_56_HAT_X_OFFSET * dir * charScale;
}
float accessoryDrawX = play.xPos + accessoryBaseOffsetX + normalYoshiRunningHatOffsetX + drawAdjustX*dir*charScale + (dir == -1 ? charScale : 0);
float accessoryDrawY = play.yPos + accessoryBaseOffsetY*grav + scaledPlayerYOffset + hangLedgeHatSizeYOffset + drawAdjustY*grav*charScale + (grav == 1 ? 0 : charScale);
int accessoryDrawRotation = drawRotation*dir*grav;
float accessoryDrawScaleX = accessoryBaseMultiplier*accessoryBaseScale*charScale*play.direction*drawScaleX;
float accessoryDrawScaleY = accessoryBaseMultiplier*accessoryBaseScale*charScale*grav*drawScaleY;
jjDrawRotatedSprite(
accessoryDrawX,
accessoryDrawY,
ANIM::CUSTOM[empty],
category,
accessoryFrame,
accessoryDrawRotation,
accessoryDrawScaleX,
accessoryDrawScaleY,
SPRITE::NORMAL,
0, 4, 4, -1);
if(hasAccessoryColorOffset(category, play.playerID))
{
ensureAccessoryColorMaskAnimation();
}
if(accessoryColorMaskAnimLoaded && hasAccessoryColorOffset(category, play.playerID))
{
for(int colorGroup = 0; colorGroup < ACCESSORY_COLOR_GROUP_COUNT; colorGroup++){
int accessoryColorOffset = getAccessoryColorOffset(category, play.playerID, colorGroup);
if(accessoryColorOffset == 0) continue;
int maskFrame = accessoryColorMaskFrame(colorGroup, category, accessoryFrame);
if(maskFrame >= 0)
jjDrawRotatedSprite(
accessoryDrawX,
accessoryDrawY,
ANIM::CUSTOM[accessoryColorMaskSet],
0,
maskFrame,
accessoryDrawRotation,
accessoryDrawScaleX,
accessoryDrawScaleY,
SPRITE::PALSHIFT,
accessoryColorOffset, 4, 4, -1);
}
}
}
}
}
}
}
}
}
void saveEdit(){
clampCurrentFrame();
int frameIndex = frameIndexForAnimFrame(editorAnimationForAnimID(selectedCharacterSlot, anim_id), anim_id, frame_id, selectedCharacterSlot);
if(frameIndex < 0) return;
chosen_frame = getPositionIndex(coordinateSlotForEditor(selectedCharacterSlot), frameIndex);
adjustX[chosen_frame] = adjX;
adjustY[chosen_frame] = adjY;
rotation[chosen_frame] = rot;
using[chosen_frame] = use;
scaleX[chosen_frame] = adjScaleX;
scaleY[chosen_frame] = adjScaleY;
edited[chosen_frame] = 1;
//jjAlert(chosen_frame+" saved");
}
void saveFile(){
clampCurrentFrame();
int frameIndex = frameIndexForAnimFrame(editorAnimationForAnimID(selectedCharacterSlot, anim_id), anim_id, frame_id, selectedCharacterSlot);
if(frameIndex < 0) return;
chosen_frame = getPositionIndex(coordinateSlotForEditor(selectedCharacterSlot), frameIndex);
copyCurrentToCategory(editCategory);
string filename = currentCoordinateFilename();
int existingRows = countCoordinateRows(filename);
int savedRows = 0;
jjSTREAM save;
for(int slot = 1; slot < characterSlotCount; slot++){
for(int frame = 0; frame < FRAMES_PER_CHARACTER; frame++){
int i = getPositionIndex(slot, frame);
int categoryIndex = editCategory * positionArrayLength() + i;
if(categoryEdited[categoryIndex] == 1){
string str = frame+" "+categoryAdjustX[categoryIndex]+" "+categoryAdjustY[categoryIndex]+" "+categoryRotation[categoryIndex]+" "+categoryUsing[categoryIndex]+" "+slot+" "+categoryScaleX[categoryIndex]+" "+categoryScaleY[categoryIndex]+" \n";
save.write(str);
savedRows++;
}
}
}
if(existingRows > 20 && savedRows < existingRows / 2){
jjAlert("Refusing to shrink " + filename + " from " + existingRows + " rows to " + savedRows + ". Save blocked.", false);
return;
}
save.save(filename);
}
int countCoordinateRows(const string &in filename)
{
jjSTREAM load(filename);
int rows = 0;
while(!load.isEmpty()){
string str = "";
load.getLine(str);
array<string> parts = str.split(" ");
if(parts.length >= 5)
rows++;
}
return rows;
}
int getMappedEditAnimID(int charSlot, int animID)
{
int mappedAnim = animID;
if(charSlot >= 0
&& charSlot < int(CharsNew::chars.length)
&& animID >= 0
&& animID < int(CharsNew::chars[charSlot].animRel.length))
{
int candidate = CharsNew::chars[charSlot].animRel[animID];
if(candidate >= 0 && candidate < ANIMS_PER_SET)
mappedAnim = candidate;
}
return mappedAnim;
}
bool hasMappedEditorAnimList(int charSlot)
{
return charSlot >= 0
&& charSlot < int(CharsNew::chars.length)
&& CharsNew::chars[charSlot].animRel.length > 0;
}
bool hasSingleEditorAnimTarget(int charSlot)
{
return isHeadSlot(charSlot)
&& charSlot >= 0
&& charSlot < int(CharsNew::chars.length)
&& CharsNew::chars[charSlot].LivesAnim >= 0;
}
bool hasFixedEditorAnimTargets(int charSlot)
{
return charSlot == CharsNew::CharAnim::WITCH
|| charSlot == CharsNew::CharAnim::EVA
|| hasSingleEditorAnimTarget(charSlot);
}
bool editorAnimTargetExists(array<int>@ targets, int target)
{
for(uint i = 0; i < targets.length; i++)
if(targets[i] == target) return true;
return false;
}
void appendSpazBirdEditorTargets(int charSlot, array<int>@ targets)
{
if(charSlot != CharsNew::CharAnim::SPAZ) return;
for(int animID = SPAZ_BIRD_ANIM_FIRST; animID <= SPAZ_BIRD_ANIM_LAST; animID++)
{
if(!editorAnimTargetExists(targets, animID))
targets.insertLast(animID);
}
}
void appendHangLedgeEditorTarget(int charSlot, array<int>@ targets)
{
if(!isHangLedgeAccessorySlot(charSlot)) return;
for(int i = int(targets.length) - 1; i >= 0; i--)
{
if(targets[i] == CharsNew::HANG_LEDGE_EDITOR_ANIM)
targets.removeAt(i);
}
targets.insertLast(CharsNew::HANG_LEDGE_EDITOR_ANIM);
}
void buildEditorAnimTargets(int charSlot, array<int>@ targets)
{
targets.resize(0);
if(charSlot == CharsNew::CharAnim::WITCH){
targets.insertLast(0);
targets.insertLast(1);
targets.insertLast(2);
return;
}
if(charSlot == CharsNew::CharAnim::EVA){
targets.insertLast(0);
targets.insertLast(2);
targets.insertLast(3);
targets.insertLast(5);
targets.insertLast(6);
return;
}
if(hasSingleEditorAnimTarget(charSlot)){
targets.insertLast(CharsNew::chars[charSlot].LivesAnim);
return;
}
if(!hasMappedEditorAnimList(charSlot))
{
if(isHangLedgeAccessorySlot(charSlot))
{
int count = getAnimCountForSet(editorAnimationForSlot(charSlot));
if(count < 0) count = 0;
for(int i = 0; i < count; i++)
{
if(i != CharsNew::HANG_LEDGE_EDITOR_ANIM)
targets.insertLast(i);
}
appendSpazBirdEditorTargets(charSlot, targets);
appendHangLedgeEditorTarget(charSlot, targets);
}
return;
}
for(uint i = 0; i < CharsNew::chars[charSlot].animRel.length; i++){
int target = getMappedEditAnimID(charSlot, int(i));
if(target < 0 || target >= ANIMS_PER_SET) continue;
if(!editorAnimTargetExists(targets, target))
targets.insertLast(target);
}
appendSpazBirdEditorTargets(charSlot, targets);
targets.sortAsc();
appendHangLedgeEditorTarget(charSlot, targets);
}
int firstEditorAnimID()
{
array<int> targets;
buildEditorAnimTargets(selectedCharacterSlot, @targets);
if(targets.length > 0) return targets[0];
return 0;
}
int clampEditorAnimID(int value)
{
if(!hasMappedEditorAnimList(selectedCharacterSlot) && !hasFixedEditorAnimTargets(selectedCharacterSlot) && !isHangLedgeAccessorySlot(selectedCharacterSlot))
{
int count = getAnimCountForSet(animation);
if(value < 0) return 0;
if(count <= 0) return 0;
if(value >= count) return count - 1;
return value;
}
array<int> targets;
buildEditorAnimTargets(selectedCharacterSlot, @targets);
if(targets.length == 0) return 0;
for(uint i = 0; i < targets.length; i++)
{
if(targets[i] == value) return value;
}
for(uint i = 0; i < targets.length; i++){
if(targets[i] > value) return targets[i];
}
return targets[int(targets.length) - 1];
}
int stepEditorAnimID(int current, int delta)
{
if(!hasMappedEditorAnimList(selectedCharacterSlot) && !hasFixedEditorAnimTargets(selectedCharacterSlot) && !isHangLedgeAccessorySlot(selectedCharacterSlot))
return clampEditorAnimID(current + delta);
array<int> targets;
buildEditorAnimTargets(selectedCharacterSlot, @targets);
if(targets.length == 0) return 0;
int index = 0;
bool foundCurrent = false;
for(uint i = 0; i < targets.length; i++){
if(targets[i] == current) { index = int(i); foundCurrent = true; break; }
if(!foundCurrent && targets[i] < current) index = int(i);
}
if(!foundCurrent && isHangLedgeAccessorySlot(selectedCharacterSlot) && current > CharsNew::HANG_LEDGE_EDITOR_ANIM && targets.length >= 2)
index = int(targets.length) - 2;
index += delta;
if(index < 0) index = 0;
if(index >= int(targets.length)) index = int(targets.length) - 1;
return targets[index];
}
int getAnimCountForSet(int setID)
{
if(selectedCharacterSlot == CharsNew::CharAnim::DEVAN)
return int(CharsNew::DevanAnim::WARP_OUT_GUN) + 1;
int minCount = CharsNew::GetCharactersMinAnimCount(setID);
if(minCount > 0)
return minCount;
if(setID < 0 || setID >= MAX_ANIM_SETS)
return ANIMS_PER_SET;
int start = jjAnimSets[setID].firstAnim;
int limit = -1;
for(int i = 0; i < MAX_ANIM_SETS; i++){
int nextStart = jjAnimSets[i].firstAnim;
if(nextStart > start && (limit < 0 || nextStart < limit))
limit = nextStart;
}
if(limit < 0)
limit = start + ANIMS_PER_SET;
int count = limit - start;
return count > 0 ? count : ANIMS_PER_SET;
}
int getDevanEditorFrameCount(int animID)
{
switch(animID)
{
case CharsNew::DevanAnim::DEVIL_FIRE_BREATH: return int(CharsNew::numFrames(CharsNew::DevanAnim::DEVIL_FIRE_BREATH));
case CharsNew::DevanAnim::STARS: return int(CharsNew::numFrames(CharsNew::DevanAnim::STARS));
case CharsNew::DevanAnim::FALLING: return int(CharsNew::numFrames(CharsNew::DevanAnim::FALLING));
case CharsNew::DevanAnim::STARS_SQUISH: return int(CharsNew::numFrames(CharsNew::DevanAnim::STARS_SQUISH));
case CharsNew::DevanAnim::FIREBALL: return int(CharsNew::numFrames(CharsNew::DevanAnim::FIREBALL));
case CharsNew::DevanAnim::DEVIL_FLY: return int(CharsNew::numFrames(CharsNew::DevanAnim::DEVIL_FLY));
case CharsNew::DevanAnim::DEVIL_TRANSFORM: return int(CharsNew::numFrames(CharsNew::DevanAnim::DEVIL_TRANSFORM));
case CharsNew::DevanAnim::DEVIL_REVERT: return int(CharsNew::numFrames(CharsNew::DevanAnim::DEVIL_REVERT));
case CharsNew::DevanAnim::IDLE: return int(CharsNew::numFrames(CharsNew::DevanAnim::IDLE));
case CharsNew::DevanAnim::DEVIL_TURN: return int(CharsNew::numFrames(CharsNew::DevanAnim::DEVIL_TURN));
case CharsNew::DevanAnim::WARP_IN: return int(CharsNew::numFrames(CharsNew::DevanAnim::WARP_IN));
case CharsNew::DevanAnim::WARP_OUT: return int(CharsNew::numFrames(CharsNew::DevanAnim::WARP_OUT));
case CharsNew::DevanAnim::BLASTER_DISSAPEAR: return int(CharsNew::numFrames(CharsNew::DevanAnim::BLASTER_DISSAPEAR));
case CharsNew::DevanAnim::DUCK: return int(CharsNew::numFrames(CharsNew::DevanAnim::DUCK));
case CharsNew::DevanAnim::FIRE: return int(CharsNew::numFrames(CharsNew::DevanAnim::FIRE));
case CharsNew::DevanAnim::BLASTER: return int(CharsNew::numFrames(CharsNew::DevanAnim::BLASTER));
case CharsNew::DevanAnim::RUN_JUMP: return int(CharsNew::numFrames(CharsNew::DevanAnim::RUN_JUMP));
case CharsNew::DevanAnim::BLASTERBULLET: return int(CharsNew::numFrames(CharsNew::DevanAnim::BLASTERBULLET));
case CharsNew::DevanAnim::RUN: return int(CharsNew::numFrames(CharsNew::DevanAnim::RUN));
case CharsNew::DevanAnim::RAISE_GUN: return int(CharsNew::numFrames(CharsNew::DevanAnim::RAISE_GUN));
case CharsNew::DevanAnim::FALL: return int(CharsNew::numFrames(CharsNew::DevanAnim::FALL));
case CharsNew::DevanAnim::IDLE_GUN: return int(CharsNew::numFrames(CharsNew::DevanAnim::IDLE_GUN));
case CharsNew::DevanAnim::WARP_IN_GUN: return int(CharsNew::numFrames(CharsNew::DevanAnim::WARP_IN_GUN));
case CharsNew::DevanAnim::WARP_OUT_GUN: return int(CharsNew::numFrames(CharsNew::DevanAnim::WARP_OUT_GUN));
}
return 1;
}
int getCurrentAnimFrameCount()
{
if(isSpazBirdEditorAnim(selectedCharacterSlot, anim_id))
{
int count = jjAnimations[
jjAnimSets[ANIM::SPAZ2].firstAnim + spazBirdLocalAnimID(anim_id)
].frameCount;
return count > 0 ? count : 1;
}
if(anim_id == CharsNew::HANG_LEDGE_EDITOR_ANIM && isHangLedgeAccessorySlot(selectedCharacterSlot))
{
int count = jjAnimations[jjAnimSets[ledgesEditorAnimSetForSlot(selectedCharacterSlot)].firstAnim + ledgesEditorAnimIDForSlot(selectedCharacterSlot)].frameCount;
return count > 0 ? count : 1;
}
if(selectedCharacterSlot == CharsNew::CharAnim::DEVAN)
{
int count = getDevanEditorFrameCount(anim_id);
return count > 0 ? count : 1;
}
int sourceSet = -1;
if(selectedCharacterSlot >= 0 && selectedCharacterSlot < int(CharsNew::chars.length))
sourceSet = sourceSetForFrameSlot(selectedCharacterSlot);
int currentAnimation = editorAnimationForAnimID(selectedCharacterSlot, anim_id);
if(sourceSet >= 0
&& sourceSet < int(CharsNew::charactersMinAnimCount.length)
&& anim_id >= 0
&& anim_id < int(CharsNew::charactersMinAnimCount[sourceSet].length))
{
int minCount = CharsNew::charactersMinAnimCount[sourceSet][anim_id];
if(minCount > 0)
return minCount;
}
if(currentAnimation < 0 || anim_id < 0)
return 1;
int count = jjAnimations[jjAnimSets[currentAnimation].firstAnim + anim_id].frameCount;
return count > 0 ? count : 1;
}
void clampCurrentFrame()
{
int frameCount = getCurrentAnimFrameCount();
if(frameCount <= 0)
frameCount = 1;
if(frame_id >= uint(frameCount))
frame_id = uint(frameCount - 1);
}
void changeEditorAnim(int delta)
{
saveEdit();
anim_id = stepEditorAnimID(anim_id, delta);
frame_id = 0;
adjustForCurrentFrame();
saveFile();
}
void changeEditorFrame(int delta)
{
clampCurrentFrame();
saveEdit();
int frameCount = getCurrentAnimFrameCount();
int nextFrame = int(frame_id) + delta;
if(nextFrame >= frameCount)
{
int nextAnim = stepEditorAnimID(anim_id, 1);
if(nextAnim != anim_id)
{
anim_id = nextAnim;
frame_id = 0;
}
else frame_id = uint(frameCount - 1);
}
else if(nextFrame < 0)
{
int previousAnim = stepEditorAnimID(anim_id, -1);
if(previousAnim != anim_id)
{
anim_id = previousAnim;
frame_id = uint(getCurrentAnimFrameCount() - 1);
}
else frame_id = 0;
}
else frame_id = uint(nextFrame);
adjustForCurrentFrame();
saveFile();
}
void changeEditorCharSlot(int delta)
{
saveEdit();
setEditCharacterSlot(selectedCharacterSlot + delta);
saveFile();
}
void changeEditorCategory(int delta)
{
saveEdit();
saveFile();
int categoryCount = int(AccessoryCategoryNames.length);
editCategory += delta;
while(editCategory < 0) editCategory += categoryCount;
while(editCategory >= categoryCount) editCategory -= categoryCount;
currentCategoryLoaded = false;
readFile();
use = uint(int(use) % accessoryFrameCount(editCategory));
adjustForCurrentFrame();
}
void saveCurrentEditorAdjustment()
{
saveEdit();
}
void flushCurrentEditorAdjustment()
{
saveEdit();
saveFile();
}
void adjustForCurrentFrame(){
clampCurrentFrame();
int frameIndex = frameIndexForAnimFrame(editorAnimationForAnimID(selectedCharacterSlot, anim_id), anim_id, frame_id, selectedCharacterSlot);
if(frameIndex < 0) return;
chosen_frame = getPositionIndex(coordinateSlotForEditor(selectedCharacterSlot), frameIndex);
//jjAlert(chosen_frame+"");
if(edited[chosen_frame] == 1){ //this is so that it does not reset to 0,0 for unedited positions
adjX = adjustX[chosen_frame];
adjY = adjustY[chosen_frame];
rot = rotation[chosen_frame];
adjScaleX = scaleX[chosen_frame];
adjScaleY = scaleY[chosen_frame];
//use = using[chosen_frame];
}
}
void copyPreviousFrame(){
clampCurrentFrame();
int frameIndex = frameIndexForAnimFrame(editorAnimationForAnimID(selectedCharacterSlot, anim_id), anim_id, frame_id, selectedCharacterSlot);
if(frameIndex < 0) return;
int coordinateSlot = coordinateSlotForEditor(selectedCharacterSlot);
chosen_frame = getPositionIndex(coordinateSlot, frameIndex);
if(frameIndex - 1 > 0){
int previous = getPositionIndex(coordinateSlot, frameIndex - 1);
adjX = adjustX[previous];
adjY = adjustY[previous];
rot = rotation[previous];
adjScaleX = scaleX[previous];
adjScaleY = scaleY[previous];
}
}
int increment = 1, rotationIncrement = 10;
bool pressedFire = false;
string formatHatScale(float value)
{
bool negative = value < 0;
float absolute = negative ? -value : value;
int tenths = int(absolute * 10 + 0.5);
int whole = tenths / 10;
int frac = tenths - whole * 10;
return (negative ? "-" : "") + whole + "." + frac;
}
bool isHatNumericField(int action)
{
return action == HAT_ACTION_ANIM
|| action == HAT_ACTION_FRAME
|| action == HAT_ACTION_CHAR
|| action == HAT_ACTION_POS_X
|| action == HAT_ACTION_POS_Y
|| action == HAT_ACTION_ROTATION
|| action == HAT_ACTION_SCALE_X
|| action == HAT_ACTION_SCALE_Y
|| action == HAT_ACTION_ADDITIONAL_HAT;
}
bool isHatFloatField(int action)
{
return action == HAT_ACTION_SCALE_X || action == HAT_ACTION_SCALE_Y;
}
string hatFieldValueText(int action)
{
if(action == HAT_ACTION_ANIM) return "" + anim_id;
if(action == HAT_ACTION_FRAME) return "" + frame_id;
if(action == HAT_ACTION_CHAR) return "" + selectedCharacterSlot;
if(action == HAT_ACTION_POS_X) return "" + adjX;
if(action == HAT_ACTION_POS_Y) return "" + adjY;
if(action == HAT_ACTION_ROTATION) return "" + rot;
if(action == HAT_ACTION_SCALE_X) return formatHatScale(adjScaleX);
if(action == HAT_ACTION_SCALE_Y) return formatHatScale(adjScaleY);
if(action == HAT_ACTION_ADDITIONAL_HAT) return "" + additionalAccessory;
return "";
}
void clampAdditionalAccessory()
{
int count = accessoryFrameCount(editCategory);
if(additionalAccessory < -1) additionalAccessory = -1;
if(count <= 0) additionalAccessory = -1;
else if(additionalAccessory >= count) additionalAccessory = count - 1;
}
void setHatFieldValue(int action, const string &in value)
{
if(value == "" || value == "-" || value == "." || value == "-.") return;
if(action == HAT_ACTION_ANIM)
{
saveEdit();
anim_id = clampEditorAnimID(parseInt(value));
frame_id = 0;
adjustForCurrentFrame();
saveFile();
return;
}
if(action == HAT_ACTION_FRAME)
{
saveEdit();
int newFrame = parseInt(value);
if(newFrame < 0) newFrame = 0;
frame_id = uint(newFrame);
clampCurrentFrame();
adjustForCurrentFrame();
saveFile();
return;
}
if(action == HAT_ACTION_CHAR)
{
saveEdit();
setEditCharacterSlot(parseInt(value));
saveFile();
return;
}
if(action == HAT_ACTION_POS_X) adjX = parseInt(value);
else if(action == HAT_ACTION_POS_Y) adjY = parseInt(value);
else if(action == HAT_ACTION_ROTATION) rot = parseInt(value);
else if(action == HAT_ACTION_SCALE_X) adjScaleX = parseFloat(value);
else if(action == HAT_ACTION_SCALE_Y) adjScaleY = parseFloat(value);
else if(action == HAT_ACTION_ADDITIONAL_HAT)
{
additionalAccessory = parseInt(value);
clampAdditionalAccessory();
}
else return;
if(action == HAT_ACTION_ADDITIONAL_HAT) return;
saveCurrentEditorAdjustment();
}
void beginHatFieldEdit(int action)
{
if(!isHatNumericField(action)) return;
if(hatEditField >= 0 && hatEditField != action)
commitHatFieldEdit();
hatEditField = action;
hatEditValue = hatFieldValueText(action);
hatEditFresh = true;
}
void commitHatFieldEdit()
{
if(hatEditField < 0) return;
setHatFieldValue(hatEditField, hatEditValue);
hatEditField = -1;
hatEditValue = "";
hatEditFresh = false;
}
void cancelHatFieldEdit()
{
hatEditField = -1;
hatEditValue = "";
hatEditFresh = false;
}
bool hatTextHasDot()
{
for(uint i = 0; i < hatEditValue.length; i++)
if(hatEditValue.substr(i, 1) == ".") return true;
return false;
}
void appendHatEditChar(const string &in value)
{
if(hatEditFresh)
{
hatEditValue = "";
hatEditFresh = false;
}
if(value == "-")
{
if(int(hatEditValue.length) > 0 && hatEditValue.substr(0, 1) == "-")
hatEditValue = hatEditValue.substr(1);
else
hatEditValue = "-" + hatEditValue;
return;
}
if(value == ".")
{
if(!isHatFloatField(hatEditField) || hatTextHasDot()) return;
}
hatEditValue += value;
}
string hatTextWithEditValue(const string &in text, int action)
{
if(hatEditField != action) return text;
int colon = int(text.findFirst(":"));
if(colon >= 0) return text.substr(0, colon + 2) + hatEditValue;
return hatEditValue;
}
string additionalAccessoryLabel()
{
if(editCategory == AccessoryCategory::SCARFS) return "Additional Scarfs: " + additionalAccessory;
if(editCategory == AccessoryCategory::GLASSES) return "Additional Glasses: " + additionalAccessory;
return "Additional Hats: " + additionalAccessory;
}
void drawHatStepper(jjCANVAS@ canvas, int x, int y, int textW, int action, int delta, const string &in text, bool selected = false)
{
int actualTextW = textW;
string shownText = hatTextWithEditValue(text, action);
int measuredTextW = jjGetStringWidth(shownText, STRING::SMALL, STRING::NORMAL) + 8;
if(measuredTextW > actualTextW)
actualTextW = measuredTextW;
DbDrawButton(canvas, @hatEditorWindow, x, y, 16, 16, action, 0, -delta, "<");
if(isHatNumericField(action))
canvas.drawRectangle(x + 18, y - 11, actualTextW, 17, hatEditField == action ? 80 : 0, SPRITE::TRANSLUCENT);
if(selected || hatEditField == action)
canvas.drawString(x + 20, y, shownText, STRING::SMALL, STRING::PALSHIFT, hatEditField == action ? 80 : 24);
else
canvas.drawString(x + 20, y, shownText, STRING::SMALL, STRING::NORMAL);
if(isHatNumericField(action))
{
DbAddControl(@hatEditorWindow, x + 18, y, actualTextW, 16, HAT_ACTION_EDIT_FIELD, 0, action, shownText);
}
else
DbAddControl(@hatEditorWindow, x + 18, y, actualTextW, 16, action, 0, delta, shownText);
DbDrawButton(canvas, @hatEditorWindow, x + 22 + actualTextW, y, 16, 16, action, 0, delta, ">");
}
int hatStepperWidth(int textW, const string &in text)
{
int actualTextW = textW;
int measuredTextW = jjGetStringWidth(text, STRING::SMALL, STRING::NORMAL) + 8;
if(measuredTextW > actualTextW)
actualTextW = measuredTextW;
return actualTextW + 38;
}
void applyHatEditorAction(int action, int delta)
{
if(hatEditField >= 0 && action != HAT_ACTION_EDIT_FIELD)
commitHatFieldEdit();
if(action == HAT_ACTION_INCREMENT)
{
increment = increment == 1 ? 10 : 1;
return;
}
if(action == HAT_ACTION_ROT_INCREMENT)
{
rotationIncrement = rotationIncrement == 1 ? 10 : 1;
return;
}
if(action == HAT_ACTION_CATEGORY)
{
changeEditorCategory(delta);
clampAdditionalAccessory();
return;
}
if(action == HAT_ACTION_ANIM)
{
choice = 1;
changeEditorAnim(delta);
return;
}
if(action == HAT_ACTION_FRAME)
{
choice = 0;
changeEditorFrame(delta);
return;
}
if(action == HAT_ACTION_CHAR)
{
changeEditorCharSlot(delta);
return;
}
if(action == HAT_ACTION_POS_X)
{
adjX += delta * increment;
saveCurrentEditorAdjustment();
return;
}
if(action == HAT_ACTION_POS_Y)
{
adjY += delta * increment;
saveCurrentEditorAdjustment();
return;
}
if(action == HAT_ACTION_ROTATION)
{
rot += delta * rotationIncrement;
saveCurrentEditorAdjustment();
return;
}
if(action == HAT_ACTION_SCALE_X)
{
adjScaleX += delta * (increment == 1 ? 0.1 : 1);
saveCurrentEditorAdjustment();
return;
}
if(action == HAT_ACTION_SCALE_Y)
{
adjScaleY += delta * (increment == 1 ? 0.1 : 1);
saveCurrentEditorAdjustment();
return;
}
if(action == HAT_ACTION_MOUSE)
{
hatMouseMode = !hatMouseMode;
return;
}
if(action == HAT_ACTION_ADDITIONAL_HAT)
{
additionalAccessory += delta;
clampAdditionalAccessory();
return;
}
if(action == HAT_ACTION_EDIT_FIELD)
{
beginHatFieldEdit(delta);
}
}
int currentHatEditKey()
{
for(int key = 0x30; key <= 0x39; key++)
if(jjKey[key]) return key;
for(int key = 0x60; key <= 0x69; key++)
if(jjKey[key]) return key;
if(jjKey[0xBD] || jjKey[0x6D]) return 0xBD;
if(jjKey[0xBE] || jjKey[0x6E]) return 0xBE;
if(jjKey[0x08]) return 0x08;
if(jjKey[0x0D]) return 0x0D;
if(jjKey[0x1B]) return 0x1B;
return -1;
}
bool processHatTextInput()
{
if(hatEditField < 0) return false;
int key = currentHatEditKey();
if(key < 0)
{
hatEditLastKey = -1;
return true;
}
if(key == hatEditLastKey) return true;
hatEditLastKey = key;
if(key >= 0x30 && key <= 0x39)
appendHatEditChar("" + (key - 0x30));
else if(key >= 0x60 && key <= 0x69)
appendHatEditChar("" + (key - 0x60));
else if(key == 0xBD)
appendHatEditChar("-");
else if(key == 0xBE)
appendHatEditChar(".");
else if(key == 0x08)
{
if(hatEditFresh)
{
hatEditValue = "";
hatEditFresh = false;
}
else if(int(hatEditValue.length) > 0)
hatEditValue = hatEditValue.substr(0, int(hatEditValue.length) - 1);
}
else if(key == 0x0D)
commitHatFieldEdit();
else if(key == 0x1B)
cancelHatFieldEdit();
return true;
}
void drawHatEditorPanel(jjPLAYER@ p, jjCANVAS@ canvas)
{
DbBegin(@hatEditorWindow, "charsNewHatEditor", "Hat Editor", jjResolutionWidth - 432, 72, 422, 280);
hatEditorWindow.draggable = true;
hatEditorWindow.closeButton = true;
hatEditorWindow.titleBar = true;
hatEditorWindow.backgroundColor = 32;
hatEditorWindow.titleColor = 24;
hatEditorWindow.focusColor = 15;
DbDrawFrame(canvas, @hatEditorWindow);
int x = hatEditorWindow.x + 10;
int y = hatEditorWindow.y + 32;
drawHatStepper(canvas, x, y, 110, HAT_ACTION_CATEGORY, 1, "Category: " + currentCategoryName());
y += 20;
drawHatStepper(canvas, x, y, 96, HAT_ACTION_INCREMENT, 1, "Increment: " + increment);
drawHatStepper(canvas, x + 180, y, 86, HAT_ACTION_ROT_INCREMENT, 1, "Rot Inc: " + rotationIncrement);
y += 20;
drawHatStepper(canvas, x, y, 90, HAT_ACTION_ANIM, 1, "Anim ID: " + anim_id, choice == 1);
drawHatStepper(canvas, x + 205, y, 100, HAT_ACTION_FRAME, 1, "Frame ID: " + frame_id, choice == 0);
y += 20;
drawHatStepper(canvas, x, y, 110, HAT_ACTION_CHAR, 1, "Char slot: " + selectedCharacterSlot);
y += 22;
canvas.drawString(x, y, "Position:", STRING::SMALL, STRING::NORMAL);
y += 17;
string posXText = "X: " + adjX;
drawHatStepper(canvas, x, y, 70, HAT_ACTION_POS_X, 1, posXText);
drawHatStepper(canvas, x + hatStepperWidth(70, posXText) + 14, y, 78, HAT_ACTION_POS_Y, 1, "Y: " + adjY);
y += 22;
canvas.drawString(x, y, "Rotation:", STRING::SMALL, STRING::NORMAL);
y += 17;
drawHatStepper(canvas, x, y, 70, HAT_ACTION_ROTATION, 1, "" + rot);
y += 22;
canvas.drawString(x, y, "Size:", STRING::SMALL, STRING::NORMAL);
y += 17;
string scaleXText = "X: " + formatHatScale(adjScaleX);
drawHatStepper(canvas, x, y, 82, HAT_ACTION_SCALE_X, 1, scaleXText);
drawHatStepper(canvas, x + hatStepperWidth(82, scaleXText) + 14, y, 82, HAT_ACTION_SCALE_Y, 1, "Y: " + formatHatScale(adjScaleY));
y += 24;
DbDrawButton(canvas, @hatEditorWindow, x, y, 112, 16, HAT_ACTION_MOUSE, 0, 0, "Mouse: " + (hatMouseMode ? "ON" : "OFF"), true, hatMouseMode);
drawHatStepper(canvas, x + 132, y, 150, HAT_ACTION_ADDITIONAL_HAT, 1, additionalAccessoryLabel());
DbResetResult(@hatEditorResult);
DbProcessMouse(@hatEditorWindow, @hatEditorResult);
if(hatEditorResult.closeRequested)
{
flushCurrentEditorAdjustment();
EDITING_MODE = false;
}
else if(hatEditorResult.activated)
applyHatEditorAction(hatEditorResult.action, hatEditorResult.b);
}
void drawHatInstructionsBox(jjCANVAS@ canvas)
{
if(!SHOW_HAT_INSTRUCTIONS) return;
array<string> lines = {
"!hatInstructions on/off toggles this help",
"Up/Down: next/previous frame or anim",
"W/A/S/D: move accessory, Q/E: rotate",
"Mouse ON: click preview to place",
"Jump: toggle increment 1/10, Fire: copy previous frame",
"!hatBase jazz/spaz/lori/frog edits base chars",
"!hatChar id/name edits slot, e.g. !hatChar mario, witch, eggman",
"!hatGroup hat/bowtie/glasses sets accessory type",
"!hatColorOffsetRed/Y/Bk/B/G, !scarfColorOffset, !glassesColorOffset"
};
int x = 5;
int y = 392;
int w = 0;
for(uint i = 0; i < lines.length; i++){
int lineW = jjGetStringWidth(lines[i], STRING::SMALL, STRING::NORMAL);
if(lineW > w) w = lineW;
}
w += 24;
if(w > jjResolutionWidth - 10)
w = jjResolutionWidth - 10;
int h = 20 + int(lines.length) * 18;
canvas.drawRectangle(x, y, w, h, 0, SPRITE::TRANSLUCENT);
canvas.drawRectangle(x, y, w, h, 0, SPRITE::SHADOW);
int lineY = y + 12;
for(uint i = 0; i < lines.length; i++){
canvas.drawString(x + 8, lineY, lines[i], STRING::SMALL, STRING::NORMAL);
lineY += 18;
}
}
bool hatMouseOverEditor()
{
return DbPointIn(jjMouseX, jjMouseY + 8, hatEditorWindow.x, hatEditorWindow.y, hatEditorWindow.w, hatEditorWindow.h);
}
void handleHatMouseEditing(jjPLAYER@ play)
{
if(!hatMouseMode)
{
hatMouseWasDown = false;
return;
}
[preview ends here]
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.