diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 52364ab58..ccbab55f7 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -600,9 +600,9 @@ public: protected: void Heapify(); - unsigned int Parent(unsigned int i) { return (i + 1u) / 2u - 1u; } - unsigned int Left(unsigned int i) { return (i + 1u) * 2u - 1u; } - unsigned int Right(unsigned int i) { return (i + 1u) * 2u; } + unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; } + unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; } + unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; } }; class HMISong : public MIDIStreamer @@ -630,7 +630,7 @@ protected: struct TrackInfo; void ProcessInitialMetaEvents (); - DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay); + DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom); TrackInfo *FindNextDue (); static DWORD ReadVarLenHMI(TrackInfo *); @@ -673,7 +673,7 @@ protected: void AdvanceSong(DWORD time); void ProcessInitialMetaEvents(); - DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay); + DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay, ptrdiff_t room, bool &sysex_noroom); EventSource FindNextDue(); BYTE *MusHeader; diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index ca3ae0905..1796b1372 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -523,7 +523,12 @@ DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) // Play all events for this tick. do { - DWORD *new_events = SendCommand(events, TrackDue, time); + bool sysex_noroom = false; + DWORD *new_events = SendCommand(events, TrackDue, time, max_event_p - events, sysex_noroom); + if (sysex_noroom) + { + return events; + } TrackDue = FindNextDue(); if (new_events != events) { @@ -568,7 +573,7 @@ void HMISong::AdvanceTracks(DWORD time) // //========================================================================== -DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) +DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom) { DWORD len; BYTE event, data1 = 0, data2 = 0; @@ -584,10 +589,20 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) return events + 3; } + sysex_noroom = false; + size_t start_p = track->TrackP; + CHECK_FINISHED event = track->TrackBegin[track->TrackP++]; CHECK_FINISHED + // The actual event type will be filled in below. If it's not a NOP, + // the events pointer will be advanced once the actual event is written. + // Otherwise, we do it at the end of the function. + events[0] = delay; + events[1] = 0; + events[2] = MEVT_NOP << 24; + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe) { // Normal short message @@ -626,17 +641,10 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) data2 = VolumeControllerChange(event & 15, data2); } - events[0] = delay; - events[1] = 0; if (event != MIDI_META) { events[2] = event | (data1<<8) | (data2<<16); } - else - { - events[2] = MEVT_NOP << 24; - } - events += 3; if (ReadVarLen == ReadVarLenHMI && (event & 0x70) == (MIDI_NOTEON & 0x70)) { // HMI note on events include the time until an implied note off event. @@ -645,13 +653,41 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) } else { - // Skip SysEx events just because I don't want to bother with them. - // The old MIDI player ignored them too, so this won't break - // anything that played before. + // SysEx events could potentially not have enough room in the buffer... if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { len = ReadVarLen(track); - track->TrackP += len; + if (len >= (MAX_EVENTS-1)*3*4) + { // This message will never fit. Throw it away. + track->TrackP += len; + } + else if (len + 12 >= (size_t)room * 4) + { // Not enough room left in this buffer. Backup and wait for the next one. + track->TrackP = start_p; + sysex_noroom = true; + return events; + } + else + { + BYTE *msg = (BYTE *)&events[3]; + if (event == MIDI_SYSEX) + { // Need to add the SysEx marker to the message. + events[2] = (MEVT_LONGMSG << 24) | (len + 1); + *msg++ = MIDI_SYSEX; + } + else + { + events[2] = (MEVT_LONGMSG << 24) | len; + } + memcpy(msg, &track->TrackBegin[track->TrackP], len); + msg += len; + // Must pad with 0 + while ((size_t)msg & 3) + { + *msg++ = 0; + } + track->TrackP += len; + } } else if (event == MIDI_META) { @@ -677,7 +713,6 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) events[0] = delay; events[1] = 0; events[2] = (MEVT_TEMPO << 24) | Tempo; - events += 3; break; } track->TrackP += len; @@ -720,6 +755,18 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) { track->Delay = ReadVarLen(track); } + // Advance events pointer unless this is a non-delaying NOP. + if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP) + { + if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG) + { + events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2); + } + else + { + events += 3; + } + } return events; } diff --git a/src/sound/music_smf_midiout.cpp b/src/sound/music_smf_midiout.cpp index 43755eb08..918639399 100644 --- a/src/sound/music_smf_midiout.cpp +++ b/src/sound/music_smf_midiout.cpp @@ -384,6 +384,11 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr event = track->TrackBegin[track->TrackP++]; CHECK_FINISHED + // The actual event type will be filled in below. + events[0] = delay; + events[1] = 0; + events[2] = MEVT_NOP << 24; + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND) { // Normal short message @@ -582,17 +587,10 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr break; } } - events[0] = delay; - events[1] = 0; if (event != MIDI_META && (!track->Designated || (track->Designation & DesignationMask))) { events[2] = event | (data1<<8) | (data2<<16); } - else - { - events[2] = MEVT_NOP << 24; - } - events += 3; } else { @@ -612,8 +610,6 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr } else { - events[0] = delay; - events[1] = 0; BYTE *msg = (BYTE *)&events[3]; if (event == MIDI_SYSEX) { // Need to add the SysEx marker to the message. @@ -631,7 +627,6 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr { *msg++ = 0; } - events = (DWORD *)msg; track->TrackP += len; } } @@ -659,7 +654,6 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr events[0] = delay; events[1] = 0; events[2] = (MEVT_TEMPO << 24) | Tempo; - events += 3; break; } track->TrackP += len; @@ -678,6 +672,18 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr { track->Delay = track->ReadVarLen(); } + // Advance events pointer unless this is a non-delaying NOP. + if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP) + { + if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG) + { + events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2); + } + else + { + events += 3; + } + } return events; } diff --git a/src/sound/music_xmi_midiout.cpp b/src/sound/music_xmi_midiout.cpp index 71246f518..fbbf4db53 100644 --- a/src/sound/music_xmi_midiout.cpp +++ b/src/sound/music_xmi_midiout.cpp @@ -337,7 +337,12 @@ DWORD *XMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) // Play all events for this tick. do { - DWORD *new_events = SendCommand(events, EventDue, time); + bool sysex_noroom = false; + DWORD *new_events = SendCommand(events, EventDue, time, max_event_p - events, sysex_noroom); + if (sysex_noroom) + { + return events; + } EventDue = FindNextDue(); if (new_events != events) { @@ -382,7 +387,7 @@ void XMISong::AdvanceSong(DWORD time) // //========================================================================== -DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) +DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay, ptrdiff_t room, bool &sysex_noroom) { DWORD len; BYTE event, data1 = 0, data2 = 0; @@ -399,10 +404,20 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) TrackInfo *track = CurrSong; + sysex_noroom = false; + size_t start_p = track->EventP; + CHECK_FINISHED event = track->EventChunk[track->EventP++]; CHECK_FINISHED + // The actual event type will be filled in below. If it's not a NOP, + // the events pointer will be advanced once the actual event is written. + // Otherwise, we do it at the end of the function. + events[0] = delay; + events[1] = 0; + events[2] = MEVT_NOP << 24; + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND) { // Normal short message @@ -499,10 +514,6 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) { events[2] = event | (data1<<8) | (data2<<16); } - else - { - events[2] = MEVT_NOP << 24; - } events += 3; @@ -513,13 +524,41 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) } else { - // Skip SysEx events just because I don't want to bother with them. - // The old MIDI player ignored them too, so this won't break - // anything that played before. + // SysEx events could potentially not have enough room in the buffer... if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) { - len = track->ReadVarLen (); - track->EventP += len; + len = track->ReadVarLen(); + if (len >= (MAX_EVENTS-1)*3*4) + { // This message will never fit. Throw it away. + track->EventP += len; + } + else if (len + 12 >= (size_t)room * 4) + { // Not enough room left in this buffer. Backup and wait for the next one. + track->EventP = start_p; + sysex_noroom = true; + return events; + } + else + { + BYTE *msg = (BYTE *)&events[3]; + if (event == MIDI_SYSEX) + { // Need to add the SysEx marker to the message. + events[2] = (MEVT_LONGMSG << 24) | (len + 1); + *msg++ = MIDI_SYSEX; + } + else + { + events[2] = (MEVT_LONGMSG << 24) | len; + } + memcpy(msg, &track->EventChunk[track->EventP++], len); + msg += len; + // Must pad with 0 + while ((size_t)msg & 3) + { + *msg++ = 0; + } + track->EventP += len; + } } else if (event == MIDI_META) { @@ -551,6 +590,18 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay) { track->Delay = track->ReadDelay(); } + // Advance events pointer unless this is a non-delaying NOP. + if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP) + { + if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG) + { + events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2); + } + else + { + events += 3; + } + } return events; } diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 6a33bf595..51a920c8e 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -4265,6 +4265,7 @@ enum T_Flags TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. TF_NOJUMP = 0x00000200, // Don't jump after teleporting. TF_OVERRIDE = 0x00000400, // Ignore NOTELEPORT. + TF_SENSITIVEZ = 0x00000800, // Fail if the actor wouldn't fit in the position (for Z). }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) @@ -4329,6 +4330,20 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) return; } + + // [MC] By default, the function adjusts the actor's Z if it's below the floor or above the ceiling. + // This can be an issue as actors designed to maintain specific z positions wind up teleporting + // anyway when they should not, such as a floor rising above or ceiling lowering below the position + // of the spot. + if (flags & TF_SENSITIVEZ) + { + fixed_t posz = (flags & TF_USESPOTZ) ? spot->Z() : spot->floorz; + if ((posz + ref->height > spot->ceilingz) || (posz < spot->floorz)) + { + ACTION_SET_RESULT(false); + return; + } + } fixedvec3 prev = ref->Pos(); fixed_t aboveFloor = spot->Z() - spot->floorz; fixed_t finalz = spot->floorz + aboveFloor; @@ -4339,7 +4354,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) finalz = spot->floorz; //Take precedence and cooperate with telefragging first. - bool tele_result = P_TeleportMove(ref, spot->X(), spot->Y(), finalz, flags & TF_TELEFRAG); + bool tele_result = P_TeleportMove(ref, spot->X(), spot->Y(), finalz, !!(flags & TF_TELEFRAG)); if (!tele_result && (flags & TF_FORCED)) { @@ -4380,7 +4395,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) } ref->SetZ((flags & TF_USESPOTZ) ? spot->Z() : ref->floorz, false); - self->SetZ((flags & TF_USESPOTZ) ? spot->Z() : self->floorz, false); if (!(flags & TF_KEEPANGLE)) ref->angle = spot->angle; diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 213e2109d..a88290451 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -208,6 +208,7 @@ enum TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. TF_NOJUMP = 0x00000200, // Don't jump after teleporting. TF_OVERRIDE = 0x00000400, // Ignore NOTELEPORT. + TF_SENSITIVEZ = 0x00000800, // Fail if the actor wouldn't fit in the position (for Z). TF_KEEPORIENTATION = TF_KEEPVELOCITY|TF_KEEPANGLE, TF_NOFOG = TF_NOSRCFOG|TF_NODESTFOG,