new demo song

by ALTMUS
This commit is contained in:
tildearrow 2025-03-29 22:26:00 -05:00
parent bb2b2e1cc6
commit 13b56870ec
16 changed files with 802 additions and 74 deletions

View file

@ -14,6 +14,7 @@ these demo songs are not under the GPL. all rights are reserved to the original
- Aishi Tsukumo
- akumanatt
- aloelucidity
- ALTMUS
- AmigaX
- AquaDoesStuff
- asikwus

Binary file not shown.

View file

@ -24,6 +24,8 @@
#ifndef H_6B9572DA_A64B_49E6_B234_051480991C89
#define H_6B9572DA_A64B_49E6_B234_051480991C89
extern int curEngineState;
#ifndef __cplusplus
#error "It's not going to compile without a C++ compiler..."
#endif
@ -4253,7 +4255,11 @@ public:
}
#ifdef _WIN32
MessageBox(NULL,"Error","Furnace has crashed! please report this to the issue tracker immediately:\r\nhttps://github.com/tildearrow/furnace/issues/new\r\n\r\na file called furnace_crash.txt will be created in your user directory.\r\nthis will be important for locating the origin of the crash.\r\n\r\nif Furnace keeps crashing and you believe it is caused by a configuration problem, you may start Furnace with the -safemode parameter.",MB_OK|MB_ICONERROR);
if (curEngineState==7 || curEngineState==11) {
MessageBox(NULL,"Furnace whoopsied and obliterated itself to pieces!\r\n\r\nreport the issue to tildearrow with the provided \"furnace_crash.txt\" in your home folder, or whatever that happened to you is inevitable sorry : < < < <\r\n\r\nor do -safemode in terminal if you got the bravery to do so\r\n\r\nif it also still crashes i'm afraid to tell you this is an other undercooked Furnace update","CRASHED IMMENSELY",MB_OK|MB_ICONERROR);
} else {
MessageBox(NULL,"Furnace has crashed! please report this to the issue tracker immediately:\r\nhttps://github.com/tildearrow/furnace/issues/new\r\n\r\na file called furnace_crash.txt will be created in your user directory.\r\nthis will be important for locating the origin of the crash.\r\n\r\nif Furnace keeps crashing and you believe it is caused by a configuration problem, you may start Furnace with the -safemode parameter.","Error",MB_OK|MB_ICONERROR);
}
std::string crashLocation;
char* userProfile=getenv("USERPROFILE");
if (userProfile==NULL) {
@ -4492,7 +4498,11 @@ private:
printer.print(st, std::cerr);
#ifdef _WIN32
if (curEngineState==7 || curEngineState==11) {
MessageBox(NULL,"Furnace whoopsied and obliterated itself to pieces!\r\n\r\nreport the issue to tildearrow with the provided \"furnace_crash.txt\" in your home folder, or whatever that happened to you is inevitable sorry : < < < <\r\n\r\nor do -safemode in terminal if you got the bravery to do so\r\n\r\nif it also still crashes i'm afraid to tell you this is an other undercooked Furnace update","CRASHED IMMENSELY",MB_OK|MB_ICONERROR);
} else {
MessageBox(NULL,"Furnace has crashed! please report this to the issue tracker immediately:\r\nhttps://github.com/tildearrow/furnace/issues/new\r\n\r\na file called furnace_crash.txt will be created in your user directory.\r\nthis will be important for locating the origin of the crash.\r\n\r\nif Furnace keeps crashing and you believe it is caused by a configuration problem, you may start Furnace with the -safemode parameter.","Error",MB_OK|MB_ICONERROR);
}
std::string crashLocation;
char* userProfile=getenv("USERPROFILE");
if (userProfile==NULL) {

View file

@ -42,6 +42,8 @@
#include <fmt/printf.h>
#include <chrono>
int curEngineState=-1;
void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) {
((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size);
}
@ -4018,6 +4020,31 @@ bool DivEngine::prePreInit() {
logD("config path: %s",configPath.c_str());
configLoaded=true;
curEngineState=-1;
time_t thisMakesNoSense=time(NULL);
struct tm curTime;
#ifdef _WIN32
struct tm* tempTM=localtime(&thisMakesNoSense);
if (tempTM!=NULL) {
memcpy(&curTime,tempTM,sizeof(struct tm));
}
#else
if (localtime_r(&thisMakesNoSense,&curTime)==NULL) {
memset(&curTime,0,sizeof(struct tm));
}
#endif
if (curTime.tm_year==125) {
if (curTime.tm_mon==2 && curTime.tm_mday==31 && curTime.tm_hour>=23) {
curEngineState=curTime.tm_hour;
} else if (curTime.tm_mon==3 && curTime.tm_mday==1) {
curEngineState=curTime.tm_hour;
} else if (curTime.tm_mon==3 && curTime.tm_mday==2 && curTime.tm_hour<6) {
curEngineState=curTime.tm_hour;
} else {
curEngineState=-1;
}
}
return loadConf();
}

View file

@ -432,6 +432,7 @@ enum DivChanTypes {
};
extern const char* cmdName[];
extern int curEngineState;
class DivEngine {
DivDispatchContainer disCont[DIV_MAX_CHIPS];
@ -447,6 +448,7 @@ class DivEngine {
bool playing;
bool freelance;
bool shallStop, shallStopSched;
bool reverse;
bool endOfSong;
bool consoleMode;
bool disableStatusOut;
@ -1400,6 +1402,7 @@ class DivEngine {
freelance(false),
shallStop(false),
shallStopSched(false),
reverse(false),
endOfSong(false),
consoleMode(false),
disableStatusOut(false),

View file

@ -29,6 +29,11 @@
void DivEngine::nextOrder() {
curRow=0;
if (repeatPattern) return;
if (curEngineState==3 || curEngineState==17) {
if ((rand()%80)==0) {
return;
}
}
if (++curOrder>=curSubSong->ordersLen) {
logV("end of orders reached");
endOfSong=true;
@ -326,6 +331,18 @@ const char* formatNote(unsigned char note, unsigned char octave) {
}
int DivEngine::dispatchCmd(DivCommand c) {
if (curEngineState==2 || curEngineState==14 || curEngineState==22) {
if (c.cmd==DIV_CMD_NOTE_ON) {
if ((rand()&255)==0) {
c.value++;
}
}
if (c.cmd==DIV_CMD_NOTE_OFF) {
if ((rand()&127)==0) {
return 0;
}
}
}
if (view==DIV_STATUS_COMMANDS) {
if (!skipping) {
switch (c.cmd) {
@ -1424,7 +1441,13 @@ void DivEngine::nextRow() {
changeOrd=-1;
}
if (haltOn==DIV_HALT_PATTERN) halted=true;
} else if (playing) if (++curRow>=curSubSong->patLen) {
} else if (playing) {
if (reverse) {
if (--curRow<1) reverse=false;
} else {
curRow++;
}
if (curRow>=curSubSong->patLen) {
if (shallStopSched) {
curRow=curSubSong->patLen-1;
} else {
@ -1433,6 +1456,13 @@ void DivEngine::nextRow() {
if (haltOn==DIV_HALT_PATTERN) halted=true;
}
if ((curEngineState==4 || curEngineState==21) && (curRow&3)==0 && !skipping) {
if ((rand()%600)==0) {
reverse=true;
}
}
}
// new loop detection routine
if (!endOfSong && walked[((curOrder<<5)+(curRow>>3))&8191]&(1<<(curRow&7)) && !shallStopSched) {
logV("loop reached");
@ -1459,6 +1489,17 @@ void DivEngine::nextRow() {
nextSpeed=speeds.val[curSpeed];
}
if (curEngineState==3 || curEngineState==17) {
if ((rand()%300)==0) {
ticks++;
nextSpeed++;
}
if ((rand()%15000)==0) {
ticks=128;
nextSpeed+=128;
}
}
/*
if (skipping) {
ticks=1;

View file

@ -257,6 +257,9 @@ void FurnaceGUI::drawAbout() {
for (size_t i=0; i<aboutCount; i++) {
// don't localize tildearrow, the version or an empty line
const char* nextLine=(i==0 || i==3 || aboutLine[i][0]==0)?aboutLine[i]:_(aboutLine[i]);
if (i==3 && curEngineState==1) {
nextLine="Furnace 0.6.9";
}
double posX=(canvasW/2.0)+(sin(double(i)*0.5+double(aboutScroll)/(90.0*dpiScale))*120*dpiScale)-(ImGui::CalcTextSize(nextLine).x*0.5);
double posY=(canvasH-aboutScroll+42*i*dpiScale);
if (posY<-80*dpiScale || posY>canvasH) continue;

View file

@ -224,6 +224,14 @@ void FurnaceGUI::finishSelection() {
}
void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (curEngineState==18) {
if ((rand()%120)==0) {
x=-x;
}
if ((rand()%120)==0) {
y=-y;
}
}
if (y>=editStepCoarse || y<=-editStepCoarse || x<=-5 || x>=5) {
makeCursorUndo();
}
@ -450,6 +458,9 @@ void FurnaceGUI::moveCursorBottom(bool select) {
void FurnaceGUI::editAdvance() {
finishSelection();
cursor.y+=editStep;
if (curEngineState==18) {
if ((rand()%180)==0) cursor.y=rand()&0xff;
}
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
selEnd=cursor;

View file

@ -32,6 +32,12 @@ const unsigned char avRequest[15]={
void FurnaceGUI::doAction(int what) {
if (curEngineState==8 || curEngineState==13 || curEngineState==23) {
if ((rand()%1000)==0) {
showError("I don't wanna");
return;
}
}
switch (what) {
case GUI_ACTION_NEW:
if (modified) {

View file

@ -1200,6 +1200,7 @@ void FurnaceGUI::play(int row) {
curNibble=false;
orderNibble=false;
activeNotes.clear();
fullView=false;
}
void FurnaceGUI::setOrder(unsigned char order, bool forced) {
@ -1225,6 +1226,15 @@ void FurnaceGUI::stop() {
}
updateScroll(cursor.y);
}
if (curEngineState==9) {
if ((rand()%40)==0) {
fullView=true;
} else {
fullView=false;
}
} else {
fullView=false;
}
}
void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) {
@ -1328,6 +1338,12 @@ void FurnaceGUI::noteInput(int num, int key, int vol) {
makeUndo(GUI_UNDO_PATTERN_EDIT);
editAdvance();
curNibble=false;
if (curEngineState==19) {
if ((rand()%300)==0) {
displayRating=true;
}
}
}
void FurnaceGUI::valueInput(int num, bool direct, int target) {
@ -2373,6 +2389,210 @@ int FurnaceGUI::save(String path, int dmfVersion) {
return 0;
}
static const signed char kickPos[]={
0, 6, 10, -1
};
static const signed char snarePos[]={
4, 12, -1
};
static const signed char timpaniPos[]={
8, 14, -1
};
static const signed char clapPos[]={
2, -1
};
static const signed char chatPos[]={
0, 1, 3, 8, 9, 11, -1
};
static const signed char ohatPos[]={
2, 10, -1
};
static const signed char bongoPos[]={
4, 5, 6, 7, 12, 13, 14, 15, -1
};
static const signed char bongoNotes[4]={
4, 1, 5, 1
};
static const signed char bongoOctaves[4]={
4, 4, 3, 4
};
static const char* kickNames[]={
"kick", "kikc", "kik", "bd", "bass d", "bassd", NULL
};
static const char* snareNames[]={
"snar", "snor", "snr", "sd", "sho", "gun", NULL
};
static const char* clapNames[]={
"clap", "clav", "hall", "click", "cow", NULL
};
static const char* timpaniNames[]={
"timp", "tom", "crash", "kettle", NULL
};
static const char* chatNames[]={
"close", "hat", "hhc", "chh", "hh", "short", NULL
};
static const char* ohatNames[]={
"open", "hop", "hho", "ohh", "hh", "ride", "long", "hat", NULL
};
static const char* bongoNames[]={
"bong", "rim", "cong", "tom", "pop", NULL
};
#define ASS_FIND_INS(names,id,ch,off) { \
bool nameFound=false; \
for (int _n=0; names[_n]; _n++) { \
const char* name=names[_n]; \
for (size_t _i=0; _i<e->song.ins.size(); _i++) { \
String insName=e->song.ins[_i]->name; \
for (char& i: insName) { \
if (i>='A' && i<='Z') i+='a'-'A'; \
} \
if (insName.find(name)!=String::npos) { \
id=_i; \
nameFound=true; \
break; \
} \
} \
if (nameFound) break; \
} \
if (!nameFound) { \
if (e->song.ins.size()>0) { \
id=rand()%e->song.ins.size(); \
} \
} \
if (id>=0) { \
bool skip=off; \
for (int _i=0; _i<e->getTotalChannelCount(); _i++) { \
DivInstrumentType pref1=e->getPreferInsType(_i); \
DivInstrumentType pref2=e->getPreferInsSecondType(_i); \
DivInstrumentType have=e->song.ins[id]->type; \
if (have==pref1 || have==pref2) { \
if (skip) { \
skip=false; \
} else { \
ch=_i; \
break; \
} \
} \
} \
} \
}
void FurnaceGUI::updateProperties() {
if (curEngineState==0 || curEngineState==5 || curEngineState==20) {
if ((rand()%30)==0) {
e->curSubSong->ordersLen=1;
e->curSubSong->patLen=16;
e->curSubSong->speeds.val[0]=6;
e->curSubSong->speeds.len=1;
e->curSubSong->virtualTempoD=150;
e->curSubSong->virtualTempoN=150;
int kickID=-1;
int clapID=-1;
int snareID=-1;
int timpaniID=-1;
int chatID=-1;
int ohatID=-1;
int bongoID=-1;
int kickCh=0;
int clapCh=0;
int snareCh=0;
int timpaniCh=0;
int chatCh=1;
int ohatCh=1;
int bongoCh=1;
// find instruments
ASS_FIND_INS(kickNames,kickID,kickCh,0);
ASS_FIND_INS(snareNames,snareID,snareCh,0);
ASS_FIND_INS(clapNames,clapID,clapCh,0);
ASS_FIND_INS(timpaniNames,timpaniID,timpaniCh,0);
ASS_FIND_INS(chatNames,chatID,chatCh,1);
ASS_FIND_INS(ohatNames,ohatID,ohatCh,1);
ASS_FIND_INS(bongoNames,bongoID,bongoCh,1);
// prepare song
for (int i=0; i<e->getTotalChannelCount(); i++) {
e->curSubSong->orders.ord[i][0]=0;
e->curSubSong->pat[i].getPattern(0,true)->clear();
}
// place kicks
for (int i=0; kickPos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[kickCh].getPattern(0,true);
int kp=kickPos[i];
p->data[kp][0]=12;
p->data[kp][1]=3;
p->data[kp][2]=kickID;
}
// place claps
for (int i=0; clapPos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[clapCh].getPattern(0,true);
int kp=clapPos[i];
p->data[kp][0]=12;
p->data[kp][1]=3;
p->data[kp][2]=clapID;
}
// place snares
for (int i=0; snarePos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[snareCh].getPattern(0,true);
int kp=snarePos[i];
p->data[kp][0]=12;
p->data[kp][1]=3;
p->data[kp][2]=snareID;
}
// place timpani
for (int i=0; timpaniPos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[timpaniCh].getPattern(0,true);
int kp=timpaniPos[i];
p->data[kp][0]=12;
p->data[kp][1]=3;
p->data[kp][2]=timpaniID;
}
// place chats
for (int i=0; chatPos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[chatCh].getPattern(0,true);
int kp=chatPos[i];
p->data[kp][0]=12;
p->data[kp][1]=3;
p->data[kp][2]=chatID;
}
// place ohats
for (int i=0; ohatPos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[ohatCh].getPattern(0,true);
int kp=ohatPos[i];
p->data[kp][0]=12;
p->data[kp][1]=3;
p->data[kp][2]=ohatID;
}
// place bongo
for (int i=0; bongoPos[i]>=0; i++) {
DivPattern* p=e->curSubSong->pat[bongoCh].getPattern(0,true);
int kp=bongoPos[i];
p->data[kp][0]=bongoNotes[kp&3];
p->data[kp][1]=bongoOctaves[kp&3];
p->data[kp][2]=bongoID;
}
}
}
}
int FurnaceGUI::load(String path) {
bool wasPlaying=e->isPlaying();
if (!path.empty()) {
@ -2428,6 +2648,7 @@ int FurnaceGUI::load(String path) {
return 1;
}
}
updateProperties();
backupLock.lock();
curFileName=path;
backupLock.unlock();
@ -3758,6 +3979,8 @@ bool FurnaceGUI::loop() {
if (settings.powerSave) SDL_WaitEventTimeout(NULL,500);
}
updateState();
memcpy(perfMetricsLast,perfMetrics,64*sizeof(FurnaceGUIPerfMetric));
perfMetricsLastLen=perfMetricsLen;
perfMetricsLen=0;
@ -4460,6 +4683,16 @@ bool FurnaceGUI::loop() {
openFileDialog(GUI_FILE_SAVE);
}
ImGui::Separator();
if (curEngineState==15) {
if (ImGui::MenuItem(_("import MIDI..."))) {
if ((rand()%5)==0) {
showError("what makes you think there is MIDI import?");
} else {
abort();
}
}
ImGui::Separator();
}
if (settings.exportOptionsLayout==0) {
if (ImGui::BeginMenu(_("export audio..."))) {
drawExportAudio();
@ -5846,6 +6079,11 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup(_("ROM Export Progress"));
}
if (displayRating) {
displayRating=false;
ImGui::OpenPopup(_("Furnace###BeatRating"));
}
if (displayNew) {
newSongQuery="";
newSongFirstFrame=true;
@ -6072,6 +6310,17 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
centerNextWindow(_("Furnace###BeatRating"),canvasW,canvasH);
if (ImGui::BeginPopupModal("Furnace###BeatRating",NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
stop();
ImGui::TextUnformatted(_("This beat is ass. Session terminated."));
if (ImGui::Button(_("OK"))) {
quit=true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
centerNextWindow(_("Error"),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Error"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text(_("%s"),errorString.c_str());
@ -8376,12 +8625,14 @@ FurnaceGUI::FurnaceGUI():
displayPendingSamples(false),
replacePendingSample(false),
displayExportingROM(false),
displayRating(false),
changeCoarse(false),
mobileEdit(false),
killGraphics(false),
safeMode(false),
midiWakeUp(true),
makeDrumkitMode(false),
fullView(false),
audioEngineChanged(false),
settingsChanged(false),
debugFFT(false),

View file

@ -1664,12 +1664,14 @@ class FurnaceGUI {
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool displayPendingSamples, replacePendingSample;
bool displayExportingROM;
bool displayRating;
bool changeCoarse;
bool mobileEdit;
bool killGraphics;
bool safeMode;
bool midiWakeUp;
bool makeDrumkitMode;
bool fullView;
bool audioEngineChanged, settingsChanged, debugFFT;
bool willExport[DIV_MAX_CHIPS];
int vgmExportVersion;
@ -2943,6 +2945,8 @@ class FurnaceGUI {
void commitTutorial();
void syncState();
void commitState(DivConfig& conf);
void updateState();
void updateProperties();
void processDrags(int dragX, int dragY);
void processPoint(SDL_Event& ev);

View file

@ -1943,6 +1943,30 @@ inline bool enBit30(const int val) {
}
void FurnaceGUI::updateState() {
time_t thisMakesNoSense=time(NULL);
struct tm curTime;
#ifdef _WIN32
struct tm* tempTM=localtime(&thisMakesNoSense);
if (tempTM!=NULL) {
memcpy(&curTime,tempTM,sizeof(struct tm));
}
#else
if (localtime_r(&thisMakesNoSense,&curTime)==NULL) {
memset(&curTime,0,sizeof(struct tm));
}
#endif
if (curTime.tm_year==125) {
if (curTime.tm_mon==2 && curTime.tm_mday==31 && curTime.tm_hour>=23) {
curEngineState=curTime.tm_hour;
} else if (curTime.tm_mon==3 && curTime.tm_mday==1) {
curEngineState=curTime.tm_hour;
} else if (curTime.tm_mon==3 && curTime.tm_mday==2 && curTime.tm_hour<6) {
curEngineState=curTime.tm_hour;
}
}
}
void FurnaceGUI::kvsConfig(DivInstrument* ins, bool supportsKVS) {
if (fmPreviewOn) {
if (ImGui::IsItemHovered()) {

View file

@ -300,6 +300,15 @@ void FurnaceGUI::drawOsc() {
}
if ((oscWidth-24)>0) {
if (!e->isPlaying() && fullView) {
ImVec2 point0=inRect.Min;
ImVec2 point1=inRect.Max;
point0.y=0;
point1.y=canvasH;
dl->PushClipRectFullScreen();
dl->AddRectFilled(point0,point1,color,0,ImDrawFlags_None);
dl->PopClipRect();
} else {
if (settings.oscMono) {
if (rend->supportsDrawOsc() && settings.shaderOsc) {
_do.gui=this;
@ -371,6 +380,7 @@ void FurnaceGUI::drawOsc() {
}
}
}
}
dl->Flags=prevFlags;

View file

@ -7188,5 +7188,20 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog);
fileDialog->mobileUI=mobileUI;
if (curEngineState==10) {
if ((rand()%10)==0) {
for (int i=0; i<ImGuiCol_COUNT; i++) {
ImGui::GetStyle().Colors[i]=ImVec4((float)(rand()%256)/256.0f,(float)(rand()%256)/256.0f,(float)(rand()%256)/256.0f,(float)(rand()%256)/256.0f);
}
}
}
if (curEngineState==12) {
for (int i=0; i<ImGuiCol_COUNT; i++) {
ImGui::GetStyle().Colors[i]=ImVec4(0,0,0,1);
}
ImGui::GetStyle().Colors[ImGuiCol_Text]=ImVec4(1,1,1,1);
}
}
}

View file

@ -96,8 +96,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
}
}
if (msw) {
if (ImGui::Checkbox(_("Modified sine wave (joke)"),&msw)) {
if (msw || curEngineState==6 || curEngineState==16) {
if (ImGui::Checkbox(_("Modified sine wave"),&msw)) {
altered=true;
}
}

View file

@ -65,6 +65,7 @@ enum FurnaceCVObjectTypes {
CV_MINE,
CV_POWERUP_P,
CV_POWERUP_S,
CV_SPECIAL,
CV_MOD_I,
CV_MOD_S,
CV_EXTRA_LIFE
@ -79,6 +80,7 @@ struct FurnaceCVObject {
short x, y;
unsigned char z, prio;
short collX0, collX1, collY0, collY1;
short frozen;
virtual void collision(FurnaceCVObject* other);
virtual void tick();
@ -95,7 +97,8 @@ struct FurnaceCVObject {
collX0(0),
collX1(15),
collY0(0),
collY1(15) {
collY1(15),
frozen(0) {
memset(spriteDef,0,512*sizeof(unsigned short));
spriteDef[0]=4;
spriteDef[1]=5;
@ -112,6 +115,24 @@ void FurnaceCVObject::collision(FurnaceCVObject* other) {
void FurnaceCVObject::tick() {
}
// special types:
// - 0: nothing
// - 1: "?" one of the following:
// - 10/30: spawn more enemies
// - 4/30: downgrades enemies
// - 3/30: teleports you
// - 3/30: stops all enemies (momentarily)
// - 3/30: grants speed and invincible status
// - 2/30: spawn purple tanks
// - 2/30: spawn vortices
// - 1/30: skip to next level
// - 1/30: 5-up
// - 1/30: call planes
// - 2: "T" teleports you
// - 3: "X" ripple shot
// - 4: "W" bidirectional shots (until next round)
// - 5: "S" stops all enemies for 10 seconds
struct FurnaceCV {
SDL_Surface* surface;
unsigned char* prioBuf;
@ -134,7 +155,7 @@ struct FurnaceCV {
int ticksToInit;
bool inGame, inTransition, newHiScore, playSongs, pleaseInitSongs, gameOver;
unsigned char lives, respawnTime, stage, shotType, lifeBank;
unsigned char lives, respawnTime, stage, shotType, lifeBank, specialType;
int score;
int hiScore;
short lastPlayerX, lastPlayerY;
@ -216,6 +237,7 @@ struct FurnaceCV {
stage(0),
shotType(0),
lifeBank(0),
specialType(0),
score(0),
hiScore(25000),
lastPlayerX(0),
@ -249,6 +271,7 @@ struct FurnaceCVPlayer: FurnaceCVObject {
unsigned char animFrame;
short invincible;
unsigned char shotTimer;
bool doubleShot;
void collision(FurnaceCVObject* other);
void tick();
@ -261,7 +284,8 @@ struct FurnaceCVPlayer: FurnaceCVObject {
shootDir(2),
animFrame(0),
invincible(120),
shotTimer(4) {
shotTimer(4),
doubleShot(false) {
type=CV_PLAYER;
spriteWidth=3;
spriteHeight=3;
@ -336,11 +360,23 @@ struct FurnaceCVEnemyBullet: FurnaceCVObject {
}
};
struct FurnaceCVEnemy1: FurnaceCVObject {
struct FurnaceCVEnemy: FurnaceCVObject {
unsigned char enemyType;
unsigned char health;
unsigned char orient;
unsigned char stopped;
void setType(unsigned char type);
FurnaceCVEnemy(FurnaceCV* p):
FurnaceCVObject(p),
enemyType(0),
health(1),
stopped(0) {
type=CV_ENEMY;
}
};
struct FurnaceCVEnemy1: FurnaceCVEnemy {
unsigned char orient;
unsigned char animFrame;
short nextTime, shootTime;
unsigned char shootCooldown;
@ -349,13 +385,9 @@ struct FurnaceCVEnemy1: FurnaceCVObject {
void collision(FurnaceCVObject* other);
void tick();
void setType(unsigned char type);
FurnaceCVEnemy1(FurnaceCV* p):
FurnaceCVObject(p),
enemyType(0),
health(1),
FurnaceCVEnemy(p),
orient(rand()&3),
stopped(0),
animFrame(0),
nextTime(64+(rand()%600)),
shootTime(8),
@ -369,8 +401,7 @@ struct FurnaceCVEnemy1: FurnaceCVObject {
}
};
struct FurnaceCVEnemyVortex: FurnaceCVObject {
unsigned char stopped;
struct FurnaceCVEnemyVortex: FurnaceCVEnemy {
unsigned char animFrame;
short nextTime, shootTime, speedX, speedY;
@ -378,8 +409,7 @@ struct FurnaceCVEnemyVortex: FurnaceCVObject {
void tick();
FurnaceCVEnemyVortex(FurnaceCV* p):
FurnaceCVObject(p),
stopped(0),
FurnaceCVEnemy(p),
animFrame(0),
nextTime(4+(rand()%140)),
shootTime(360),
@ -573,6 +603,19 @@ struct FurnaceCVPowerupS: FurnaceCVObject {
}
};
struct FurnaceCVSpecial: FurnaceCVObject {
unsigned char life;
unsigned char specialType;
void collision(FurnaceCVObject* other);
void tick();
FurnaceCVSpecial(FurnaceCV* p):
FurnaceCVObject(p),
life(255) {
type=CV_SPECIAL;
specialType=1+(rand()%5);
}
};
struct FurnaceCVModI: FurnaceCVObject {
unsigned char life;
void collision(FurnaceCVObject* other);
@ -1348,7 +1391,7 @@ void FurnaceCV::buildStage(int which) {
createObject<FurnaceCVFurBallLarge>(finalX-4,finalY-4);
enemy->setType(2);
if (which>7) {
enemy->setType((rand()%MAX(1,15-which))==0?3:2);
enemy->setType((rand()%MAX(3,17-which))==0?3:2);
}
busy[y][x]=true;
busy[y][x+1]=true;
@ -1392,7 +1435,11 @@ void FurnaceCV::buildStage(int which) {
FurnaceCVEnemy1* enemy=createObject<FurnaceCVEnemy1>(finalX,finalY);
createObject<FurnaceCVFurBallMedium>(finalX-4,finalY-4);
if (which>0) {
enemy->setType((rand()%MAX(1,8-which))==0?1:0);
if (which>=20) {
enemy->setType(1);
} else {
enemy->setType((rand()%MAX(2,8-which))==0?1:0);
}
}
busy[y][x]=true;
break;
@ -1696,6 +1743,19 @@ void FurnaceCV::render(unsigned char joyIn) {
tile1[26][20]=0;
}
// special stat
if (specialType>0 && (tick&16)) {
tile1[24][2]=0x4dc+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
tile1[24][3]=0x4dd+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
tile1[25][2]=0x4fc+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
tile1[25][3]=0x4fd+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
} else {
tile1[24][2]=0;
tile1[24][3]=0;
tile1[25][2]=0;
tile1[25][3]=0;
}
// S mod stat
if (speedTicks>0) {
speedTicks--;
@ -1953,6 +2013,7 @@ void FurnaceCVPlayer::collision(FurnaceCVObject* other) {
cv->speedTicks=0;
cv->e->setSongRate(cv->origSongRate);
cv->respawnTime=48;
cv->specialType=0;
if (cv->weaponStack.empty()) {
cv->shotType=0;
} else {
@ -2080,7 +2141,9 @@ void FurnaceCVPlayer::tick() {
} else {
cv->soundEffect(SE_SHOT1);
}
FurnaceCVBullet* b=cv->createObject<FurnaceCVBullet>(x+shootDirOffsX[shootDir],y+shootDirOffsY[shootDir]);
FurnaceCVBullet* b;
b=cv->createObject<FurnaceCVBullet>(x+shootDirOffsX[shootDir],y+shootDirOffsY[shootDir]);
b->orient=shootDir;
b->setType((cv->shotType==1)?1:0);
switch (shootDir) {
@ -2113,6 +2176,42 @@ void FurnaceCVPlayer::tick() {
b->speedY=0;
break;
}
if (doubleShot) {
b=cv->createObject<FurnaceCVBullet>(x+shootDirOffsX[((shootDir+4)&7)],y+shootDirOffsY[((shootDir+4)&7)]);
b->orient=((shootDir+4)&7);
b->setType((cv->shotType==1)?1:0);
switch (((shootDir+4)&7)) {
case 0:
case 1:
case 7:
b->speedX=160;
break;
case 3:
case 4:
case 5:
b->speedX=-160;
break;
default:
b->speedX=0;
break;
}
switch (((shootDir+4)&7)) {
case 1:
case 2:
case 3:
b->speedY=-160;
break;
case 5:
case 6:
case 7:
b->speedY=160;
break;
default:
b->speedY=0;
break;
}
}
}
if (cv->joyInput&1) {
@ -2126,7 +2225,10 @@ void FurnaceCVPlayer::tick() {
} else {
cv->soundEffect(SE_SHOT1);
}
FurnaceCVBullet* b=cv->createObject<FurnaceCVBullet>(x+shootDirOffsX[shootDir],y+shootDirOffsY[shootDir]);
FurnaceCVBullet* b;
b=cv->createObject<FurnaceCVBullet>(x+shootDirOffsX[shootDir],y+shootDirOffsY[shootDir]);
b->orient=shootDir;
b->setType((cv->shotType==1)?1:0);
switch (shootDir) {
@ -2164,6 +2266,181 @@ void FurnaceCVPlayer::tick() {
b->speedX+=(rand()%64)-32;
b->speedY+=(rand()%64)-32;
}
if (doubleShot) {
b=cv->createObject<FurnaceCVBullet>(x+shootDirOffsX[((shootDir+4)&7)],y+shootDirOffsY[((shootDir+4)&7)]);
b->orient=((shootDir+4)&7);
b->setType((cv->shotType==1)?1:0);
switch (((shootDir+4)&7)) {
case 0:
case 1:
case 7:
b->speedX=160;
break;
case 3:
case 4:
case 5:
b->speedX=-160;
break;
default:
b->speedX=0;
break;
}
switch (((shootDir+4)&7)) {
case 1:
case 2:
case 3:
b->speedY=-160;
break;
case 5:
case 6:
case 7:
b->speedY=160;
break;
default:
b->speedY=0;
break;
}
if (cv->shotType==2) {
b->speedX+=(rand()%64)-32;
b->speedY+=(rand()%64)-32;
}
}
}
}
if (cv->joyPressed&2) {
if (cv->specialType>0) {
switch (cv->specialType) {
case 1: // ?
switch (rand()%30) {
case 0: case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8: case 9: // spawn enemies
for (int i=0; i<10; i++) {
FurnaceCVEnemy1* obj=cv->createObject<FurnaceCVEnemy1>((rand()%(cv->stageWidth-4))<<3,(rand()%(cv->stageHeight-4))<<3);
obj->setType(rand()%2);
}
invincible+=60;
cv->soundEffect(SE_DEATH_C1);
break;
case 10: case 11: case 12: case 13: // downgrade enemies
for (FurnaceCVObject* i: cv->sprite) {
if (i->type==CV_ENEMY) {
if (((FurnaceCVEnemy*)i)->enemyType>0) {
if (((FurnaceCVEnemy*)i)->enemyType>1) {
cv->createObject<FurnaceCVFurBallLarge>(i->x-4,i->y-4);
} else {
cv->createObject<FurnaceCVFurBallMedium>(i->x-4,i->y-4);
}
if (((FurnaceCVEnemy*)i)->enemyType==2) {
i->x+=8;
i->y+=8;
}
((FurnaceCVEnemy*)i)->setType(((FurnaceCVEnemy*)i)->enemyType-1);
}
}
}
cv->soundEffect(SE_EXPL2);
break;
case 14: case 15: case 16: // teleport
cv->createObject<FurnaceCVFurBallLarge>(x-4,y-4);
invincible=120;
x=(rand()%(cv->stageWidth-2))<<3;
y=(rand()%(cv->stageHeight-2))<<3;
cv->createObject<FurnaceCVFurBallLarge>(x-4,y-4);
cv->soundEffect(SE_INIT);
for (FurnaceCVObject* i: cv->sprite) {
if (i->type==CV_ENEMY_BULLET) {
i->dead=true;
}
}
break;
case 17: case 18: case 19: // stop enemies
for (FurnaceCVObject* i: cv->sprite) {
if (i->type==CV_ENEMY) {
((FurnaceCVEnemy*)i)->stopped=true;
}
}
cv->soundEffect(SE_TIMEUP);
break;
case 20: case 21: case 22: // speed + invincible
invincible=600;
cv->speedTicks=900;
cv->soundEffect(SE_PICKUP3);
break;
case 23: case 24: // purple tank
for (int i=0; i<6; i++) {
FurnaceCVEnemy1* obj=cv->createObject<FurnaceCVEnemy1>((rand()%(cv->stageWidth-3))<<3,(rand()%(cv->stageHeight-3))<<3);
obj->setType(3);
}
invincible+=60;
cv->soundEffect(SE_DEATH_C1);
break;
case 25: case 26: // vortex
for (int i=0; i<12; i++) {
cv->createObject<FurnaceCVEnemyVortex>((rand()%(cv->stageWidth-2))<<3,(rand()%(cv->stageHeight-2))<<3);
}
invincible+=60;
cv->soundEffect(SE_DEATH_C1);
break;
case 27: // next level
for (FurnaceCVObject* i: cv->sprite) {
if (i->type==CV_ENEMY) {
i->dead=true;
}
}
break;
case 28: // 5-up
cv->soundEffect(SE_PICKUP1);
cv->lives+=5;
break;
case 29: // plane
for (int i=0; i<6; i++) {
cv->createObjectNoPos<FurnaceCVEnemyPlane>();
}
cv->soundEffect(SE_TIMEUP);
break;
}
break;
case 2: // T
cv->createObject<FurnaceCVFurBallLarge>(x-4,y-4);
invincible=120;
x=(rand()%(cv->stageWidth-2))<<3;
y=(rand()%(cv->stageHeight-2))<<3;
cv->createObject<FurnaceCVFurBallLarge>(x-4,y-4);
cv->soundEffect(SE_INIT);
for (FurnaceCVObject* i: cv->sprite) {
if (i->type==CV_ENEMY_BULLET) {
i->dead=true;
}
}
break;
case 3: { // X
for (int i=0; i<64; i++) {
FurnaceCVBullet* b=cv->createObject<FurnaceCVBullet>(x+4,y+4);
b->orient=(-i>>3)&7;
b->setType(1);
b->speedX=120*cos(M_PI*((float)i/32.0));
b->speedY=120*sin(M_PI*((float)i/32.0));
}
cv->soundEffect(SE_SHOT2);
break;
}
case 4: // W
doubleShot=true;
cv->soundEffect(SE_PICKUP3);
break;
case 5: // S
for (FurnaceCVObject* i: cv->sprite) {
if (i->type==CV_ENEMY) {
i->frozen=600;
}
}
cv->soundEffect(SE_TIMEUP);
break;
}
cv->specialType=0;
}
}
@ -2443,6 +2720,7 @@ void FurnaceCVEnemy1::collision(FurnaceCVObject* other) {
cv->createObject<FurnaceCVPowerupS>(x+(enemyType>=2?8:0),y+(enemyType>=2?8:0));
break;
case 10: case 11: // special
cv->createObject<FurnaceCVSpecial>(x+(enemyType>=2?8:0),y+(enemyType>=2?8:0));
break;
case 12: // mod
cv->createObject<FurnaceCVModS>(x+(enemyType>=2?8:0),y+(enemyType>=2?8:0));
@ -2496,6 +2774,10 @@ void FurnaceCVEnemy1::collision(FurnaceCVObject* other) {
}
void FurnaceCVEnemy1::tick() {
if (frozen>0) {
if (--frozen>0) return;
}
if (!stopped) {
switch (orient) {
case 0:
@ -2663,7 +2945,7 @@ void FurnaceCVEnemy1::tick() {
}
}
void FurnaceCVEnemy1::setType(unsigned char t) {
void FurnaceCVEnemy::setType(unsigned char t) {
enemyType=t;
switch (enemyType) {
case 0:
@ -3307,6 +3589,33 @@ void FurnaceCVExtraLife::tick() {
}
}
// FurnaceCVSpecial IMPLEMENTATION
void FurnaceCVSpecial::collision(FurnaceCVObject* other) {
if (other->type==CV_PLAYER) {
dead=true;
cv->soundEffect(SE_PICKUP2);
cv->specialType=specialType;
}
}
void FurnaceCVSpecial::tick() {
if (--life==0) dead=true;
if (life>64 || (life&1)) {
spriteDef[0]=0x4dc+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
spriteDef[1]=0x4dd+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
spriteDef[2]=0x4fc+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
spriteDef[3]=0x4fd+(((specialType-1)&1)<<1)+(((specialType-1)>>1)<<6);
} else {
spriteDef[0]=0;
spriteDef[1]=0;
spriteDef[2]=0;
spriteDef[3]=0;
}
}
// FurnaceCVModI IMPLEMENTATION
void FurnaceCVModI::collision(FurnaceCVObject* other) {
@ -3368,16 +3677,25 @@ void FurnaceCVEnemyVortex::collision(FurnaceCVObject* other) {
if (other->type==CV_BULLET || other->type==CV_PLAYER) {
dead=true;
if ((rand()%2)==0) {
switch (rand()%10) {
case 0:
switch (rand()%14) {
case 0: // extra life
cv->createObject<FurnaceCVExtraLife>(x,y);
break;
case 1: case 2: case 3: case 4:
case 1: case 2: case 3: case 4: // powerup
cv->createObject<FurnaceCVPowerupP>(x,y);
break;
case 5: case 6: case 7: case 8: case 9:
case 5: case 6: case 7: case 8: case 9: // powerup
cv->createObject<FurnaceCVPowerupS>(x,y);
break;
case 10: case 11: // special
cv->createObject<FurnaceCVSpecial>(x,y);
break;
case 12: // mod
cv->createObject<FurnaceCVModS>(x,y);
break;
case 13: // mod
cv->createObject<FurnaceCVModI>(x,y);
break;
}
}
cv->soundEffect(SE_EXPL1);
@ -3387,6 +3705,10 @@ void FurnaceCVEnemyVortex::collision(FurnaceCVObject* other) {
}
void FurnaceCVEnemyVortex::tick() {
if (frozen>0) {
if (--frozen>0) return;
}
x+=speedX;
y+=speedY;
animFrame+=0x08;
@ -3470,12 +3792,12 @@ void FurnaceCVEnemyPlane::tick() {
if (--shootTime<=0) {
shootTime=28-(speed*2);
cv->soundEffect(SE_EXPL2);
cv->createObject<FurnaceCVFurBallLarge>(x+(spriteWidth<<2),y+(spriteHeight<<2));
cv->createObject<FurnaceCVFurBallLarge>(x+(spriteWidth<<2)-16,y+(spriteHeight<<2)-16);
for (int i=0; i<14; i++) {
float fraction=(float)i/13.0f;
float xs=cos(fraction*M_PI*2.0)*28;
float ys=sin(fraction*M_PI*2.0)*28;
FurnaceCVEnemyBullet* b=cv->createObject<FurnaceCVEnemyBullet>(x+(spriteWidth<<2),y+(spriteHeight<<2));
FurnaceCVEnemyBullet* b=cv->createObject<FurnaceCVEnemyBullet>(x+(spriteWidth<<2)-4,y+(spriteHeight<<2)-4);
b->speedX=xs;
b->speedY=ys;
}