diff --git a/CMakeLists.txt b/CMakeLists.txt index c67f73aee..6e5d9cc40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required( VERSION 2.4 ) +project(ZDoom) IF( NOT CMAKE_BUILD_TYPE ) SET( CMAKE_BUILD_TYPE Debug CACHE STRING diff --git a/dumb/CMakeLists.txt b/dumb/CMakeLists.txt index c2cbe8d1b..591e31400 100644 --- a/dumb/CMakeLists.txt +++ b/dumb/CMakeLists.txt @@ -36,6 +36,7 @@ add_library( dumb src/core/rendsig.c src/core/unload.c src/helpers/barray.c + src/helpers/blip_buf.c src/helpers/clickrem.c src/helpers/memfile.c src/helpers/resample.c @@ -60,6 +61,8 @@ add_library( dumb src/it/loadmod2.c src/it/loadmtm.c src/it/loadmtm2.c + src/it/loadokt.c + src/it/loadokt2.c src/it/loadoldpsm.c src/it/loadoldpsm2.c src/it/loadpsm.c @@ -83,6 +86,8 @@ add_library( dumb src/it/readmod.c src/it/readmod2.c src/it/readmtm.c + src/it/readokt.c + src/it/readokt2.c src/it/readoldpsm.c src/it/readpsm.c src/it/readptm.c diff --git a/dumb/include/dumb.h b/dumb/include/dumb.h index 09041ce3c..bcd202298 100644 --- a/dumb/include/dumb.h +++ b/dumb/include/dumb.h @@ -233,7 +233,6 @@ int32 DUMBEXPORT duh_get_length(DUH *duh); const char *DUMBEXPORT duh_get_tag(DUH *duh, const char *key); - /* Signal Rendering Functions */ typedef struct DUH_SIGRENDERER DUH_SIGRENDERER; @@ -396,6 +395,8 @@ void DUMBEXPORT dumb_it_set_global_volume_zero_callback(DUMB_IT_SIGRENDERER *sig int DUMBCALLBACK dumb_it_callback_terminate(void *data); int DUMBCALLBACK dumb_it_callback_midi_block(void *data, int channel, unsigned char midi_byte); +/* dumb_*_mod*: restrict |= 1-Don't read 15 sample files / 2-Use old pattern counting method */ + DUH *DUMBEXPORT dumb_load_it(const char *filename); DUH *DUMBEXPORT dumb_load_xm(const char *filename); DUH *DUMBEXPORT dumb_load_s3m(const char *filename); @@ -408,6 +409,7 @@ DUH *DUMBEXPORT dumb_load_old_psm(const char * filename); DUH *DUMBEXPORT dumb_load_mtm(const char *filename); DUH *DUMBEXPORT dumb_load_riff(const char *filename); DUH *DUMBEXPORT dumb_load_asy(const char *filename); +DUH *DUMBEXPORT dumb_load_okt(const char *filename); DUH *DUMBEXPORT dumb_read_it(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_xm(DUMBFILE *f); @@ -421,6 +423,7 @@ DUH *DUMBEXPORT dumb_read_old_psm(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_mtm(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_riff(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_asy(DUMBFILE *f); +DUH *DUMBEXPORT dumb_read_okt(DUMBFILE *f); DUH *DUMBEXPORT dumb_load_it_quick(const char *filename); DUH *DUMBEXPORT dumb_load_xm_quick(const char *filename); @@ -434,6 +437,7 @@ DUH *DUMBEXPORT dumb_load_old_psm_quick(const char * filename); DUH *DUMBEXPORT dumb_load_mtm_quick(const char *filename); DUH *DUMBEXPORT dumb_load_riff_quick(const char *filename); DUH *DUMBEXPORT dumb_load_asy_quick(const char *filename); +DUH *DUMBEXPORT dumb_load_okt_quick(const char *filename); DUH *DUMBEXPORT dumb_read_it_quick(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_xm_quick(DUMBFILE *f); @@ -447,6 +451,7 @@ DUH *DUMBEXPORT dumb_read_old_psm_quick(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_mtm_quick(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_riff_quick(DUMBFILE *f); DUH *DUMBEXPORT dumb_read_asy_quick(DUMBFILE *f); +DUH *DUMBEXPORT dumb_read_okt_quick(DUMBFILE *f); int32 DUMBEXPORT dumb_it_build_checkpoints(DUMB_IT_SIGDATA *sigdata, int startorder); void DUMBEXPORT dumb_it_do_initial_runthrough(DUH *duh); @@ -606,6 +611,8 @@ sigdata_t *DUMBEXPORT duh_get_raw_sigdata(DUH *duh, int sig, int32 type); DUH_SIGRENDERER *DUMBEXPORT duh_encapsulate_raw_sigrenderer(sigrenderer_t *vsigrenderer, DUH_SIGTYPE_DESC *desc, int n_channels, int32 pos); sigrenderer_t *DUMBEXPORT duh_get_raw_sigrenderer(DUH_SIGRENDERER *sigrenderer, int32 type); +int DUMBEXPORT duh_add_signal(DUH *duh, DUH_SIGTYPE_DESC *desc, sigdata_t *sigdata); + /* Standard Signal Types */ @@ -662,6 +669,8 @@ typedef struct DUMB_VOLUME_RAMP_INFO DUMB_VOLUME_RAMP_INFO; typedef void (*DUMB_RESAMPLE_PICKUP)(DUMB_RESAMPLER *resampler, void *data); +#include "internal/blip_buf.h" + struct DUMB_RESAMPLER { void *src; @@ -679,6 +688,9 @@ struct DUMB_RESAMPLER signed char x8[3*2]; } x; int overshot; + int last_clock; + int last_amp[2]; + blip_t* blip_buffer[2]; }; struct DUMB_VOLUME_RAMP_INFO diff --git a/dumb/include/internal/blip_buf.h b/dumb/include/internal/blip_buf.h new file mode 100644 index 000000000..7f687c3e9 --- /dev/null +++ b/dumb/include/internal/blip_buf.h @@ -0,0 +1,77 @@ +/** \file +Sample buffer that resamples from input clock rate to output sample rate */ + +/* blip_buf 1.1.0 */ +#ifndef BLIP_BUF_H +#define BLIP_BUF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/** First parameter of most functions is blip_t*, or const blip_t* if nothing +is changed. */ +typedef struct blip_t blip_t; + +/** Creates new buffer that can hold at most sample_count samples. Sets rates +so that there are blip_max_ratio clocks per sample. Returns pointer to new +buffer, or NULL if insufficient memory. */ +blip_t* blip_new( int sample_count ); + +blip_t* blip_dup( blip_t* ); + +/** Sets approximate input clock rate and output sample rate. For every +clock_rate input clocks, approximately sample_rate samples are generated. */ +void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); + +enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, +clock_rate must not be greater than sample_rate*blip_max_ratio. */ +blip_max_ratio = 1 << 20 }; + +/** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ +void blip_clear( blip_t* ); + +/** Adds positive/negative delta into buffer at specified clock time. */ +void blip_add_delta( blip_t*, unsigned int clock_time, int delta ); + +/** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ +void blip_add_delta_fast( blip_t*, unsigned int clock_time, int delta ); + +/** Length of time frame, in clocks, needed to make sample_count additional +samples available. */ +int blip_clocks_needed( const blip_t*, int sample_count ); + +enum { /** Maximum number of samples that can be generated from one time frame. */ +blip_max_frame = 4000 }; + +/** Makes input clocks before clock_duration available for reading as output +samples. Also begins new time frame at clock_duration, so that clock time 0 in +the new time frame specifies the same clock as clock_duration in the old time +frame specified. Deltas can have been added slightly past clock_duration (up to +however many clocks there are in two output samples). */ +void blip_end_frame( blip_t*, unsigned int clock_duration ); + +/** Number of buffered samples available for reading. */ +int blip_samples_avail( const blip_t* ); + +/** Reads and removes at most 'count' samples and writes them to 'out'. If +'stereo' is true, writes output to every other element of 'out', allowing easy +interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed +samples. Returns number of samples actually read. */ +int blip_read_samples( blip_t*, int out [], int count ); + +/** Reads the current integrator and returns it */ +int blip_peek_sample( blip_t* ); + +/** Frees buffer. No effect if NULL is passed. */ +void blip_delete( blip_t* ); + + +/* Deprecated */ +typedef blip_t blip_buffer_t; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/dumb/include/internal/it.h b/dumb/include/internal/it.h index e248ade79..922d9bed0 100644 --- a/dumb/include/internal/it.h +++ b/dumb/include/internal/it.h @@ -54,7 +54,7 @@ sigdata->flags & IT_COMPATIBLE_GXX * handle ambiguities in the format specification. The correct code in each * case will be determined most likely by experimentation. */ -#define STEREO_SAMPLES_COUNT_AS_TWO +//#define STEREO_SAMPLES_COUNT_AS_TWO #define INVALID_ORDERS_END_SONG #define INVALID_NOTES_CAUSE_NOTE_CUT #define SUSTAIN_LOOP_OVERRIDES_NORMAL_LOOP @@ -300,7 +300,18 @@ struct IT_SAMPLE #define IT_PTM_NOTE_SLIDE_DOWN_RETRIG 36 #define IT_PTM_NOTE_SLIDE_UP_RETRIG 37 -#define IT_N_EFFECTS 38 +/* More effects needed for OKT compatibility */ +#define IT_OKT_NOTE_SLIDE_DOWN 38 +#define IT_OKT_NOTE_SLIDE_DOWN_ROW 39 +#define IT_OKT_NOTE_SLIDE_UP 40 +#define IT_OKT_NOTE_SLIDE_UP_ROW 41 +#define IT_OKT_ARPEGGIO_3 42 +#define IT_OKT_ARPEGGIO_4 43 +#define IT_OKT_ARPEGGIO_5 44 +#define IT_OKT_VOLUME_SLIDE_DOWN 45 +#define IT_OKT_VOLUME_SLIDE_UP 46 + +#define IT_N_EFFECTS 47 /* These represent the top nibble of the command value. */ #define IT_S_SET_FILTER 0 /* Greyed out in IT... */ @@ -399,6 +410,10 @@ struct IT_PATTERN #define IT_WAS_A_669 1024 +#define IT_WAS_AN_OKT 2048 + +#define IT_WAS_AN_STM 4096 + #define IT_ORDER_END 255 #define IT_ORDER_SKIP 254 @@ -452,6 +467,7 @@ struct IT_PLAYING_ENVELOPE #define IT_PLAYING_SUSTAINOFF 2 #define IT_PLAYING_FADING 4 #define IT_PLAYING_DEAD 8 +#define IT_PLAYING_REVERSE 16 struct IT_PLAYING { @@ -586,7 +602,8 @@ struct IT_CHANNEL unsigned char new_note_action; - int arpeggio; + unsigned int arpeggio; + int arpeggio_shift; unsigned char retrig; unsigned char xm_retrig; int retrig_tick; @@ -601,7 +618,7 @@ struct IT_CHANNEL int portamento; int toneporta; int toneslide; - unsigned char toneslide_tick, last_toneslide_tick, ptm_toneslide, ptm_last_toneslide; + unsigned char toneslide_tick, last_toneslide_tick, ptm_toneslide, ptm_last_toneslide, okt_toneslide; unsigned char destnote; unsigned char toneslide_retrig; @@ -643,6 +660,10 @@ struct IT_CHANNEL unsigned char xm_lastX1; unsigned char xm_lastX2; + unsigned char inv_loop_delay; + unsigned char inv_loop_speed; + int inv_loop_offset; + IT_PLAYING *playing; #ifdef BIT_ARRAY_BULLSHIT @@ -802,6 +823,7 @@ extern DUH_SIGTYPE_DESC _dumb_sigtype_it; #define XM_E_NOTE_CUT 0xC #define XM_E_NOTE_DELAY 0xD #define XM_E_PATTERN_DELAY 0xE +#define XM_E_SET_MIDI_MACRO 0xF #define XM_X_EXTRAFINE_PORTA_UP 1 #define XM_X_EXTRAFINE_PORTA_DOWN 2 @@ -880,4 +902,6 @@ void _dumb_it_ptm_convert_effect(int effect, int value, IT_ENTRY *entry); int32 _dumb_it_read_sample_data_adpcm4(IT_SAMPLE *sample, DUMBFILE *f); +void _dumb_it_interleave_stereo_sample(IT_SAMPLE *sample); + #endif /* INTERNAL_IT_H */ diff --git a/dumb/src/core/makeduh.c b/dumb/src/core/makeduh.c index 598b637bc..24f29259b 100644 --- a/dumb/src/core/makeduh.c +++ b/dumb/src/core/makeduh.c @@ -130,3 +130,22 @@ DUH *make_duh( return duh; } + +int DUMBEXPORT duh_add_signal(DUH *duh, DUH_SIGTYPE_DESC *desc, sigdata_t *sigdata) +{ + DUH_SIGNAL **signal; + + if ( !duh || !desc || !sigdata ) return -1; + + signal = ( DUH_SIGNAL ** ) realloc( duh->signal, ( duh->n_signals + 1 ) * sizeof( *duh->signal ) ); + if ( !signal ) return -1; + duh->signal = signal; + + memmove( signal + 1, signal, duh->n_signals * sizeof( *signal ) ); + duh->n_signals++; + + signal[ 0 ] = make_signal( desc, sigdata ); + if ( !signal[ 0 ] ) return -1; + + return 0; +} diff --git a/dumb/src/core/rawsig.c b/dumb/src/core/rawsig.c index a1edd60d0..2287bb068 100644 --- a/dumb/src/core/rawsig.c +++ b/dumb/src/core/rawsig.c @@ -29,16 +29,30 @@ */ sigdata_t *DUMBEXPORT duh_get_raw_sigdata(DUH *duh, int sig, int32 type) { + int i; DUH_SIGNAL *signal; if (!duh) return NULL; - if ((unsigned int)sig >= (unsigned int)duh->n_signals) return NULL; + if ( sig >= 0 ) + { + if ((unsigned int)sig >= (unsigned int)duh->n_signals) return NULL; - signal = duh->signal[sig]; + signal = duh->signal[sig]; - if (signal && signal->desc->type == type) - return signal->sigdata; + if (signal && signal->desc->type == type) + return signal->sigdata; + } + else + { + for ( i = 0; i < duh->n_signals; i++ ) + { + signal = duh->signal[i]; + + if (signal && signal->desc->type == type) + return signal->sigdata; + } + } return NULL; } diff --git a/dumb/src/helpers/blip_buf.c b/dumb/src/helpers/blip_buf.c new file mode 100644 index 000000000..03c111198 --- /dev/null +++ b/dumb/src/helpers/blip_buf.c @@ -0,0 +1,354 @@ +/* blip_buf 1.1.0. http://www.slack.net/~ant/ */ + +#include "internal/blip_buf.h" + +#include +#include +#include +#include + +/* Library Copyright (C) 2003-2009 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#if defined (BLARGG_TEST) && BLARGG_TEST + #include "blargg_test.h" +#endif + +/* Equivalent to ULONG_MAX >= 0xFFFFFFFF00000000. +Avoids constants that don't fit in 32 bits. */ +#if ULONG_MAX/0xFFFFFFFF > 0xFFFFFFFF + typedef unsigned long fixed_t; + enum { pre_shift = 32 }; + +#elif defined(ULLONG_MAX) + typedef unsigned long long fixed_t; + enum { pre_shift = 32 }; + +#else + typedef unsigned fixed_t; + enum { pre_shift = 0 }; + +#endif + +enum { time_bits = pre_shift + 20 }; + +static fixed_t const time_unit = (fixed_t) 1 << time_bits; + +enum { bass_shift = 9 }; /* affects high-pass filter breakpoint frequency */ +enum { end_frame_extra = 2 }; /* allows deltas slightly after frame length */ + +enum { half_width = 8 }; +enum { buf_extra = half_width*2 + end_frame_extra }; +enum { phase_bits = 5 }; +enum { phase_count = 1 << phase_bits }; +enum { delta_bits = 15 }; +enum { delta_unit = 1 << delta_bits }; +enum { frac_bits = time_bits - pre_shift }; + +/* We could eliminate avail and encode whole samples in offset, but that would +limit the total buffered samples to blip_max_frame. That could only be +increased by decreasing time_bits, which would reduce resample ratio accuracy. +*/ + +/** Sample buffer that resamples to output rate and accumulates samples +until they're read out */ +struct blip_t +{ + fixed_t factor; + fixed_t offset; + int avail; + int size; + int integrator; +}; + +typedef int buf_t; + +/* probably not totally portable */ +#define SAMPLES( buf ) ((buf_t*) ((buf) + 1)) + +/* Arithmetic (sign-preserving) right shift */ +#define ARITH_SHIFT( n, shift ) \ + ((n) >> (shift)) + +enum { max_sample = +32767 }; +enum { min_sample = -32768 }; + +#define CLAMP( n ) \ + {\ + if ( (short) n != n )\ + n = ARITH_SHIFT( n, 16 ) ^ max_sample;\ + } + +static void check_assumptions( void ) +{ + int n; + + #if INT_MAX < 0x7FFFFFFF || UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" + #endif + + assert( (-3 >> 1) == -2 ); /* right shift must preserve sign */ + + n = max_sample * 2; + CLAMP( n ); + assert( n == max_sample ); + + n = min_sample * 2; + CLAMP( n ); + assert( n == min_sample ); + + assert( blip_max_ratio <= time_unit ); + assert( blip_max_frame <= (fixed_t) -1 >> time_bits ); +} + +blip_t* blip_new( int size ) +{ + blip_t* m; + assert( size >= 0 ); + + m = (blip_t*) malloc( sizeof *m + (size + buf_extra) * sizeof (buf_t) ); + if ( m ) + { + m->factor = time_unit / blip_max_ratio; + m->size = size; + blip_clear( m ); + check_assumptions(); + } + return m; +} + +blip_t* blip_dup( blip_t* m ) +{ + size_t size = sizeof *m + (m->size + buf_extra) * sizeof(buf_t); + blip_t* r = (blip_t*) malloc( size ); + if ( r ) memcpy( r, m, size ); + return r; +} + +void blip_delete( blip_t* m ) +{ + if ( m != NULL ) + { + /* Clear fields in case user tries to use after freeing */ + memset( m, 0, sizeof *m ); + free( m ); + } +} + +void blip_set_rates( blip_t* m, double clock_rate, double sample_rate ) +{ + double factor = time_unit * sample_rate / clock_rate; + m->factor = (fixed_t) factor; + + /* Fails if clock_rate exceeds maximum, relative to sample_rate */ + assert( 0 <= factor - m->factor && factor - m->factor < 1 ); + + /* Avoid requiring math.h. Equivalent to + m->factor = (int) ceil( factor ) */ + if ( m->factor < factor ) + m->factor++; + + /* At this point, factor is most likely rounded up, but could still + have been rounded down in the floating-point calculation. */ +} + +void blip_clear( blip_t* m ) +{ + /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if + factor is rounded up. factor-1 is suitable if factor is rounded down. + Since we don't know rounding direction, factor/2 accommodates either, + with the slight loss of showing an error in half the time. Since for + a 64-bit factor this is years, the halving isn't a problem. */ + + m->offset = m->factor / 2; + m->avail = 0; + m->integrator = 0; + memset( SAMPLES( m ), 0, (m->size + buf_extra) * sizeof (buf_t) ); +} + +int blip_clocks_needed( const blip_t* m, int samples ) +{ + fixed_t needed; + + /* Fails if buffer can't hold that many more samples */ + assert( samples >= 0 && m->avail + samples <= m->size ); + + needed = (fixed_t) samples * time_unit; + if ( needed < m->offset ) + return 0; + + return (int)((needed - m->offset + m->factor - 1) / m->factor); +} + +void blip_end_frame( blip_t* m, unsigned t ) +{ + fixed_t off = t * m->factor + m->offset; + m->avail += (int)(off >> time_bits); + m->offset = off & (time_unit - 1); + + /* Fails if buffer size was exceeded */ + assert( m->avail <= m->size ); +} + +int blip_samples_avail( const blip_t* m ) +{ + return m->avail; +} + +static void remove_samples( blip_t* m, int count ) +{ + buf_t* buf = SAMPLES( m ); + int remain = m->avail + buf_extra - count; + m->avail -= count; + + memmove( &buf [0], &buf [count], remain * sizeof buf [0] ); + memset( &buf [remain], 0, count * sizeof buf [0] ); +} + +int blip_read_samples( blip_t* m, int out [], int count ) +{ + assert( count >= 0 ); + + if ( count > m->avail ) + count = m->avail; + + if ( count ) + { + buf_t const* in = SAMPLES( m ); + buf_t const* end = in + count; + int sum = m->integrator; + do + { + /* Eliminate fraction */ + int s = ARITH_SHIFT( sum, delta_bits - 8 ); + + sum += *in++; + + *out = s; + out++; + + /* High-pass filter */ + sum -= s >> (8 - (delta_bits - bass_shift)); //<< (delta_bits - bass_shift - 8); + } + while ( in != end ); + m->integrator = sum; + + remove_samples( m, count ); + } + + return count; +} + +int blip_peek_sample( blip_t* m ) +{ + return ARITH_SHIFT( m->integrator, delta_bits - 8 ); +} + +/* Things that didn't help performance on x86: + __attribute__((aligned(128))) + #define short int + restrict +*/ + +/* Sinc_Generator( 0.9, 0.55, 4.5 ) */ +static short const bl_step [phase_count + 1] [half_width] = +{ +{ 43, -115, 350, -488, 1136, -914, 5861,21022}, +{ 44, -118, 348, -473, 1076, -799, 5274,21001}, +{ 45, -121, 344, -454, 1011, -677, 4706,20936}, +{ 46, -122, 336, -431, 942, -549, 4156,20829}, +{ 47, -123, 327, -404, 868, -418, 3629,20679}, +{ 47, -122, 316, -375, 792, -285, 3124,20488}, +{ 47, -120, 303, -344, 714, -151, 2644,20256}, +{ 46, -117, 289, -310, 634, -17, 2188,19985}, +{ 46, -114, 273, -275, 553, 117, 1758,19675}, +{ 44, -108, 255, -237, 471, 247, 1356,19327}, +{ 43, -103, 237, -199, 390, 373, 981,18944}, +{ 42, -98, 218, -160, 310, 495, 633,18527}, +{ 40, -91, 198, -121, 231, 611, 314,18078}, +{ 38, -84, 178, -81, 153, 722, 22,17599}, +{ 36, -76, 157, -43, 80, 824, -241,17092}, +{ 34, -68, 135, -3, 8, 919, -476,16558}, +{ 32, -61, 115, 34, -60, 1006, -683,16001}, +{ 29, -52, 94, 70, -123, 1083, -862,15422}, +{ 27, -44, 73, 106, -184, 1152,-1015,14824}, +{ 25, -36, 53, 139, -239, 1211,-1142,14210}, +{ 22, -27, 34, 170, -290, 1261,-1244,13582}, +{ 20, -20, 16, 199, -335, 1301,-1322,12942}, +{ 18, -12, -3, 226, -375, 1331,-1376,12293}, +{ 15, -4, -19, 250, -410, 1351,-1408,11638}, +{ 13, 3, -35, 272, -439, 1361,-1419,10979}, +{ 11, 9, -49, 292, -464, 1362,-1410,10319}, +{ 9, 16, -63, 309, -483, 1354,-1383, 9660}, +{ 7, 22, -75, 322, -496, 1337,-1339, 9005}, +{ 6, 26, -85, 333, -504, 1312,-1280, 8355}, +{ 4, 31, -94, 341, -507, 1278,-1205, 7713}, +{ 3, 35, -102, 347, -506, 1238,-1119, 7082}, +{ 1, 40, -110, 350, -499, 1190,-1021, 6464}, +{ 0, 43, -115, 350, -488, 1136, -914, 5861} +}; + +/* Shifting by pre_shift allows calculation using unsigned int rather than +possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. +And by having pre_shift 32, a 32-bit platform can easily do the shift by +simply ignoring the low half. */ + +void blip_add_delta( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int const phase_shift = frac_bits - phase_bits; + int phase = fixed >> phase_shift & (phase_count - 1); + short const* in = bl_step [phase]; + short const* rev = bl_step [phase_count - phase]; + + int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); + int delta2 = (delta * interp) >> delta_bits; + delta -= delta2; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [0] += in[0]*delta + in[half_width+0]*delta2; + out [1] += in[1]*delta + in[half_width+1]*delta2; + out [2] += in[2]*delta + in[half_width+2]*delta2; + out [3] += in[3]*delta + in[half_width+3]*delta2; + out [4] += in[4]*delta + in[half_width+4]*delta2; + out [5] += in[5]*delta + in[half_width+5]*delta2; + out [6] += in[6]*delta + in[half_width+6]*delta2; + out [7] += in[7]*delta + in[half_width+7]*delta2; + + in = rev; + out [ 8] += in[7]*delta + in[7-half_width]*delta2; + out [ 9] += in[6]*delta + in[6-half_width]*delta2; + out [10] += in[5]*delta + in[5-half_width]*delta2; + out [11] += in[4]*delta + in[4-half_width]*delta2; + out [12] += in[3]*delta + in[3-half_width]*delta2; + out [13] += in[2]*delta + in[2-half_width]*delta2; + out [14] += in[1]*delta + in[1-half_width]*delta2; + out [15] += in[0]*delta + in[0-half_width]*delta2; +} + +void blip_add_delta_fast( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); + int delta2 = delta * interp; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [7] += delta * delta_unit - delta2; + out [8] += delta2; +} diff --git a/dumb/src/helpers/resamp2.inc b/dumb/src/helpers/resamp2.inc index 84826ce51..de65dfb34 100644 --- a/dumb/src/helpers/resamp2.inc +++ b/dumb/src/helpers/resamp2.inc @@ -95,7 +95,8 @@ static int process_pickup(DUMB_RESAMPLER *resampler) #define SET_VOLUME_VARIABLES SET_MONO_DEST_VOLUME_VARIABLES #define RETURN_VOLUME_VARIABLES RETURN_MONO_DEST_VOLUME_VARIABLES #define VOLUMES_ARE_ZERO MONO_DEST_VOLUMES_ARE_ZERO -#define MIX_ALIAS(op, upd, offset) MONO_DEST_MIX_ALIAS(op, upd, offset) +#define MIX_ALIAS(count) MONO_DEST_MIX_ALIAS(count) +#define PEEK_ALIAS MONO_DEST_PEEK_ALIAS #define MIX_LINEAR(op, upd, o0, o1) MONO_DEST_MIX_LINEAR(op, upd, o0, o1) #define MIX_CUBIC(op, upd, x0, x3, o0, o1, o2, o3) MONO_DEST_MIX_CUBIC(op, upd, x0, x3, o0, o1, o2, o3) #define MIX_ZEROS(op) *dst++ op 0 @@ -137,7 +138,8 @@ static int process_pickup(DUMB_RESAMPLER *resampler) if ( volume_right ) volume_right->volume = (float)rvolr / 16777216.0f; \ } #define VOLUMES_ARE_ZERO (lvol == 0 && lvolt == 0 && rvol == 0 && rvolt == 0) -#define MIX_ALIAS(op, upd, offset) STEREO_DEST_MIX_ALIAS(op, upd, offset) +#define MIX_ALIAS(count) STEREO_DEST_MIX_ALIAS(count) +#define PEEK_ALIAS STEREO_DEST_PEEK_ALIAS #define MIX_LINEAR(op, upd, o0, o1) STEREO_DEST_MIX_LINEAR(op, upd, o0, o1) #define MIX_CUBIC(op, upd, x0, x3, o0, o1, o2, o3) STEREO_DEST_MIX_CUBIC(op, upd, x0, x3, o0, o1, o2, o3) #define MIX_ZEROS(op) { *dst++ op 0; *dst++ op 0; } @@ -157,6 +159,9 @@ static int process_pickup(DUMB_RESAMPLER *resampler) #undef MONO_DEST_VOLUME_ZEROS #undef MONO_DEST_VOLUME_VARIABLES #undef MONO_DEST_VOLUME_PARAMETERS +#undef STEREO_DEST_PEEK_ALIAS +#undef MONO_DEST_PEEK_ALIAS +#undef POKE_ALIAS #undef COPYSRC2 #undef COPYSRC #undef DIVIDE_BY_SRC_CHANNELS diff --git a/dumb/src/helpers/resamp3.inc b/dumb/src/helpers/resamp3.inc index 3d61bfff1..05c0dfbb7 100644 --- a/dumb/src/helpers/resamp3.inc +++ b/dumb/src/helpers/resamp3.inc @@ -46,12 +46,13 @@ int32 dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, int32 dst_size, VOLUME_PARAMETERS, double delta) { - int dt; + int dt, inv_dt; int VOLUME_VARIABLES; long done; long todo; LONG_LONG todo64; int quality; + int blip_samples[256*SRC_CHANNELS]; if (!resampler || resampler->dir == 0) return 0; ASSERT(resampler->dir == -1 || resampler->dir == 1); @@ -59,6 +60,7 @@ int32 dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, int32 dst_size, VO done = 0; dt = (int)(delta * 65536.0 + 0.5); if (dt == 0 || dt == 0x80000000) return 0; + inv_dt = (int)(1.0 / delta * 65536.0 + 0.5); SET_VOLUME_VARIABLES; if (VOLUMES_ARE_ZERO) dst = NULL; @@ -104,29 +106,33 @@ int32 dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, int32 dst_size, VO subpos = (long)new_subpos & 65535; } else if (quality <= DUMB_RQ_ALIASING) { /* Aliasing, backwards */ - SRCTYPE xbuf[2*SRC_CHANNELS]; + int todo_clocks = todo << 16, todo_clocks_set = todo_clocks; + SRCTYPE xbuf[2*SRC_CHANNELS]; SRCTYPE *x = &xbuf[0]; - SRCTYPE *xstart; COPYSRC(xbuf, 0, resampler->X, 1); COPYSRC(xbuf, 1, resampler->X, 2); - while (todo && x < &xbuf[2*SRC_CHANNELS]) { + if ( todo_clocks_set > 256 * 65536 ) todo_clocks_set = 256 * 65536; + while (resampler->last_clock < todo_clocks_set && x < &xbuf[2*SRC_CHANNELS]) { // TODO: check what happens when multiple tempo slides occur per row HEAVYASSERT(pos >= resampler->start); - MIX_ALIAS(+=, 1, 0); - subpos += dt; - pos += subpos >> 16; - x -= (subpos >> 16) * SRC_CHANNELS; - subpos &= 65535; - todo--; + POKE_ALIAS(0); + pos--; + x += SRC_CHANNELS; + } + x = &src[pos*SRC_CHANNELS]; + while ( todo_clocks ) { + todo_clocks_set = todo_clocks; + if ( todo_clocks_set > 256 * 65536 ) todo_clocks_set = 256 * 65536; + todo_clocks -= todo_clocks_set; + while ( resampler->last_clock < todo_clocks_set ) + { + POKE_ALIAS(2); + pos--; + x -= SRC_CHANNELS; + } + todo = todo_clocks_set >> 16; + MIX_ALIAS( todo ); } - x = xstart = &src[pos*SRC_CHANNELS]; - LOOP4(todo, - MIX_ALIAS(+=, 1, 2); - subpos += dt; - x += (subpos >> 16) * SRC_CHANNELS; - subpos &= 65535; - ); - pos += DIVIDE_BY_SRC_CHANNELS((long)(x - xstart)); } else if (quality <= DUMB_RQ_LINEAR) { /* Linear interpolation, backwards */ SRCTYPE xbuf[3*SRC_CHANNELS]; @@ -205,28 +211,32 @@ int32 dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, int32 dst_size, VO subpos = (long)new_subpos & 65535; } else if (quality <= DUMB_RQ_ALIASING) { /* Aliasing, forwards */ + int todo_clocks = todo << 16, todo_clocks_set = todo_clocks; SRCTYPE xbuf[2*SRC_CHANNELS]; SRCTYPE *x = &xbuf[0]; - SRCTYPE *xstart; COPYSRC(xbuf, 0, resampler->X, 1); COPYSRC(xbuf, 1, resampler->X, 2); - while (todo && x < &xbuf[2*SRC_CHANNELS]) { + if ( todo_clocks_set > 256 * 65536 ) todo_clocks_set = 256 * 65536; + while (resampler->last_clock < todo_clocks_set && x < &xbuf[2*SRC_CHANNELS]) { HEAVYASSERT(pos < resampler->end); - MIX_ALIAS(+=, 1, 0); - subpos += dt; - pos += subpos >> 16; - x += (subpos >> 16) * SRC_CHANNELS; - subpos &= 65535; - todo--; + POKE_ALIAS(0); + pos++; + x += SRC_CHANNELS; + } + x = &src[pos*SRC_CHANNELS]; + while ( todo_clocks ) { + todo_clocks_set = todo_clocks; + if ( todo_clocks_set > 256 * 65536 ) todo_clocks_set = 256 * 65536; + todo_clocks -= todo_clocks_set; + while ( resampler->last_clock < todo_clocks_set ) + { + POKE_ALIAS(-2); + pos++; + x += SRC_CHANNELS; + } + todo = todo_clocks_set >> 16; + MIX_ALIAS( todo ); } - x = xstart = &src[pos*SRC_CHANNELS]; - LOOP4(todo, - MIX_ALIAS(+=, 1, -2); - subpos += dt; - x += (subpos >> 16) * SRC_CHANNELS; - subpos &= 65535; - ); - pos += DIVIDE_BY_SRC_CHANNELS((long)(x - xstart)); } else if (quality <= DUMB_RQ_LINEAR) { /* Linear interpolation, forwards */ SRCTYPE xbuf[3*SRC_CHANNELS]; @@ -339,7 +349,7 @@ void dumb_resample_get_current_sample(DUMB_RESAMPLER *resampler, VOLUME_PARAMETE HEAVYASSERT(pos >= resampler->start); if (dumb_resampling_quality <= DUMB_RQ_ALIASING) { /* Aliasing, backwards */ - MIX_ALIAS(=, 0, 1); + PEEK_ALIAS; } else if (quality <= DUMB_RQ_LINEAR) { /* Linear interpolation, backwards */ MIX_LINEAR(=, 0, 2, 1); @@ -351,7 +361,7 @@ void dumb_resample_get_current_sample(DUMB_RESAMPLER *resampler, VOLUME_PARAMETE HEAVYASSERT(pos < resampler->end); if (dumb_resampling_quality <= DUMB_RQ_ALIASING) { /* Aliasing */ - MIX_ALIAS(=, 0, 1); + PEEK_ALIAS; } else if (dumb_resampling_quality <= DUMB_RQ_LINEAR) { /* Linear interpolation, forwards */ MIX_LINEAR(=, 0, 1, 2); @@ -368,6 +378,7 @@ void dumb_resample_get_current_sample(DUMB_RESAMPLER *resampler, VOLUME_PARAMETE #undef MIX_CUBIC #undef MIX_LINEAR #undef MIX_ALIAS +#undef PEEK_ALIAS #undef VOLUMES_ARE_ZERO #undef SET_VOLUME_VARIABLES #undef RETURN_VOLUME_VARIABLES diff --git a/dumb/src/helpers/resample.c b/dumb/src/helpers/resample.c index db42ee7ae..4424a4934 100644 --- a/dumb/src/helpers/resample.c +++ b/dumb/src/helpers/resample.c @@ -117,9 +117,9 @@ int dumb_resampling_quality = DUMB_RQ_CUBIC; */ #define LOOP4(iterator, CONTENT) \ { \ - while (iterator) { \ + while ( (iterator)-- ) \ + { \ CONTENT; \ - (iterator)--; \ } \ } #endif @@ -189,7 +189,7 @@ static void init_cubic(void) #define SRCTYPE sample_t #define SRCBITS 24 -#define ALIAS(x, vol) MULSC(x, vol) +#define ALIAS(x) (x >> 8) #define LINEAR(x0, x1) (x0 + MULSC(x1 - x0, subpos)) /* #define SET_CUBIC_COEFFICIENTS(x0, x1, x2, x3) { \ @@ -225,7 +225,7 @@ static void init_cubic(void) #define SUFFIX _16 #define SRCTYPE short #define SRCBITS 16 -#define ALIAS(x, vol) (x * vol >> 8) +#define ALIAS(x) (x) #define LINEAR(x0, x1) ((x0 << 8) + MULSC16(x1 - x0, subpos)) /* #define SET_CUBIC_COEFFICIENTS(x0, x1, x2, x3) { \ @@ -247,7 +247,7 @@ static void init_cubic(void) #define SUFFIX _8 #define SRCTYPE signed char #define SRCBITS 8 -#define ALIAS(x, vol) (x * vol) +#define ALIAS(x) (x << 8) #define LINEAR(x0, x1) ((x0 << 16) + (x1 - x0) * subpos) /* #define SET_CUBIC_COEFFICIENTS(x0, x1, x2, x3) { \ diff --git a/dumb/src/helpers/resample.inc b/dumb/src/helpers/resample.inc index eaef2976c..364e36ccd 100644 --- a/dumb/src/helpers/resample.inc +++ b/dumb/src/helpers/resample.inc @@ -69,6 +69,11 @@ void dumb_reset_resampler(DUMB_RESAMPLER *resampler, SRCTYPE *src, int src_chann } for (i = 0; i < src_channels*3; i++) resampler->X[i] = 0; resampler->overshot = -1; + resampler->last_clock = 0; + resampler->last_amp[0] = 0; + resampler->last_amp[1] = 0; + blip_clear(resampler->blip_buffer[0]); + blip_clear(resampler->blip_buffer[1]); } @@ -77,6 +82,21 @@ DUMB_RESAMPLER *dumb_start_resampler(SRCTYPE *src, int src_channels, int32 pos, { DUMB_RESAMPLER *resampler = malloc(sizeof(*resampler)); if (!resampler) return NULL; + resampler->blip_buffer[0] = blip_new( 256 ); + if (!resampler->blip_buffer[0]) + { + free(resampler); + return NULL; + } + resampler->blip_buffer[1] = blip_new( 256 ); + if (!resampler->blip_buffer[1]) + { + free(resampler->blip_buffer[0]); + free(resampler); + return NULL; + } + blip_set_rates(resampler->blip_buffer[0], 65536, 1); + blip_set_rates(resampler->blip_buffer[1], 65536, 1); dumb_reset_resampler(resampler, src, src_channels, pos, start, end, quality); return resampler; } @@ -123,16 +143,41 @@ DUMB_RESAMPLER *dumb_start_resampler(SRCTYPE *src, int src_channels, int32 pos, } #define RETURN_MONO_DEST_VOLUME_VARIABLES if ( volume ) volume->volume = (float)volr / 16777216.0f #define MONO_DEST_VOLUMES_ARE_ZERO (vol == 0 && volt == 0) -#define MONO_DEST_MIX_ALIAS(op, upd, offset) { \ - *dst++ op ALIAS(x[offset], vol); \ - if ( upd ) UPDATE_VOLUME( volume, vol ); \ +#define POKE_ALIAS(offset) { \ + int delta = ALIAS(x[offset]) - resampler->last_amp[0]; \ + resampler->last_amp[0] += delta; \ + if ( delta ) blip_add_delta( resampler->blip_buffer[0], resampler->last_clock, delta ); \ + resampler->last_clock += inv_dt; \ } -#define STEREO_DEST_MIX_ALIAS(op, upd, offset) { \ - int xm = x[offset]; \ - *dst++ op ALIAS(xm, lvol); \ - *dst++ op ALIAS(xm, rvol); \ - if ( upd ) UPDATE_VOLUME( volume_left, lvol ); \ - if ( upd ) UPDATE_VOLUME( volume_right, rvol ); \ +#define MONO_DEST_PEEK_ALIAS *dst = MULSC( blip_peek_sample( resampler->blip_buffer[0] ), vol ) +#define MONO_DEST_MIX_ALIAS(count) { \ + int n = 0; \ + resampler->last_clock -= count * 65536; \ + blip_end_frame( resampler->blip_buffer[0], count * 65536 ); \ + blip_read_samples( resampler->blip_buffer[0], blip_samples, count ); \ + LOOP4( count, \ + *dst++ += MULSC( blip_samples[n], vol ); \ + n++; \ + UPDATE_VOLUME( volume, vol ); \ + ); \ +} +#define STEREO_DEST_PEEK_ALIAS { \ + int sample = blip_peek_sample( resampler->blip_buffer[0] ); \ + *dst++ = MULSC( sample, lvol ); \ + *dst++ = MULSC( sample, rvol ); \ +} +#define STEREO_DEST_MIX_ALIAS(count) { \ + int sample, n = 0; \ + resampler->last_clock -= count * 65536; \ + blip_end_frame( resampler->blip_buffer[0], count * 65536 ); \ + blip_read_samples( resampler->blip_buffer[0], blip_samples, count ); \ + LOOP4( count, \ + sample = blip_samples[n++]; \ + *dst++ += MULSC( sample, lvol ); \ + *dst++ += MULSC( sample, rvol ); \ + UPDATE_VOLUME( volume_left, lvol ); \ + UPDATE_VOLUME( volume_right, rvol ); \ + ); \ } #define MONO_DEST_MIX_LINEAR(op, upd, o0, o1) { \ *dst++ op MULSC(LINEAR(x[o0], x[o1]), vol); \ @@ -208,16 +253,51 @@ DUMB_RESAMPLER *dumb_start_resampler(SRCTYPE *src, int src_channels, int32 pos, if ( volume_right ) volume_right->volume = (float)rvolr / 16777216.0f; \ } #define MONO_DEST_VOLUMES_ARE_ZERO (lvol == 0 && lvolt == 0 && rvol == 0 && rvolt == 0) -#define MONO_DEST_MIX_ALIAS(op, upd, offset) { \ - *dst++ op ALIAS(x[(offset)*2], lvol) + ALIAS(x[(offset)*2+1], rvol); \ - if ( upd ) UPDATE_VOLUME( volume_left, lvol ); \ - if ( upd ) UPDATE_VOLUME( volume_right, rvol ); \ +#define POKE_ALIAS(offset) { \ + int deltal = ALIAS(x[(offset)*2+0]) - resampler->last_amp[0]; \ + int deltar = ALIAS(x[(offset)*2+1]) - resampler->last_amp[1]; \ + resampler->last_amp[0] += deltal; \ + resampler->last_amp[1] += deltar; \ + if ( deltal ) blip_add_delta( resampler->blip_buffer[0], resampler->last_clock, deltal ); \ + if ( deltar ) blip_add_delta( resampler->blip_buffer[1], resampler->last_clock, deltar ); \ + resampler->last_clock += inv_dt; \ } -#define STEREO_DEST_MIX_ALIAS(op, upd, offset) { \ - *dst++ op ALIAS(x[(offset)*2], lvol); \ - *dst++ op ALIAS(x[(offset)*2+1], rvol); \ - if ( upd ) UPDATE_VOLUME( volume_left, lvol ); \ - if ( upd ) UPDATE_VOLUME( volume_right, rvol ); \ +#define MONO_DEST_PEEK_ALIAS { \ + *dst = MULSC( blip_peek_sample( resampler->blip_buffer[0] ), lvol ) + \ + MULSC( blip_peek_sample( resampler->blip_buffer[1] ), rvol ); \ +} +#define MONO_DEST_MIX_ALIAS(count) { \ + int n = 0; \ + resampler->last_clock -= count * 65536; \ + blip_end_frame( resampler->blip_buffer[0], count * 65536 ); \ + blip_end_frame( resampler->blip_buffer[1], count * 65536 ); \ + blip_read_samples( resampler->blip_buffer[0], blip_samples, count ); \ + blip_read_samples( resampler->blip_buffer[1], blip_samples + 256, count ); \ + LOOP4( count, \ + *dst++ += MULSC( blip_samples[n], lvol ) + MULSC( blip_samples[256+n], rvol ); \ + n++; \ + UPDATE_VOLUME( volume_left, lvol ); \ + UPDATE_VOLUME( volume_right, rvol ); \ + ); \ +} +#define STEREO_DEST_PEEK_ALIAS { \ + *dst++ = MULSC( blip_peek_sample( resampler->blip_buffer[0] ), lvol ); \ + *dst++ = MULSC( blip_peek_sample( resampler->blip_buffer[1] ), rvol ); \ +} +#define STEREO_DEST_MIX_ALIAS(count) { \ + int n = 0; \ + resampler->last_clock -= count * 65536; \ + blip_end_frame( resampler->blip_buffer[0], count * 65536 ); \ + blip_end_frame( resampler->blip_buffer[1], count * 65536 ); \ + blip_read_samples( resampler->blip_buffer[0], blip_samples, count ); \ + blip_read_samples( resampler->blip_buffer[1], blip_samples + 256, count ); \ + LOOP4( count, \ + *dst++ += MULSC( blip_samples[n], lvol); \ + *dst++ += MULSC( blip_samples[256+n], rvol); \ + n++; \ + UPDATE_VOLUME( volume_left, lvol ); \ + UPDATE_VOLUME( volume_right, rvol ); \ + ); \ } #define MONO_DEST_MIX_LINEAR(op, upd, o0, o1) { \ *dst++ op MULSC(LINEAR(x[(o0)*2], x[(o1)*2]), lvol) + MULSC(LINEAR(x[(o0)*2+1], x[(o1)*2+1]), rvol); \ diff --git a/dumb/src/it/itmisc.c b/dumb/src/it/itmisc.c index 1045af8fa..5a45d3192 100644 --- a/dumb/src/it/itmisc.c +++ b/dumb/src/it/itmisc.c @@ -24,7 +24,7 @@ DUMB_IT_SIGDATA *DUMBEXPORT duh_get_it_sigdata(DUH *duh) { - return duh_get_raw_sigdata(duh, 0, SIGTYPE_IT); + return duh_get_raw_sigdata(duh, -1, SIGTYPE_IT); } diff --git a/dumb/src/it/itread.c b/dumb/src/it/itread.c index b06fbbca5..1f29ad4d9 100644 --- a/dumb/src/it/itread.c +++ b/dumb/src/it/itread.c @@ -31,18 +31,71 @@ //#define INVESTIGATE_OLD_INSTRUMENTS - -static int it_seek(DUMBFILE *f, int32 offset) +typedef struct tdumbfile_mem_status { - int32 pos = dumbfile_pos(f); + const unsigned char * ptr; + unsigned offset, size; +} dumbfile_mem_status; - if (pos > offset) +static int dumbfile_mem_skip(void * f, int32 n) +{ + dumbfile_mem_status * s = (dumbfile_mem_status *) f; + s->offset += n; + if (s->offset > s->size) + { + s->offset = s->size; + return 1; + } + + return 0; +} + + + +static int dumbfile_mem_getc(void * f) +{ + dumbfile_mem_status * s = (dumbfile_mem_status *) f; + if (s->offset < s->size) + { + return *(s->ptr + s->offset++); + } + return -1; +} + + + +static int32 dumbfile_mem_getnc(char * ptr, int32 n, void * f) +{ + dumbfile_mem_status * s = (dumbfile_mem_status *) f; + int32 max = s->size - s->offset; + if (max > n) max = n; + if (max) + { + memcpy(ptr, s->ptr + s->offset, max); + s->offset += max; + } + return max; +} + + + +static DUMBFILE_SYSTEM mem_dfs = { + NULL, // open + &dumbfile_mem_skip, + &dumbfile_mem_getc, + &dumbfile_mem_getnc, + NULL // close +}; + + + +static int it_seek(dumbfile_mem_status * s, int32 offset) +{ + if ( (unsigned)offset > s->size ) return -1; - if (pos < offset) - if (dumbfile_skip(f, offset - pos)) - return -1; - + s->offset = offset; + return 0; } @@ -306,6 +359,8 @@ static int it_read_envelope(IT_ENVELOPE *envelope, DUMBFILE *f) envelope->loop_end = dumbfile_getc(f); envelope->sus_loop_start = dumbfile_getc(f); envelope->sus_loop_end = dumbfile_getc(f); + if (envelope->n_nodes > 25) + envelope->n_nodes = 25; for (n = 0; n < envelope->n_nodes; n++) { envelope->node_y[n] = dumbfile_getc(f); envelope->node_t[n] = dumbfile_igetw(f); @@ -629,7 +684,7 @@ int32 _dumb_it_read_sample_data_adpcm4(IT_SAMPLE *sample, DUMBFILE *f) signed char * ptr, * end; signed char compression_table[16]; if (dumbfile_getnc(compression_table, 16, f) != 16) - return -1; + return -1; ptr = (signed char *) sample->data; delta = 0; @@ -682,15 +737,36 @@ static int32 it_read_sample_data(int cmwt, IT_SAMPLE *sample, unsigned char conv else decompress8(f, sample->data, datasize, ((cmwt >= 0x215) && (convert & 4))); } else if (sample->flags & IT_SAMPLE_16BIT) { - if (convert & 2) + if (sample->flags & IT_SAMPLE_STEREO) { + if (convert & 2) { + for (n = 0; n < datasize; n += 2) + ((short *)sample->data)[n] = dumbfile_mgetw(f); + for (n = 1; n < datasize; n += 2) + ((short *)sample->data)[n] = dumbfile_mgetw(f); + } else { + for (n = 0; n < datasize; n += 2) + ((short *)sample->data)[n] = dumbfile_igetw(f); + for (n = 1; n < datasize; n += 2) + ((short *)sample->data)[n] = dumbfile_igetw(f); + } + } else { + if (convert & 2) + for (n = 0; n < datasize; n++) + ((short *)sample->data)[n] = dumbfile_mgetw(f); + else + for (n = 0; n < datasize; n++) + ((short *)sample->data)[n] = dumbfile_igetw(f); + } + } else { + if (sample->flags & IT_SAMPLE_STEREO) { + for (n = 0; n < datasize; n += 2) + ((signed char *)sample->data)[n] = dumbfile_getc(f); + for (n = 1; n < datasize; n += 2) + ((signed char *)sample->data)[n] = dumbfile_getc(f); + } else for (n = 0; n < datasize; n++) - ((short *)sample->data)[n] = dumbfile_mgetw(f); - else - for (n = 0; n < datasize; n++) - ((short *)sample->data)[n] = dumbfile_igetw(f); - } else - for (n = 0; n < datasize; n++) - ((signed char *)sample->data)[n] = dumbfile_getc(f); + ((signed char *)sample->data)[n] = dumbfile_getc(f); + } if (dumbfile_error(f)) return -1; @@ -934,13 +1010,58 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) unsigned char *buffer; + unsigned char *file_buffer = NULL; + unsigned int file_size = 0; + int block_size; + + dumbfile_mem_status memdata; + + do + { + void * temp = realloc( file_buffer, file_size + 32768 ); + if ( !temp ) + { + if ( file_buffer ) free( file_buffer ); + return NULL; + } + file_buffer = temp; + block_size = dumbfile_getnc( file_buffer + file_size, 32768, f ); + if ( block_size < 0 ) + { + free( file_buffer ); + return NULL; + } + file_size += block_size; + } + while ( block_size == 32768 ); + + memdata.ptr = file_buffer; + memdata.offset = 0; + memdata.size = file_size; + + f = dumbfile_open_ex(&memdata, &mem_dfs); + + if ( !f ) + { + free( file_buffer ); + return NULL; + } + if (dumbfile_mgetl(f) != IT_SIGNATURE) + { + dumbfile_close(f); + free(file_buffer); return NULL; + } sigdata = malloc(sizeof(*sigdata)); if (!sigdata) + { + dumbfile_close(f); + free(file_buffer); return NULL; + } sigdata->song_message = NULL; sigdata->order = NULL; @@ -989,12 +1110,16 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) // XXX sample count if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_instruments > 256 || sigdata->n_samples > 4000 || sigdata->n_patterns > 256) { _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } sigdata->order = malloc(sigdata->n_orders); if (!sigdata->order) { _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1002,6 +1127,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) sigdata->instrument = malloc(sigdata->n_instruments * sizeof(*sigdata->instrument)); if (!sigdata->instrument) { _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } } @@ -1010,6 +1137,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); if (!sigdata->sample) { _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } for (n = 0; n < sigdata->n_samples; n++) @@ -1020,6 +1149,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); if (!sigdata->pattern) { _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } for (n = 0; n < sigdata->n_patterns; n++) @@ -1032,6 +1163,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) component = malloc(769 * sizeof(*component)); if (!component) { _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1076,6 +1209,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) if (dumbfile_error(f)) { free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1096,6 +1231,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) if (!sigdata->midi) { free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; // Should we be happy with this outcome in some situations? } @@ -1104,6 +1241,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) if (dumbfile_error(f) || dumbfile_skip(f, 8*i)) { free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } /* Read embedded MIDI configuration */ @@ -1111,6 +1250,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) if (dumbfile_skip(f, 32*9)) { free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } for (i = 0; i < 16; i++) { @@ -1119,6 +1260,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) if (dumbfile_getnc(mididata, 32, f) < 32) { free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } sigdata->midi->SFmacroz[i] = 0; @@ -1174,12 +1317,14 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) sigdata->flags &= IT_REAL_FLAGS; - qsort(component, n_components, sizeof(IT_COMPONENT), &it_component_compare); + qsort(component, n_components, sizeof(IT_COMPONENT), &it_component_compare); buffer = malloc(65536); if (!buffer) { free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1208,10 +1353,12 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) continue; } - if (it_seek(f, component[n].offset)) { + if (it_seek(&memdata, component[n].offset)) { free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1227,6 +1374,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } sigdata->song_message[message_length] = 0; @@ -1243,6 +1392,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } break; @@ -1252,6 +1403,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } break; @@ -1261,6 +1414,8 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1287,10 +1442,12 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) m = component[n].sampfirst; while (m >= 0) { - if (it_seek(f, component[m].offset)) { + if (it_seek(&memdata, component[m].offset)) { free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } @@ -1298,16 +1455,79 @@ static sigdata_t *it_load_sigdata(DUMBFILE *f) free(buffer); free(component); _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + free(file_buffer); return NULL; } m = component[m].sampnext; } - } + } - free(buffer); + for ( n = 0; n < 10; n++ ) + { + if ( dumbfile_getc( f ) == 'X' ) + { + if ( dumbfile_getc( f ) == 'T' ) + { + if ( dumbfile_getc( f ) == 'P' ) + { + if ( dumbfile_getc( f ) == 'M' ) + { + break; + } + } + } + } + } + + if ( !dumbfile_error( f ) && n < 10 ) + { + unsigned int mptx_id = dumbfile_igetl( f ); + while ( !dumbfile_error( f ) && mptx_id != DUMB_ID('M','P','T','S') ) + { + unsigned int size = dumbfile_igetw( f ); + switch (mptx_id) + { + /* TODO: Add instrument extension readers */ + + default: + dumbfile_skip(f, size * sigdata->n_instruments); + break; + } + + mptx_id = dumbfile_igetl( f ); + } + + mptx_id = dumbfile_igetl( f ); + while ( memdata.offset < file_size ) + { + unsigned int size = dumbfile_igetw( f ); + switch (mptx_id) + { + /* TODO: Add more song extension readers */ + + case DUMB_ID('D','T','.','.'): + if ( size == 2 ) + sigdata->tempo = dumbfile_igetw( f ); + else if ( size == 4 ) + sigdata->tempo = dumbfile_igetl( f ); + break; + + default: + dumbfile_skip(f, size); + break; + } + mptx_id = dumbfile_igetl( f ); + } + } + + free(buffer); free(component); + dumbfile_close(f); + free(file_buffer); + _dumb_it_fix_invalid_orders(sigdata); return sigdata; @@ -1327,9 +1547,11 @@ DUH *DUMBEXPORT dumb_read_it_quick(DUMBFILE *f) return NULL; { - const char *tag[1][2]; + const char *tag[2][2]; tag[0][0] = "TITLE"; tag[0][1] = ((DUMB_IT_SIGDATA *)sigdata)->name; - return make_duh(-1, 1, (const char *const (*)[2])tag, 1, &descptr, &sigdata); + tag[1][0] = "FORMAT"; + tag[1][1] = "IT"; + return make_duh(-1, 2, (const char *const (*)[2])tag, 1, &descptr, &sigdata); } } diff --git a/dumb/src/it/itrender.c b/dumb/src/it/itrender.c index 6ea048278..4f3d50dfa 100644 --- a/dumb/src/it/itrender.c +++ b/dumb/src/it/itrender.c @@ -29,15 +29,38 @@ #define END_RAMPING #define RAMP_DOWN -static IT_PLAYING *alloc_playing(DUMB_IT_SIGRENDERER *itsr) +static IT_PLAYING *new_playing(DUMB_IT_SIGRENDERER *itsr) { + IT_PLAYING *r; + if (itsr->free_playing != NULL) { - IT_PLAYING *pl = itsr->free_playing; - itsr->free_playing = pl->next; - return pl; + r = itsr->free_playing; + itsr->free_playing = r->next; + blip_clear(r->resampler.blip_buffer[0]); + blip_clear(r->resampler.blip_buffer[1]); + return r; } - return (IT_PLAYING *)malloc(sizeof(IT_PLAYING)); + r = (IT_PLAYING *)malloc(sizeof(IT_PLAYING)); + if (r) + { + r->resampler.blip_buffer[0] = blip_new( 256 ); + if ( !r->resampler.blip_buffer[0] ) + { + free( r ); + return NULL; + } + r->resampler.blip_buffer[1] = blip_new( 256 ); + if ( !r->resampler.blip_buffer[1] ) + { + free( r->resampler.blip_buffer[0] ); + free( r ); + return NULL; + } + blip_set_rates(r->resampler.blip_buffer[0], 65536, 1); + blip_set_rates(r->resampler.blip_buffer[1], 65536, 1); + } + return r; } static void free_playing(DUMB_IT_SIGRENDERER *itsr, IT_PLAYING *playing) @@ -46,6 +69,13 @@ static void free_playing(DUMB_IT_SIGRENDERER *itsr, IT_PLAYING *playing) itsr->free_playing = playing; } +static void free_playing_orig(IT_PLAYING * r) +{ + blip_delete( r->resampler.blip_buffer[1] ); + blip_delete( r->resampler.blip_buffer[0] ); + free( r ); +} + static IT_PLAYING *dup_playing(IT_PLAYING *src, IT_CHANNEL *dstchannel, IT_CHANNEL *srcchannel) { IT_PLAYING *dst; @@ -135,6 +165,19 @@ static IT_PLAYING *dup_playing(IT_PLAYING *src, IT_CHANNEL *dstchannel, IT_CHANN dst->resampler = src->resampler; dst->resampler.pickup_data = dst; + dst->resampler.blip_buffer[0] = blip_dup( dst->resampler.blip_buffer[0] ); + if ( !dst->resampler.blip_buffer[0] ) + { + free( dst ); + return NULL; + } + dst->resampler.blip_buffer[1] = blip_dup( dst->resampler.blip_buffer[1] ); + if ( !dst->resampler.blip_buffer[1] ) + { + blip_delete( dst->resampler.blip_buffer[0] ); + free( dst ); + return NULL; + } dst->time_lost = src->time_lost; //dst->output = src->output; @@ -175,6 +218,7 @@ static void dup_channel(IT_CHANNEL *dst, IT_CHANNEL *src) dst->new_note_action = src->new_note_action; dst->arpeggio = src->arpeggio; + dst->arpeggio_shift = src->arpeggio_shift; dst->retrig = src->retrig; dst->xm_retrig = src->xm_retrig; dst->retrig_tick = src->retrig_tick; @@ -192,6 +236,7 @@ static void dup_channel(IT_CHANNEL *dst, IT_CHANNEL *src) dst->last_toneslide_tick = src->last_toneslide_tick; dst->ptm_toneslide = src->ptm_toneslide; dst->ptm_last_toneslide = src->ptm_last_toneslide; + dst->okt_toneslide = src->okt_toneslide; dst->destnote = src->destnote; dst->glissando = src->glissando; @@ -231,6 +276,10 @@ static void dup_channel(IT_CHANNEL *dst, IT_CHANNEL *src) dst->xm_lastX1 = src->xm_lastX1; dst->xm_lastX2 = src->xm_lastX2; + dst->inv_loop_delay = src->inv_loop_delay; + dst->inv_loop_speed = src->inv_loop_speed; + dst->inv_loop_offset = src->inv_loop_offset; + dst->playing = dup_playing(src->playing, dst, src); @@ -760,6 +809,7 @@ static void reset_channel_effects(IT_CHANNEL *channel) channel->panslide = 0; channel->channelvolslide = 0; channel->arpeggio = 0; + channel->arpeggio_shift = 0; channel->retrig = 0; if (channel->xm_retrig) { channel->xm_retrig = 0; @@ -775,6 +825,7 @@ static void reset_channel_effects(IT_CHANNEL *channel) channel->ptm_last_toneslide = 0; channel->ptm_toneslide = 0; channel->toneslide_tick = 0; + channel->okt_toneslide = 0; if (channel->playing) { channel->playing->vibrato_n = 0; channel->playing->tremolo_speed = 0; @@ -854,6 +905,15 @@ static void it_pickup_stop_at_end(DUMB_RESAMPLER *resampler, void *data) +static void it_pickup_stop_after_reverse(DUMB_RESAMPLER *resampler, void *data) +{ + (void)data; + + resampler->dir = 0; +} + + + static void it_playing_update_resamplers(IT_PLAYING *playing) { if ((playing->sample->flags & IT_SAMPLE_SUS_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) { @@ -874,8 +934,13 @@ static void it_playing_update_resamplers(IT_PLAYING *playing) playing->resampler.pickup = &it_pickup_pingpong_loop; else playing->resampler.pickup = &it_pickup_loop; - } else { - if (playing->sample->flags & IT_SAMPLE_SUS_LOOP) + } else if (playing->flags & IT_PLAYING_REVERSE) { + playing->resampler.start = 0; + playing->resampler.end = playing->sample->length; + playing->resampler.dir = -1; + playing->resampler.pickup = &it_pickup_stop_after_reverse; + } else { + if (playing->sample->flags & IT_SAMPLE_SUS_LOOP) playing->resampler.start = playing->sample->sus_loop_start; else playing->resampler.start = 0; @@ -981,6 +1046,15 @@ static void update_retrig(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *channel) } +static void update_smooth_effects_playing(IT_PLAYING *playing) +{ + playing->vibrato_time += playing->vibrato_n * + (playing->vibrato_speed << 2); + playing->tremolo_time += playing->tremolo_speed << 2; + playing->panbrello_time += playing->panbrello_speed; + if (playing->panbrello_waveform == 3) + playing->panbrello_random = (rand() % 129) - 64; +} static void update_smooth_effects(DUMB_IT_SIGRENDERER *sigrenderer) { @@ -991,12 +1065,42 @@ static void update_smooth_effects(DUMB_IT_SIGRENDERER *sigrenderer) IT_PLAYING *playing = channel->playing; if (playing) { - playing->vibrato_time += playing->vibrato_n * - (playing->vibrato_speed << 2); - playing->tremolo_time += playing->tremolo_speed << 2; - playing->panbrello_time += playing->panbrello_speed; - if (playing->panbrello_waveform == 3) - playing->panbrello_random = (rand() % 129) - 64; + update_smooth_effects_playing(playing); + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + IT_PLAYING *playing = sigrenderer->playing[i]; + + if (playing) { + update_smooth_effects_playing(playing); + } + } +} + + +static const unsigned char pt_tab_invloop[16] = +{ + 0x00, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D, + 0x0F, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80 +}; + +static void update_invert_loop(IT_CHANNEL *channel, IT_SAMPLE *sample) +{ + channel->inv_loop_delay += pt_tab_invloop[channel->inv_loop_speed]; + if (channel->inv_loop_delay >= 0x80) + { + channel->inv_loop_delay = 0; + + if (sample && ((sample->flags & (IT_SAMPLE_EXISTS | IT_SAMPLE_LOOP)) == (IT_SAMPLE_EXISTS | IT_SAMPLE_LOOP)) && !(sample->flags & (IT_SAMPLE_STEREO | IT_SAMPLE_16BIT))) + { + if (sample->loop_end - sample->loop_start >= 4) + { + channel->inv_loop_offset++; + if (channel->inv_loop_offset >= (sample->loop_end - sample->loop_start)) channel->inv_loop_offset = 0; + + ((char *)sample->data)[sample->loop_start + channel->inv_loop_offset] ^= 0xFF; + } } } } @@ -1005,7 +1109,7 @@ static void update_smooth_effects(DUMB_IT_SIGRENDERER *sigrenderer) static void update_effects(DUMB_IT_SIGRENDERER *sigrenderer) { - int i; + int i, j; if (sigrenderer->globalvolslide) { sigrenderer->globalvolume += sigrenderer->globalvolslide; @@ -1042,10 +1146,11 @@ static void update_effects(DUMB_IT_SIGRENDERER *sigrenderer) } if (channel->volslide) { + int clip = (sigrenderer->sigdata->flags & IT_WAS_AN_S3M) ? 63 : 64; channel->volume += channel->volslide; - if (channel->volume > 64) { + if (channel->volume > clip) { if (channel->volslide >= 0) - channel->volume = 64; + channel->volume = clip; else channel->volume = 0; } @@ -1088,81 +1193,135 @@ static void update_effects(DUMB_IT_SIGRENDERER *sigrenderer) } if (channel->playing) channel->playing->channel_volume = channel->channelvolume; + for (j = 0; j < DUMB_IT_N_NNA_CHANNELS; j++) { + if (sigrenderer->playing[j] && sigrenderer->playing[j]->channel == channel) + sigrenderer->playing[j]->channel_volume = channel->channelvolume; + } } update_tremor(channel); - channel->arpeggio = (channel->arpeggio << 4) | (channel->arpeggio >> 8); - channel->arpeggio &= 0xFFF; + if (channel->arpeggio_shift) channel->arpeggio = (channel->arpeggio << 8) | (channel->arpeggio >> channel->arpeggio_shift); update_retrig(sigrenderer, channel); - if (playing) { - playing->slide += channel->portamento; + if (channel->inv_loop_speed) update_invert_loop(channel, playing ? playing->sample : NULL); - if (channel->ptm_toneslide) { + for (j = 0; j < DUMB_IT_N_NNA_CHANNELS; j++) { + if (sigrenderer->playing[j] && sigrenderer->playing[j]->channel == channel) break; + } + + if (playing || j < DUMB_IT_N_NNA_CHANNELS) { + if (playing) playing->slide += channel->portamento; + for (j = 0; j < DUMB_IT_N_NNA_CHANNELS; j++) { + if (sigrenderer->playing[j] && sigrenderer->playing[j]->channel == channel) + sigrenderer->playing[j]->slide += channel->portamento; + } + + if (channel->okt_toneslide) { + if (channel->okt_toneslide--) { + if (playing) { + playing->note += channel->toneslide; + if (playing->note >= 120) { + if (channel->toneslide < 0) playing->note = 0; + else playing->note = 119; + } + } + for (j = 0; j < DUMB_IT_N_NNA_CHANNELS; j++) { + if (sigrenderer->playing[j] && sigrenderer->playing[j]->channel == channel) { + IT_PLAYING *playing = sigrenderer->playing[j]; + playing->note += channel->toneslide; + if (playing->note >= 120) { + if (channel->toneslide < 0) playing->note = 0; + else playing->note = 119; + } + } + } + } + } else if (channel->ptm_toneslide) { if (--channel->toneslide_tick == 0) { channel->toneslide_tick = channel->ptm_toneslide; - playing->note += channel->toneslide; - if (playing->note >= 120) { - if (channel->toneslide < 0) playing->note = 0; - else playing->note = 119; - } - channel->note = channel->truenote = playing->note; - if (channel->toneslide_retrig) { - it_playing_reset_resamplers(playing, 0); + if (playing) { + playing->note += channel->toneslide; + if (playing->note >= 120) { + if (channel->toneslide < 0) playing->note = 0; + else playing->note = 119; + } + channel->note = channel->truenote = playing->note; + if (channel->toneslide_retrig) { + it_playing_reset_resamplers(playing, 0); #ifdef END_RAMPING - playing->declick_stage = 0; - playing->declick_volume = 1.f / 256.f; + playing->declick_stage = 0; + playing->declick_volume = 1.f / 256.f; #endif + } + } + for (j = 0; j < DUMB_IT_N_NNA_CHANNELS; j++) { + if (sigrenderer->playing[j] && sigrenderer->playing[j]->channel == channel) { + IT_PLAYING *playing = sigrenderer->playing[j]; + playing->note += channel->toneslide; + if (playing->note >= 120) { + if (channel->toneslide < 0) playing->note = 0; + else playing->note = 119; + } + if (channel->toneslide_retrig) { + it_playing_reset_resamplers(playing, 0); +#ifdef END_RAMPING + playing->declick_stage = 0; + playing->declick_volume = 1.f / 256.f; +#endif + } + } } } } - if (sigrenderer->sigdata->flags & IT_LINEAR_SLIDES) { - if (channel->toneporta && channel->destnote < 120) { - int currpitch = ((playing->note - 60) << 8) + playing->slide; - int destpitch = (channel->destnote - 60) << 8; - if (currpitch > destpitch) { - currpitch -= channel->toneporta; - if (currpitch < destpitch) { - currpitch = destpitch; - channel->destnote = IT_NOTE_OFF; - } - } else if (currpitch < destpitch) { - currpitch += channel->toneporta; + if (playing) { + if (sigrenderer->sigdata->flags & IT_LINEAR_SLIDES) { + if (channel->toneporta && channel->destnote < 120) { + int currpitch = ((playing->note - 60) << 8) + playing->slide; + int destpitch = (channel->destnote - 60) << 8; if (currpitch > destpitch) { - currpitch = destpitch; - channel->destnote = IT_NOTE_OFF; + currpitch -= channel->toneporta; + if (currpitch < destpitch) { + currpitch = destpitch; + channel->destnote = IT_NOTE_OFF; + } + } else if (currpitch < destpitch) { + currpitch += channel->toneporta; + if (currpitch > destpitch) { + currpitch = destpitch; + channel->destnote = IT_NOTE_OFF; + } } + playing->slide = currpitch - ((playing->note - 60) << 8); } - playing->slide = currpitch - ((playing->note - 60) << 8); - } - } else { - if (channel->toneporta && channel->destnote < 120) { - float amiga_multiplier = playing->sample->C5_speed * (1.0f / AMIGA_DIVISOR); + } else { + if (channel->toneporta && channel->destnote < 120) { + float amiga_multiplier = playing->sample->C5_speed * (1.0f / AMIGA_DIVISOR); - float deltanote = (float)pow(DUMB_SEMITONE_BASE, 60 - playing->note); - /* deltanote is 1.0 for C-5, 0.5 for C-6, etc. */ + float deltanote = (float)pow(DUMB_SEMITONE_BASE, 60 - playing->note); + /* deltanote is 1.0 for C-5, 0.5 for C-6, etc. */ - float deltaslid = deltanote - playing->slide * amiga_multiplier; + float deltaslid = deltanote - playing->slide * amiga_multiplier; - float destdelta = (float)pow(DUMB_SEMITONE_BASE, 60 - channel->destnote); - if (deltaslid < destdelta) { - playing->slide -= channel->toneporta; - deltaslid = deltanote - playing->slide * amiga_multiplier; - if (deltaslid > destdelta) { - playing->note = channel->destnote; - playing->slide = 0; - channel->destnote = IT_NOTE_OFF; - } - } else { - playing->slide += channel->toneporta; - deltaslid = deltanote - playing->slide * amiga_multiplier; + float destdelta = (float)pow(DUMB_SEMITONE_BASE, 60 - channel->destnote); if (deltaslid < destdelta) { - playing->note = channel->destnote; - playing->slide = 0; - channel->destnote = IT_NOTE_OFF; + playing->slide -= channel->toneporta; + deltaslid = deltanote - playing->slide * amiga_multiplier; + if (deltaslid > destdelta) { + playing->note = channel->destnote; + playing->slide = 0; + channel->destnote = IT_NOTE_OFF; + } + } else { + playing->slide += channel->toneporta; + deltaslid = deltanote - playing->slide * amiga_multiplier; + if (deltaslid < destdelta) { + playing->note = channel->destnote; + playing->slide = 0; + channel->destnote = IT_NOTE_OFF; + } } } } @@ -1199,8 +1358,14 @@ static int update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY * case IT_S: { unsigned char effectvalue = entry->effectvalue; - if (effectvalue == 0) - effectvalue = channel->lastS; + if (sigrenderer->sigdata->flags & IT_WAS_AN_S3M) { + if (effectvalue == 0) + effectvalue = channel->lastDKL; + channel->lastDKL = effectvalue; + } else { + if (effectvalue == 0) + effectvalue = channel->lastS; + } channel->lastS = effectvalue; switch (effectvalue >> 4) { case IT_S_PATTERN_LOOP: @@ -1211,6 +1376,8 @@ static int update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY * if (!channel->played_patjump) channel->played_patjump = bit_array_create(256); else { + if ( channel->played_patjump_order != 0xFFFE && channel->played_patjump_order != sigrenderer->order ) + bit_array_merge(sigrenderer->played, channel->played_patjump, channel->played_patjump_order * 256); //if (channel->played_patjump_order != sigrenderer->order) bit_array_reset(channel->played_patjump); } @@ -1225,13 +1392,13 @@ static int update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY * int n; bit_array_destroy(channel->played_patjump); channel->played_patjump = bit_array_create(256); - for (n = 0; n < 256; n++) + for (n = channel->pat_loop_row; n <= sigrenderer->row; n++) bit_array_clear(sigrenderer->played, sigrenderer->order * 256 + n); channel->played_patjump_order = sigrenderer->order; } else if (channel->played_patjump_order == sigrenderer->order) { bit_array_set(channel->played_patjump, sigrenderer->row); bit_array_mask(sigrenderer->played, channel->played_patjump, channel->played_patjump_order * 256); - bit_array_reset(channel->played_patjump); + //bit_array_reset(channel->played_patjump); } #endif channel->pat_loop_count = v; @@ -1258,7 +1425,7 @@ static int update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY * if (channel->played_patjump_order == sigrenderer->order) { bit_array_set(channel->played_patjump, sigrenderer->row); bit_array_mask(sigrenderer->played, channel->played_patjump, channel->played_patjump_order * 256); - bit_array_reset(channel->played_patjump); + //bit_array_reset(channel->played_patjump); } #endif sigrenderer->breakrow = channel->pat_loop_row; @@ -1290,8 +1457,8 @@ static int update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY * - If we jump, then effect a loop using an old E60, and then the pattern ends, the next pattern starts on the row corresponding to the E60. - Theory: breakrow is not cleared when it's a pattern loop effect! */ - //if (sigrenderer->processrow < 0xFFFE) // I have no idea if this is correct or not - FT2 is so weird :( - // sigrenderer->breakrow = channel->pat_loop_row; /* emulate bug in FT2 */ + if ((sigrenderer->processrow | 0xC00) < 0xFFFE) // I have no idea if this is correct or not - FT2 is so weird :( + sigrenderer->breakrow = channel->pat_loop_row; /* emulate bug in FT2 */ } else channel->pat_loop_row = sigrenderer->processrow + 1; #ifdef BIT_ARRAY_BULLSHIT @@ -1419,7 +1586,8 @@ static void it_note_off(IT_PLAYING *playing) static void xm_note_off(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) { if (channel->playing) { - if (!(sigdata->instrument[channel->instrument-1].volume_envelope.flags & IT_ENVELOPE_ON)) + if (!channel->instrument || channel->instrument > sigdata->n_instruments || + !(sigdata->instrument[channel->instrument-1].volume_envelope.flags & IT_ENVELOPE_ON)) //if (!(entry->mask & IT_ENTRY_INSTRUMENT)) // dunno what that was there for ... channel->volume = 0; @@ -1467,7 +1635,7 @@ static void it_retrigger_note(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *chan nna = channel->playing->instrument->new_note_action; #endif - if ((sigdata->flags & IT_USE_INSTRUMENTS) && (channel->playing->enabled_envelopes) && channel->playing->instnum == channel->instrument) { + if (!(channel->playing->flags & IT_PLAYING_DEAD) && (sigdata->flags & IT_USE_INSTRUMENTS) && (channel->playing->enabled_envelopes) && channel->playing->instnum == channel->instrument) { IT_PLAYING * playing = channel->playing; IT_INSTRUMENT * inst = &sigdata->instrument[channel->instrument-1]; if ((playing->enabled_envelopes & IT_ENV_VOLUME) && (inst->volume_envelope.flags & IT_ENVELOPE_CARRY)) { @@ -1523,7 +1691,7 @@ static void it_retrigger_note(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *chan for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { IT_PLAYING * playing = sigrenderer->playing[i]; if (playing && playing->channel == channel && playing->instrument->dup_check_type) { - int match = 0; + int match = 1; switch (playing->instrument->dup_check_type) { case DCT_NOTE: @@ -1580,7 +1748,7 @@ static void it_retrigger_note(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *chan if (channel->playing) free_playing(sigrenderer, channel->playing); - channel->playing = alloc_playing(sigrenderer); + channel->playing = new_playing(sigrenderer); if (!channel->playing) return; @@ -1588,7 +1756,8 @@ static void it_retrigger_note(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *chan if (!flags && sigdata->flags & IT_USE_INSTRUMENTS) { for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { IT_PLAYING * playing = sigrenderer->playing[i]; - if (playing && (playing->enabled_envelopes) && playing->instnum == channel->instrument) { + if (!playing || playing->channel != channel) continue; + if (playing->enabled_envelopes && playing->instnum == channel->instrument) { IT_INSTRUMENT * inst = &sigdata->instrument[channel->instrument-1]; if ((playing->enabled_envelopes & IT_ENV_VOLUME) && (inst->volume_envelope.flags & IT_ENVELOPE_CARRY)) { flags |= 1; @@ -1878,10 +2047,19 @@ static void xm_envelope_calculate_value(IT_ENVELOPE *envelope, IT_PLAYING_ENVELO extern const char xm_convert_vibrato[]; +const char mod_convert_vibrato[] = { + IT_VIBRATO_SINE, + IT_VIBRATO_RAMP_UP, /* this will be inverted by IT_OLD_EFFECTS */ + IT_VIBRATO_XM_SQUARE, + IT_VIBRATO_XM_SQUARE +}; + /* Returns 1 if a callback caused termination of playback. */ static int process_effects(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry, int ignore_cxx) { DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + IT_PLAYING *playing; + int i; IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; @@ -1912,7 +2090,18 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than /*if (entry->effectvalue == 255) if (sigrenderer->callbacks->xm_speed_zero && (*sigrenderer->callbacks->xm_speed_zero)(sigrenderer->callbacks->xm_speed_zero_data)) return 1;*/ - sigrenderer->tick = sigrenderer->speed = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_STM) + { + int n = entry->effectvalue; + if (n >= 32) + { + sigrenderer->tick = sigrenderer->speed = n; + } + } + else + { + sigrenderer->tick = sigrenderer->speed = entry->effectvalue; + } } else if ((sigdata->flags & (IT_WAS_AN_XM|IT_WAS_A_MOD)) == IT_WAS_AN_XM) { #ifdef BIT_ARRAY_BULLSHIT @@ -1938,10 +2127,17 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than break; case IT_VOLSLIDE_VIBRATO: - if (channel->playing) { - channel->playing->vibrato_speed = channel->lastHspeed; - channel->playing->vibrato_depth = channel->lastHdepth; - channel->playing->vibrato_n++; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) { + playing->vibrato_speed = channel->lastHspeed; + playing->vibrato_depth = channel->lastHdepth; + playing->vibrato_n++; + } } /* Fall through and process volume slide. */ case IT_VOLUME_SLIDE: @@ -1954,24 +2150,43 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than v = channel->lastDKL; channel->lastDKL = v; } - if ((v & 0x0F) == 0) { /* Dx0 */ - channel->volslide = v >> 4; - if (channel->volslide == 15 && !(sigdata->flags & IT_WAS_AN_XM)) { - channel->volume += 15; - if (channel->volume > 64) channel->volume = 64; + if (!(sigdata->flags & IT_WAS_AN_XM)) { + int clip = (sigdata->flags & IT_WAS_AN_S3M) ? 63 : 64; + if ((v & 0x0F) == 0x0F) { + if (!(v & 0xF0)) { + channel->volslide = -15; + channel->volume -= 15; + if (channel->volume > clip) channel->volume = 0; + } else { + channel->volume += v >> 4; + if (channel->volume > clip) channel->volume = clip; + } + } else if ((v & 0xF0) == 0xF0) { + if (!(v & 0x0F)) { + channel->volslide = 15; + channel->volume += 15; + if (channel->volume > clip) channel->volume = clip; + } else { + channel->volume -= v & 15; + if (channel->volume > clip) channel->volume = 0; + } + } else if (!(v & 0x0F)) { + channel->volslide = v >> 4; + } else { + channel->volslide = -(v & 15); } - } else if ((v & 0xF0) == 0) { /* D0x */ - channel->volslide = -v; - if (channel->volslide == -15 && !(sigdata->flags & IT_WAS_AN_XM)) { - channel->volume -= 15; + } else { + if ((v & 0x0F) == 0) { /* Dx0 */ + channel->volslide = v >> 4; + } else if ((v & 0xF0) == 0) { /* D0x */ + channel->volslide = -v; + } else if ((v & 0x0F) == 0x0F) { /* DxF */ + channel->volume += v >> 4; + if (channel->volume > 64) channel->volume = 64; + } else if ((v & 0xF0) == 0xF0) { /* DFx */ + channel->volume -= v & 15; if (channel->volume > 64) channel->volume = 0; } - } else if ((v & 0x0F) == 0x0F) { /* DxF */ - channel->volume += v >> 4; - if (channel->volume > 64) channel->volume = 64; - } else if ((v & 0xF0) == 0xF0) { /* DFx */ - channel->volume -= v & 15; - if (channel->volume > 64) channel->volume = 0; } } break; @@ -2009,20 +2224,31 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than else channel->xm_lastX2 = v & 15; } + } else if (sigdata->flags & IT_WAS_AN_S3M) { + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; } else { if (v == 0) v = channel->lastEF; channel->lastEF = v; } - if (channel->playing) { - if ((v & 0xF0) == 0xF0) - channel->playing->slide -= (v & 15) << 4; - else if ((v & 0xF0) == 0xE0) - channel->playing->slide -= (v & 15) << 2; - else if (sigdata->flags & IT_WAS_A_669) - channel->portamento -= v << 3; - else - channel->portamento -= v << 4; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) { + if ((v & 0xF0) == 0xF0) + playing->slide -= (v & 15) << 4; + else if ((v & 0xF0) == 0xE0) + playing->slide -= (v & 15) << 2; + else if (i < 0 && sigdata->flags & IT_WAS_A_669) + channel->portamento -= v << 3; + else if (i < 0) + channel->portamento -= v << 4; + } } } break; @@ -2040,20 +2266,31 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than else channel->xm_lastX1 = v & 15; } + } else if (sigdata->flags & IT_WAS_AN_S3M) { + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; } else { if (v == 0) v = channel->lastEF; channel->lastEF = v; } - if (channel->playing) { - if ((v & 0xF0) == 0xF0) - channel->playing->slide += (v & 15) << 4; - else if ((v & 0xF0) == 0xE0) - channel->playing->slide += (v & 15) << 2; - else if (sigdata->flags & IT_WAS_A_669) - channel->portamento += v << 3; - else - channel->portamento += v << 4; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) { + if ((v & 0xF0) == 0xF0) + playing->slide += (v & 15) << 4; + else if ((v & 0xF0) == 0xE0) + playing->slide += (v & 15) << 2; + else if (i < 0 && sigdata->flags & IT_WAS_A_669) + channel->portamento += v << 3; + else if (i < 0) + channel->portamento += v << 4; + } } } break; @@ -2096,16 +2333,23 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than if (depth == 0) depth = channel->lastHdepth; else { - if (sigdata->flags & IT_OLD_EFFECTS) + if (sigdata->flags & IT_OLD_EFFECTS && !(sigdata->flags & IT_WAS_A_MOD)) depth <<= 3; else depth <<= 2; channel->lastHdepth = depth; } - if (channel->playing) { - channel->playing->vibrato_speed = speed; - channel->playing->vibrato_depth = depth; - channel->playing->vibrato_n++; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) { + playing->vibrato_speed = speed; + playing->vibrato_depth = depth; + playing->vibrato_n++; + } } } } @@ -2113,13 +2357,20 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than case IT_TREMOR: { unsigned char v = entry->effectvalue; - if (v == 0) - v = channel->lastI; + if (v == 0) { + if (sigdata->flags & IT_WAS_AN_S3M) + v = channel->lastDKL; + else + v = channel->lastI; + } else if (!(sigdata->flags & IT_OLD_EFFECTS)) { if (v & 0xF0) v -= 0x10; if (v & 0x0F) v -= 0x01; } - channel->lastI = v; + if (sigdata->flags & IT_WAS_AN_S3M) + channel->lastDKL = v; + else + channel->lastI = v; channel->tremor_time |= 128; } update_tremor(channel); @@ -2131,11 +2382,18 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than * and we use lastJ for portamento down instead. */ if (!(sigdata->flags & IT_WAS_AN_XM)) { - if (v == 0) - v = channel->lastJ; - channel->lastJ = v; + if (sigdata->flags & IT_WAS_AN_S3M) { + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; + } else { + if (v == 0) + v = channel->lastJ; + channel->lastJ = v; + } } - channel->arpeggio = v; + channel->arpeggio = ((v & 0xF0) << 4) | (v & 0x0F); + channel->arpeggio_shift = 16; } break; case IT_SET_CHANNEL_VOLUME: @@ -2147,8 +2405,15 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than else channel->channelvolume = 64; #endif - if (channel->playing) - channel->playing->channel_volume = channel->channelvolume; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) + playing->channel_volume = channel->channelvolume; + } break; case IT_CHANNEL_VOLUME_SLIDE: { @@ -2169,8 +2434,15 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than if (channel->channelvolume > 64) channel->channelvolume = 0; } else break; - if (channel->playing) - channel->playing->channel_volume = channel->channelvolume; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) + playing->channel_volume = channel->channelvolume; + } } } break; @@ -2271,11 +2543,16 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than if (sigdata->flags & IT_WAS_AN_XM) { if ((v & 0x0F) == 0) v |= channel->lastQ & 0x0F; if ((v & 0xF0) == 0) v |= channel->lastQ & 0xF0; + channel->lastQ = v; + } else if (sigdata->flags & IT_WAS_AN_S3M) { + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; } else { if (v == 0) v = channel->lastQ; + channel->lastQ = v; } - channel->lastQ = v; if ((v & 0x0F) == 0) v |= 0x01; channel->retrig = v; if (entry->mask & IT_ENTRY_NOTE) { @@ -2300,21 +2577,38 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than break; case IT_TREMOLO: { - unsigned char speed = entry->effectvalue >> 4; - unsigned char depth = entry->effectvalue & 15; - if (speed == 0) - speed = channel->lastRspeed; - channel->lastRspeed = speed; - if (depth == 0) - depth = channel->lastRdepth; - channel->lastRdepth = depth; - if (channel->playing) { - channel->playing->tremolo_speed = speed; - channel->playing->tremolo_depth = depth; + unsigned char speed, depth; + if (sigdata->flags & IT_WAS_AN_S3M) { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; + speed = v >> 4; + depth = v & 15; + } else { + speed = entry->effectvalue >> 4; + depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastRspeed; + channel->lastRspeed = speed; + if (depth == 0) + depth = channel->lastRdepth; + channel->lastRdepth = depth; + } + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) { + playing->tremolo_speed = speed; + playing->tremolo_depth = depth; + } } } break; - case IT_S: + case IT_S: { /* channel->lastS was set in update_pattern_variables(). */ unsigned char effectvalue = channel->lastS; @@ -2339,7 +2633,8 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than case IT_S_SET_VIBRATO_WAVEFORM: { int waveform = effectvalue & 3; - if (sigdata->flags & IT_WAS_AN_XM) waveform = xm_convert_vibrato[waveform]; + if (sigdata->flags & IT_WAS_A_MOD) waveform = mod_convert_vibrato[waveform]; + else if (sigdata->flags & IT_WAS_AN_XM) waveform = xm_convert_vibrato[waveform]; channel->vibrato_waveform = waveform; if (channel->playing) { channel->playing->vibrato_waveform = waveform; @@ -2351,7 +2646,8 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than case IT_S_SET_TREMOLO_WAVEFORM: { int waveform = effectvalue & 3; - if (sigdata->flags & IT_WAS_AN_XM) waveform = xm_convert_vibrato[waveform]; + if (sigdata->flags & IT_WAS_A_MOD) waveform = mod_convert_vibrato[waveform]; + else if (sigdata->flags & IT_WAS_AN_XM) waveform = xm_convert_vibrato[waveform]; channel->tremolo_waveform = waveform; if (channel->playing) { channel->playing->tremolo_waveform = waveform; @@ -2472,7 +2768,13 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than channel->playing->panbrello_depth = 0; break; case IT_S_SET_SURROUND_SOUND: - if ((effectvalue & 15) == 1) { + if ((effectvalue & 15) == 15) { + if (channel->playing && channel->playing->sample && + !(channel->playing->sample->flags & (IT_SAMPLE_LOOP | IT_SAMPLE_SUS_LOOP))) { + channel->playing->flags |= IT_PLAYING_REVERSE; + it_playing_reset_resamplers( channel->playing, channel->playing->sample->length - 1 ); + } + } else if ((effectvalue & 15) == 1) { channel->pan = IT_SURROUND; channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; } @@ -2493,7 +2795,10 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than } break; case IT_S_SET_MIDI_MACRO: - channel->SFmacro = effectvalue & 15; + if ((sigdata->flags & (IT_WAS_AN_XM | IT_WAS_A_MOD)) == (IT_WAS_AN_XM | IT_WAS_A_MOD)) { + channel->inv_loop_speed = effectvalue & 15; + update_invert_loop(channel, channel->playing ? channel->playing->sample : NULL); + } else channel->SFmacro = effectvalue & 15; break; } } @@ -2526,10 +2831,17 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than depth <<= 1; channel->lastHdepth = depth; } - if (channel->playing) { - channel->playing->vibrato_speed = speed; - channel->playing->vibrato_depth = depth; - channel->playing->vibrato_n++; + for (i = -1; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (i < 0) playing = channel->playing; + else { + playing = sigrenderer->playing[i]; + if (!playing || playing->channel != channel) continue; + } + if (playing) { + playing->vibrato_speed = speed; + playing->vibrato_depth = depth; + playing->vibrato_n++; + } } } break; @@ -2706,6 +3018,63 @@ Yxy This uses a table 4 times larger (hence 4 times slower) than channel->toneslide_tick += channel->ptm_toneslide; } break; + + case IT_OKT_NOTE_SLIDE_DOWN: + case IT_OKT_NOTE_SLIDE_DOWN_ROW: + channel->toneslide = -entry->effectvalue; + channel->okt_toneslide = (entry->effect == IT_OKT_NOTE_SLIDE_DOWN) ? 255 : 1; + break; + + case IT_OKT_NOTE_SLIDE_UP: + case IT_OKT_NOTE_SLIDE_UP_ROW: + channel->toneslide = entry->effectvalue; + channel->okt_toneslide = (entry->effect == IT_OKT_NOTE_SLIDE_UP) ? 255 : 1; + break; + + case IT_OKT_ARPEGGIO_3: + case IT_OKT_ARPEGGIO_4: + case IT_OKT_ARPEGGIO_5: + { + unsigned char low = -(entry->effectvalue >> 4); + unsigned char high = entry->effectvalue & 0x0F; + + switch (entry->effect) + { + case IT_OKT_ARPEGGIO_3: + channel->arpeggio = (low << 16) | high; + channel->arpeggio_shift = 16; + break; + + case IT_OKT_ARPEGGIO_4: + channel->arpeggio = (high << 16) | low; + channel->arpeggio_shift = 24; + break; + + case IT_OKT_ARPEGGIO_5: + channel->arpeggio = (high << 16) | (high << 8); + channel->arpeggio_shift = 16; + break; + } + } + break; + + case IT_OKT_VOLUME_SLIDE_DOWN: + if ( entry->effectvalue <= 16 ) channel->volslide = -entry->effectvalue; + else + { + channel->volume -= entry->effectvalue - 16; + if (channel->volume > 64) channel->volume = 0; + } + break; + + case IT_OKT_VOLUME_SLIDE_UP: + if ( entry->effectvalue <= 16 ) channel->volslide = entry->effectvalue; + else + { + channel->volume += entry->effectvalue - 16; + if (channel->volume > 64) channel->volume = 64; + } + break; } } @@ -2887,19 +3256,19 @@ static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *ent { DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; - IT_PLAYING playing; - - playing.sample = 0; + IT_PLAYING * playing = NULL; if (entry->mask & IT_ENTRY_INSTRUMENT) { int oldsample = channel->sample; int oldvolume = channel->volume; + channel->inv_loop_offset = 0; channel->instrument = entry->instrument; instrument_to_sample(sigdata, channel); if (channel->playing && !((entry->mask & IT_ENTRY_NOTE) && entry->note >= 120) && !((entry->mask & IT_ENTRY_EFFECT) && entry->effect == IT_XM_KEY_OFF && entry->effectvalue == 0)) { - playing = *channel->playing; + playing = dup_playing(channel->playing, channel, channel); + if (!playing) return; if (!(sigdata->flags & IT_WAS_A_MOD)) { /* Retrigger vol/pan envelopes if enabled, and cancel fadeout. * Also reset vol/pan to that of _original_ instrument. @@ -2932,12 +3301,11 @@ static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *ent channel->playing = NULL; } } else { - if (!channel->playing) { - channel->playing = alloc_playing(sigrenderer); - if (!channel->playing) return; + if (channel->playing) { + free_playing(sigrenderer, channel->playing); } - *channel->playing = playing; - playing.sample = (IT_SAMPLE *)-1; + channel->playing = playing; + playing = NULL; channel->playing->declick_stage = 0; channel->playing->declick_volume = 1.f / 256.f; #else @@ -2983,7 +3351,11 @@ static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *ent if (channel->playing) { #ifdef RAMP_DOWN int i; - if (playing.sample) *channel->playing = playing; + if (playing) { + free_playing(sigrenderer, channel->playing); + channel->playing = playing; + playing = NULL; + } for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { if (!sigrenderer->playing[i]) { channel->playing->declick_stage = 2; @@ -3001,6 +3373,7 @@ static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *ent channel->playing = NULL; #endif } + if (playing) free_playing(sigrenderer, playing); return; } else if (channel->playing && (entry->mask & IT_ENTRY_VOLPAN) && ((entry->volpan>>4) == 0xF)) { /* Don't retrigger note; portamento in the volume column. */ @@ -3013,20 +3386,26 @@ static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *ent channel->destnote = IT_NOTE_OFF; if (!channel->playing) { - channel->playing = alloc_playing(sigrenderer); - if (!channel->playing) + channel->playing = new_playing(sigrenderer); + if (!channel->playing) { + if (playing) free_playing(sigrenderer, playing); return; + } // Adding the following seems to do the trick for the case where a piece starts with an instrument alone and then some notes alone. retrigger_xm_envelopes(channel->playing); } #ifdef RAMP_DOWN - else if (playing.sample != (IT_SAMPLE *)-1) { + else if (playing) { /* volume rampy stuff! move note to NNA */ int i; - IT_PLAYING * ptemp = alloc_playing(sigrenderer); - if (!ptemp) return; - if (playing.sample) *ptemp = playing; - else *ptemp = *channel->playing; + IT_PLAYING * ptemp; + if (playing->sample) ptemp = playing; + else ptemp = channel->playing; + if (!ptemp) { + if (playing) free_playing(sigrenderer, playing); + return; + } + playing = NULL; for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { if (!sigrenderer->playing[i]) { ptemp->declick_stage = 2; @@ -3187,6 +3566,8 @@ static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *ent break; } } + + if (playing) free_playing(sigrenderer, playing); } @@ -3347,7 +3728,7 @@ static int it_envelope_end(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYIN /* Returns 1 when fading should be initiated for a volume envelope. */ static int update_it_envelope(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe, int flags) { - if (!(playing->enabled_envelopes & flags)) + if (!(playing->enabled_envelopes & flags) || !envelope->n_nodes) return 0; ASSERT(envelope->n_nodes > 0); @@ -3373,7 +3754,7 @@ static int update_it_envelope(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLA } pe->tick++; - while (pe->tick >= envelope->node_t[pe->next_node]) { + while (pe->tick > envelope->node_t[pe->next_node]) { pe->next_node++; if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) { if (pe->next_node > envelope->sus_loop_end) { @@ -3713,7 +4094,7 @@ static void process_all_playing(DUMB_IT_SIGRENDERER *sigrenderer) if ( channel->arpeggio > 0xFF ) playing->delta = playing->sample->C5_speed * (1.f / 65536.f); } - else*/ playing->delta *= (float)pow(DUMB_SEMITONE_BASE, channel->arpeggio >> 8);/* + else*/ playing->delta *= (float)pow(DUMB_SEMITONE_BASE, (signed char)(channel->arpeggio >> channel->arpeggio_shift));/* }*/ playing->filter_cutoff = channel->filter_cutoff; @@ -3830,8 +4211,10 @@ static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) sigrenderer->processrow = sigrenderer->breakrow; sigrenderer->breakrow = 0; for (n = 0; n < DUMB_IT_N_CHANNELS; n++) sigrenderer->channel[n].pat_loop_end_row = 0; - } else + } else { sigrenderer->processrow = sigrenderer->breakrow; + sigrenderer->breakrow = 0; // XXX lolwut + } if (sigrenderer->processorder == 0xFFFF) sigrenderer->processorder = sigrenderer->order - 1; @@ -3846,6 +4229,14 @@ static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) sigrenderer->processorder = -1; continue; } + if (sigdata->flags & IT_WAS_AN_OKT) { + /* Reset some things */ + sigrenderer->speed = sigdata->speed; + sigrenderer->tempo = sigdata->tempo; + for (n = 0; n < DUMB_IT_N_CHANNELS; n++) { + xm_note_off(sigdata, &sigrenderer->channel[n]); + } + } } n = sigdata->order[sigrenderer->processorder]; @@ -3943,7 +4334,9 @@ static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) return 1; } - if (!(sigdata->flags & IT_OLD_EFFECTS)) + if (sigdata->flags & IT_WAS_AN_OKT) + update_effects(sigrenderer); + else if (!(sigdata->flags & IT_OLD_EFFECTS)) update_smooth_effects(sigrenderer); } else { { @@ -3962,9 +4355,12 @@ static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) update_effects(sigrenderer); } } else { - speed0: - update_effects(sigrenderer); - update_tick_counts(sigrenderer); + if ( !(sigdata->flags & IT_WAS_AN_STM) || !(sigrenderer->tick & 15)) + { + speed0: + update_effects(sigrenderer); + update_tick_counts(sigrenderer); + } } if (sigrenderer->globalvolume == 0) { @@ -3987,7 +4383,12 @@ static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) process_all_playing(sigrenderer); { - LONG_LONG t = sigrenderer->sub_time_left + ((LONG_LONG)TICK_TIME_DIVIDEND << 16) / sigrenderer->tempo; + LONG_LONG t = ((LONG_LONG)TICK_TIME_DIVIDEND << 16) / sigrenderer->tempo; + if ( sigrenderer->sigdata->flags & IT_WAS_AN_STM ) + { + t /= 16; + } + t += sigrenderer->sub_time_left; sigrenderer->time_left += (int)(t >> 16); sigrenderer->sub_time_left = (int)t & 65535; } @@ -4051,13 +4452,13 @@ static float calculate_volume(DUMB_IT_SIGRENDERER *sigrenderer, IT_PLAYING *play vol = (rand() % 129) - 64; break; case 4: - vol = it_xm_squarewave[playing->vibrato_time]; + vol = it_xm_squarewave[playing->tremolo_time]; break; case 5: - vol = it_xm_ramp[playing->vibrato_time]; + vol = it_xm_ramp[playing->tremolo_time]; break; case 6: - vol = it_xm_ramp[255-playing->vibrato_time]; + vol = it_xm_ramp[255-((sigrenderer->sigdata->flags & IT_WAS_A_MOD)?playing->vibrato_time:playing->tremolo_time)]; break; } vol *= playing->tremolo_depth; @@ -4078,7 +4479,7 @@ static float calculate_volume(DUMB_IT_SIGRENDERER *sigrenderer, IT_PLAYING *play volume *= 1.0f / ((64 << 5) * 64.0f * 64.0f * 128.0f * 128.0f); if (volume && playing->instrument) { - if (playing->enabled_envelopes & IT_ENV_VOLUME) { + if (playing->enabled_envelopes & IT_ENV_VOLUME && playing->env_instrument->volume_envelope.n_nodes) { volume *= envelope_get_y(&playing->env_instrument->volume_envelope, &playing->volume_envelope); volume *= 1.0f / (64 << IT_ENVELOPE_SHIFT); } @@ -4805,7 +5206,7 @@ static DUMB_IT_SIGRENDERER *init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_cha channel->channelvolume = sigdata->channel_volume[i]; channel->instrument = 0; channel->sample = 0; - channel->note = 0; + channel->note = IT_NOTE_OFF; channel->SFmacro = 0; channel->filter_cutoff = 127; channel->filter_resonance = 0; @@ -4820,6 +5221,7 @@ static DUMB_IT_SIGRENDERER *init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_cha channel->toneslide = 0; channel->ptm_toneslide = 0; channel->ptm_last_toneslide = 0; + channel->okt_toneslide = 0; channel->midi_state = 0; channel->lastvolslide = 0; channel->lastDKL = 0; @@ -4849,6 +5251,9 @@ static DUMB_IT_SIGRENDERER *init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_cha channel->xm_lastEB = 0; channel->xm_lastX1 = 0; channel->xm_lastX2 = 0; + channel->inv_loop_delay = 0; + channel->inv_loop_speed = 0; + channel->inv_loop_offset = 0; channel->playing = NULL; #ifdef BIT_ARRAY_BULLSHIT channel->played_patjump = NULL; @@ -5061,7 +5466,7 @@ static sigrenderer_t *it_start_sigrenderer(DUH *duh, sigdata_t *vsigdata, int n_ } } - while (pos >= sigrenderer->time_left) { + while (pos > 0 && pos >= sigrenderer->time_left) { render(sigrenderer, 0, 1.0f, 0, sigrenderer->time_left, NULL); pos -= sigrenderer->time_left; @@ -5161,7 +5566,7 @@ void _dumb_it_end_sigrenderer(sigrenderer_t *vsigrenderer) for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { if (sigrenderer->channel[i].playing) - free(sigrenderer->channel[i].playing); + free_playing_orig(sigrenderer->channel[i].playing); #ifdef BIT_ARRAY_BULLSHIT bit_array_destroy(sigrenderer->channel[i].played_patjump); #endif @@ -5169,12 +5574,12 @@ void _dumb_it_end_sigrenderer(sigrenderer_t *vsigrenderer) for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) if (sigrenderer->playing[i]) - free(sigrenderer->playing[i]); + free_playing_orig(sigrenderer->playing[i]); for (playing = sigrenderer->free_playing; playing != NULL; playing = next) { next = playing->next; - free(playing); + free_playing_orig(playing); } dumb_destroy_click_remover_array(sigrenderer->n_channels, sigrenderer->click_remover); diff --git a/dumb/src/it/loadokt.c b/dumb/src/it/loadokt.c new file mode 100644 index 000000000..9670714d8 --- /dev/null +++ b/dumb/src/it/loadokt.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadokt.c - Code to read an Oktalyzer module / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By Chris Moeller. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_okt_quick(): loads an OKT file into a DUH struct, returning a + * pointer to the DUH struct. When you have finished with it, you must + * pass the pointer to unload_duh() so that the memory can be freed. + */ +DUH *DUMBEXPORT dumb_load_okt_quick(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_okt_quick(f); + + dumbfile_close(f); + + return duh; +} diff --git a/dumb/src/it/loadokt2.c b/dumb/src/it/loadokt2.c new file mode 100644 index 000000000..3d9ae447b --- /dev/null +++ b/dumb/src/it/loadokt2.c @@ -0,0 +1,29 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadokt2.c - Function to read an Oktalyzer / / \ \ + * module file, opening and closing | < / \_ + * it for you, and do an initial run- | \/ /\ / + * through. \_ / > / + * | \ / / + * By Chris Moeller. | ' / + * \__/ + */ + +#include "dumb.h" + + + +DUH *DUMBEXPORT dumb_load_okt(const char *filename) +{ + DUH *duh = dumb_load_okt_quick(filename); + dumb_it_do_initial_runthrough(duh); + return duh; +} diff --git a/dumb/src/it/readam.c b/dumb/src/it/readam.c index 9910b1d82..6a4994df5 100644 --- a/dumb/src/it/readam.c +++ b/dumb/src/it/readam.c @@ -331,6 +331,7 @@ static DUMB_IT_SIGDATA *it_riff_amff_load_sigdata( struct riff * stream ) sigdata->n_instruments = 0; sigdata->n_orders = 0; + sigdata->restart_position = 0; memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); @@ -553,6 +554,7 @@ static DUMB_IT_SIGDATA *it_riff_am_load_sigdata( struct riff * stream ) sigdata->n_instruments = 0; sigdata->n_orders = 0; + sigdata->restart_position = 0; memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); diff --git a/dumb/src/it/readasy.c b/dumb/src/it/readasy.c index 9ab5a1f32..5738b78a1 100644 --- a/dumb/src/it/readasy.c +++ b/dumb/src/it/readasy.c @@ -71,7 +71,7 @@ static int it_asy_read_pattern( IT_PATTERN *pattern, DUMBFILE *f, unsigned char entry->mask |= IT_ENTRY_INSTRUMENT; } - _dumb_it_xm_convert_effect( buffer[ pos + 2 ] & 0x0F, buffer[ pos + 3 ], entry, 1 ); + _dumb_it_xm_convert_effect( buffer[ pos + 2 ], buffer[ pos + 3 ], entry, 1 ); if ( entry->mask ) ++entry; } @@ -90,7 +90,7 @@ static int it_asy_read_pattern( IT_PATTERN *pattern, DUMBFILE *f, unsigned char static int it_asy_read_sample_header( IT_SAMPLE *sample, DUMBFILE *f ) { - int finetune; + int finetune, key_offset; /** 21 22 Chars Sample 1 name. If the name is not a full @@ -111,7 +111,7 @@ assumed not to be an instrument name, and is probably a message. sample->default_volume = dumbfile_getc( f ); // Should we be setting global_volume to this instead? sample->global_volume = 64; if ( sample->default_volume > 64 ) sample->default_volume = 64; - dumbfile_skip( f, 1 ); /* XXX unknown */ + key_offset = ( signed char ) dumbfile_getc( f ); /* base key offset */ sample->length = dumbfile_igetl( f ); sample->loop_start = dumbfile_igetl( f ); sample->loop_end = sample->loop_start + dumbfile_igetl( f ); @@ -124,7 +124,7 @@ assumed not to be an instrument name, and is probably a message. sample->flags = IT_SAMPLE_EXISTS; sample->default_pan = 0; - sample->C5_speed = (int)( AMIGA_CLOCK / 214.0 );//( int32 )( 16726.0 * pow( DUMB_PITCH_BASE, finetune * 32 ) ); + sample->C5_speed = (int)( AMIGA_CLOCK / 214.0 * pow( DUMB_SEMITONE_BASE, key_offset ) );//( long )( 16726.0 * pow( DUMB_PITCH_BASE, finetune * 32 ) ); sample->finetune = finetune * 32; // the above line might be wrong diff --git a/dumb/src/it/readdsmf.c b/dumb/src/it/readdsmf.c index 275f37ffa..5db09a75c 100644 --- a/dumb/src/it/readdsmf.c +++ b/dumb/src/it/readdsmf.c @@ -265,6 +265,7 @@ static DUMB_IT_SIGDATA *it_riff_dsmf_load_sigdata( struct riff * stream ) sigdata->n_instruments = 0; sigdata->n_orders = 0; + sigdata->restart_position = 0; memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); diff --git a/dumb/src/it/readmod.c b/dumb/src/it/readmod.c index 05c1ba86e..9d105f126 100644 --- a/dumb/src/it/readmod.c +++ b/dumb/src/it/readmod.c @@ -393,10 +393,12 @@ static DUMBFILE *dumbfile_buffer_mod(DUMBFILE *f, uint32 *fft) return dumbfile_open_ex(bm, &buffer_mod_dfs); } -static DUMBFILE *dumbfile_buffer_mod_2(DUMBFILE *f, int32 *remain) +static DUMBFILE *dumbfile_buffer_mod_2(DUMBFILE *f, int n_samples, IT_SAMPLE *sample, int32 *total_sample_size, int32 *remain) { int32 read; + int sample_number; BUFFERED_MOD *bm = malloc(sizeof(*bm)); + unsigned char *ptr; if (!bm) return NULL; bm->buffered = malloc(32768); @@ -430,6 +432,21 @@ static DUMBFILE *dumbfile_buffer_mod_2(DUMBFILE *f, int32 *remain) if (*remain) { bm->ptr = 0; + ptr = bm->buffered + *remain; + sample_number = n_samples - 1; + *total_sample_size = 0; + while (ptr > bm->buffered && sample_number >= 0) { + if (sample[sample_number].flags & IT_SAMPLE_EXISTS) { + ptr -= (sample[sample_number].length + 1) / 2 + 5 + 16; + if (ptr >= bm->buffered && !memcmp(ptr, "ADPCM", 5)) { /* BAH */ + *total_sample_size += (sample[sample_number].length + 1) / 2 + 5 + 16; + } else { + *total_sample_size += sample[sample_number].length; + ptr -= sample[sample_number].length - ((sample[sample_number].length + 1) / 2 + 5 + 16); + } + } + sample_number--; + } } else { free(bm->buffered); bm->buffered = NULL; @@ -447,7 +464,7 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) int n_channels; int i; uint32 fft = 0; - DUMBFILE *rem; + DUMBFILE *rem = NULL; f = dumbfile_buffer_mod(f, &fft); if (!f) @@ -550,7 +567,7 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) } // moo - if ( rstrict && sigdata->n_samples == 15 ) + if ( ( rstrict & 1 ) && sigdata->n_samples == 15 ) { free(sigdata); dumbfile_close(f); @@ -629,12 +646,48 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) if (sigdata->n_samples == 31) dumbfile_skip(f, 4); - /* Work out how many patterns there are. */ sigdata->n_patterns = -1; - for (i = 0; i < 128; i++) - if (sigdata->n_patterns < sigdata->order[i]) - sigdata->n_patterns = sigdata->order[i]; - sigdata->n_patterns++; + + if ( ( rstrict & 2 ) ) + { + long total_sample_size; + long remain; + rem = f; + f = dumbfile_buffer_mod_2(rem, sigdata->n_samples, sigdata->sample, &total_sample_size, &remain); + if (!f) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(rem); + return NULL; + } + if (remain > total_sample_size) { + sigdata->n_patterns = ( remain - total_sample_size + 4 ) / ( 256 * sigdata->n_pchannels ); + if (fft == DUMB_ID('M',0,0,0) || fft == DUMB_ID('8',0,0,0)) { + remain -= sigdata->n_patterns * 256 * sigdata->n_pchannels; + if (dumbfile_skip(f, remain - total_sample_size)) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + dumbfile_close(rem); + return NULL; + } + } + } + } + else + { + for (i = 0; i < 128; i++) + { + if (sigdata->order[i] > sigdata->n_patterns) + sigdata->n_patterns = sigdata->order[i]; + } + sigdata->n_patterns++; + } + + if ( sigdata->n_patterns <= 0 ) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + if (rem) dumbfile_close(rem); + return NULL; + } /* May as well try to save a tiny bit of memory. */ if (sigdata->n_orders < 128) { @@ -646,6 +699,7 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) if (!sigdata->pattern) { _dumb_it_unload_sigdata(sigdata); dumbfile_close(f); + if (rem) dumbfile_close(rem); return NULL; } for (i = 0; i < sigdata->n_patterns; i++) @@ -653,10 +707,11 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) /* Read in the patterns */ { - unsigned char *buffer = malloc(256 * n_channels); /* 64 rows * 4 bytes */ + unsigned char *buffer = malloc(256 * sigdata->n_pchannels); /* 64 rows * 4 bytes */ if (!buffer) { _dumb_it_unload_sigdata(sigdata); dumbfile_close(f); + if (rem) dumbfile_close(rem); return NULL; } for (i = 0; i < sigdata->n_patterns; i++) { @@ -664,40 +719,13 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) free(buffer); _dumb_it_unload_sigdata(sigdata); dumbfile_close(f); + if (rem) dumbfile_close(rem); return NULL; } } free(buffer); } - rem = NULL; - - /* uggly */ - if (fft == DUMB_ID('M',0,0,0) || fft == DUMB_ID('8',0,0,0)) { - int32 skip; - int32 remain; - rem = f; - f = dumbfile_buffer_mod_2(rem, &remain); - if (!f) { - _dumb_it_unload_sigdata(sigdata); - dumbfile_close(rem); - return NULL; - } - for (skip = 0, i = 0; i < sigdata->n_samples; i++) { - if (sigdata->sample[i].flags & IT_SAMPLE_EXISTS) { - skip += sigdata->sample[i].length; - } - } - if (remain - skip) { - if (dumbfile_skip(f, remain - skip)) { - _dumb_it_unload_sigdata(sigdata); - dumbfile_close(f); - dumbfile_close(rem); - return NULL; - } - } - } - /* And finally, the sample data */ for (i = 0; i < sigdata->n_samples; i++) { if (it_mod_read_sample_data(&sigdata->sample[i], f, fft)) { @@ -727,8 +755,8 @@ static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f, int rstrict) }*/ dumbfile_close(f); /* Destroy the BUFFERED_MOD DUMBFILE we were using. */ + if (rem) dumbfile_close(rem); /* And the BUFFERED_MOD DUMBFILE used to pre-read the signature. */ /* The DUMBFILE originally passed to our function is intact. */ - if (rem) dumbfile_close(rem); /* Now let's initialise the remaining variables, and we're done! */ sigdata->flags = IT_WAS_AN_XM | IT_WAS_A_MOD | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO; diff --git a/dumb/src/it/readokt.c b/dumb/src/it/readokt.c new file mode 100644 index 000000000..078929c5c --- /dev/null +++ b/dumb/src/it/readokt.c @@ -0,0 +1,558 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readokt.c - Code to read an Oktalyzer module / / \ \ + * from an open file. | < / \_ + * | \/ /\ / + * By Chris Moeller. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +static int it_okt_read_pattern(IT_PATTERN *pattern, const unsigned char *data, int length, int n_channels) +{ + int pos; + int channel; + int row; + int n_rows; + IT_ENTRY *entry; + + if (length < 2) return -1; + + n_rows = (data[0] << 8) | data[1]; + if (!n_rows) n_rows = 64; + + if (length < 2 + (n_rows * n_channels * 4)) return -1; + + pattern->n_rows = n_rows; + + /* compute number of entries */ + pattern->n_entries = n_rows; /* Account for the row end markers */ + pos = 2; + for (row = 0; row < pattern->n_rows; row++) { + for (channel = 0; channel < n_channels; channel++) { + if (data[pos+0] | data[pos+2]) + pattern->n_entries++; + pos += 4; + } + } + + pattern->entry = (IT_ENTRY *) malloc(pattern->n_entries * sizeof(*pattern->entry)); + if (!pattern->entry) + return -1; + + entry = pattern->entry; + pos = 2; + for (row = 0; row < n_rows; row++) { + for (channel = 0; channel < n_channels; channel++) { + if (data[pos+0] | data[pos+2]) { + entry->channel = channel; + entry->mask = 0; + + if (data[pos+0] > 0 && data[pos+0] <= 36) { + entry->mask |= IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT; + + entry->note = data[pos+0] + 35; + entry->instrument = data[pos+1] + 1; + } + + entry->effect = 0; + entry->effectvalue = data[pos+3]; + + switch (data[pos+2]) { + case 2: if (data[pos+3]) entry->effect = IT_PORTAMENTO_DOWN; break; // XXX code calls this rs_portu, but it's adding to the period, which decreases the pitch + case 13: if (data[pos+3]) entry->effect = IT_OKT_NOTE_SLIDE_DOWN; break; + case 21: if (data[pos+3]) entry->effect = IT_OKT_NOTE_SLIDE_DOWN_ROW; break; + + case 1: if (data[pos+3]) entry->effect = IT_PORTAMENTO_UP; break; // XXX same deal here, increasing the pitch + case 17: if (data[pos+3]) entry->effect = IT_OKT_NOTE_SLIDE_UP; break; + case 30: if (data[pos+3]) entry->effect = IT_OKT_NOTE_SLIDE_UP_ROW; break; + + case 10: if (data[pos+3]) entry->effect = IT_OKT_ARPEGGIO_3; break; + case 11: if (data[pos+3]) entry->effect = IT_OKT_ARPEGGIO_4; break; + case 12: if (data[pos+3]) entry->effect = IT_OKT_ARPEGGIO_5; break; + + case 15: entry->effect = IT_S; entry->effectvalue = EFFECT_VALUE(IT_S_SET_FILTER, data[pos+3] & 0x0F); break; + + case 25: entry->effect = IT_JUMP_TO_ORDER; break; + + case 27: entry->note = IT_NOTE_OFF; entry->mask |= IT_ENTRY_NOTE; break; + + case 28: entry->effect = IT_SET_SPEED; break; + + case 31: + if ( data[pos+3] <= 0x40 ) entry->effect = IT_SET_CHANNEL_VOLUME; + else if ( data[pos+3] <= 0x50 ) { entry->effect = IT_OKT_VOLUME_SLIDE_DOWN; entry->effectvalue = data[pos+3] - 0x40; } + else if ( data[pos+3] <= 0x60 ) { entry->effect = IT_OKT_VOLUME_SLIDE_UP; entry->effectvalue = data[pos+3] - 0x50; } + else if ( data[pos+3] <= 0x70 ) { entry->effect = IT_OKT_VOLUME_SLIDE_DOWN; entry->effectvalue = data[pos+3] - 0x50; } + else if ( data[pos+3] <= 0x80 ) { entry->effect = IT_OKT_VOLUME_SLIDE_UP; entry->effectvalue = data[pos+3] - 0x60; } + break; + } + + if ( entry->effect ) entry->mask |= IT_ENTRY_EFFECT; + + entry++; + } + pos += 4; + } + IT_SET_END_ROW(entry); + entry++; + } + + return 0; +} + + + +static void it_okt_read_sample_header(IT_SAMPLE *sample, const unsigned char * data) +{ + int loop_start, loop_length; + + memcpy(sample->name, data, 20); + sample->name[20] = 0; + + sample->filename[0] = 0; + + sample->length = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23]; + sample->global_volume = 64; + sample->default_volume = data[29]; + loop_start = ((data[24] << 8) | data[25]) << 1; + loop_length = ((data[26] << 8) | data[27]) << 1; + sample->sus_loop_start = loop_start; + sample->sus_loop_end = loop_start + loop_length; + + if (sample->length <= 0) { + sample->flags = 0; + return; + } + + sample->flags = IT_SAMPLE_EXISTS; + + sample->default_pan = 0; + sample->C5_speed = (int)( AMIGA_CLOCK / 214.0 ); //(long)(16726.0*pow(DUMB_PITCH_BASE, finetune*32)); + sample->finetune = 0; + + if (sample->sus_loop_end > sample->length) + sample->sus_loop_end = sample->length; + + if (loop_length > 2) + sample->flags |= IT_SAMPLE_SUS_LOOP; + + sample->vibrato_speed = 0; + sample->vibrato_depth = 0; + sample->vibrato_rate = 0; + sample->vibrato_waveform = 0; // do we have to set _all_ these? + sample->max_resampling_quality = -1; +} + + + +static int it_okt_read_sample_data(IT_SAMPLE *sample, const char * data, int length) +{ + if (length && sample->length) { + if (length < sample->length) { + sample->length = length; + if (length < sample->sus_loop_end) sample->sus_loop_end = length; + } + + sample->data = malloc(length); + + if (!sample->data) + return -1; + + memcpy(sample->data, data, length); + } + + return 0; +} + + + +typedef struct IFF_CHUNK IFF_CHUNK; +typedef struct IFF_CHUNKED IFF_CHUNKED; + +struct IFF_CHUNK +{ + unsigned type; + unsigned char * data; + unsigned size; +}; + +struct IFF_CHUNKED +{ + unsigned chunk_count; + IFF_CHUNK * chunks; +}; + + + +static IFF_CHUNKED *dumbfile_read_okt(DUMBFILE *f) +{ + IFF_CHUNKED *mod = (IFF_CHUNKED *) malloc(sizeof(*mod)); + if (!mod) return NULL; + + mod->chunk_count = 0; + mod->chunks = 0; + + for (;;) + { + long bytes_read; + IFF_CHUNK * chunk = ( IFF_CHUNK * ) realloc( mod->chunks, ( mod->chunk_count + 1 ) * sizeof( IFF_CHUNK ) ); + if ( !chunk ) + { + if ( mod->chunks ) free( mod->chunks ); + free( mod ); + return NULL; + } + mod->chunks = chunk; + chunk += mod->chunk_count; + + bytes_read = dumbfile_mgetl( f ); + if ( bytes_read < 0 ) break; + + chunk->type = bytes_read; + chunk->size = dumbfile_mgetl( f ); + + if ( dumbfile_error( f ) ) break; + + chunk->data = (unsigned char *) malloc( chunk->size ); + if ( !chunk->data ) + { + free( mod->chunks ); + free( mod ); + return NULL; + } + + bytes_read = dumbfile_getnc( ( char * ) chunk->data, chunk->size, f ); + if ( bytes_read < (long)chunk->size ) + { + if ( bytes_read <= 0 ) { + free( chunk->data ); + break; + } else { + chunk->size = bytes_read; + mod->chunk_count++; + break; + } + } + + mod->chunk_count++; + } + + if ( !mod->chunk_count ) { + if ( mod->chunks ) free(mod->chunks); + free(mod); + mod = NULL; + } + + return mod; +} + +void free_okt(IFF_CHUNKED * mod) +{ + unsigned i; + if (mod) + { + if (mod->chunks) + { + for (i = 0; i < mod->chunk_count; i++) + { + if (mod->chunks[i].data) free(mod->chunks[i].data); + } + free(mod->chunks); + } + free(mod); + } +} + +const IFF_CHUNK * get_chunk_by_type(IFF_CHUNKED * mod, unsigned type, unsigned offset) +{ + unsigned i; + if (mod) + { + if (mod->chunks) + { + for (i = 0; i < mod->chunk_count; i++) + { + if (mod->chunks[i].type == type) + { + if (!offset) return &mod->chunks[i]; + else offset--; + } + } + } + } + return NULL; +} + +unsigned get_chunk_count(IFF_CHUNKED *mod, unsigned type) +{ + unsigned i, count = 0; + if (mod) + { + if (mod->chunks) + { + for (i = 0; i < mod->chunk_count; i++) + { + if (mod->chunks[i].type == type) count++; + } + } + } + return count; +} + + +static DUMB_IT_SIGDATA *it_okt_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + unsigned n_channels; + unsigned i, j, k, l; + IFF_CHUNKED *mod; + const IFF_CHUNK *chunk; + + char signature[8]; + + if (dumbfile_getnc(signature, 8, f) < 8 || + memcmp(signature, "OKTASONG", 8)) { + return NULL; + } + + mod = dumbfile_read_okt(f); + if (!mod) + return NULL; + + sigdata = (DUMB_IT_SIGDATA *) malloc(sizeof(*sigdata)); + if (!sigdata) { + free_okt(mod); + return NULL; + } + + sigdata->name[0] = 0; + + chunk = get_chunk_by_type(mod, DUMB_ID('S','P','E','E'), 0); + if (!chunk || chunk->size < 2) { + free(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->speed = (chunk->data[0] << 8) | chunk->data[1]; + + chunk = get_chunk_by_type(mod, DUMB_ID('S','A','M','P'), 0); + if (!chunk || chunk->size < 32) { + free(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->n_samples = chunk->size / 32; + + chunk = get_chunk_by_type(mod, DUMB_ID('C','M','O','D'), 0); + if (!chunk || chunk->size < 8) { + free(sigdata); + free_okt(mod); + return NULL; + } + + n_channels = 0; + + for (i = 0; i < 4; i++) { + j = (chunk->data[i * 2] << 8) | chunk->data[i * 2 + 1]; + if (!j) n_channels++; + else if (j == 1) n_channels += 2; + } + + if (!n_channels) { + free(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->n_pchannels = n_channels; + + sigdata->sample = (IT_SAMPLE *) malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + free(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->song_message = NULL; + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + sigdata->n_instruments = 0; + + for (i = 0; i < (unsigned)sigdata->n_samples; i++) + sigdata->sample[i].data = NULL; + + chunk = get_chunk_by_type(mod, DUMB_ID('S','A','M','P'), 0); + + for (i = 0; i < (unsigned)sigdata->n_samples; i++) { + it_okt_read_sample_header(&sigdata->sample[i], chunk->data + 32 * i); + } + + sigdata->restart_position = 0; + + chunk = get_chunk_by_type(mod, DUMB_ID('P','L','E','N'), 0); + if (!chunk || chunk->size < 2) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->n_orders = (chunk->data[0] << 8) | chunk->data[1]; + // what if this is > 128? + + if (sigdata->n_orders <= 0 || sigdata->n_orders > 128) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + + chunk = get_chunk_by_type(mod, DUMB_ID('P','A','T','T'), 0); + if (!chunk || chunk->size < (unsigned)sigdata->n_orders) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->order = (unsigned char *) malloc(sigdata->n_orders); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + + memcpy(sigdata->order, chunk->data, sigdata->n_orders); + + /* Work out how many patterns there are. */ + chunk = get_chunk_by_type(mod, DUMB_ID('S','L','E','N'), 0); + if (!chunk || chunk->size < 2) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->n_patterns = (chunk->data[0] << 8) | chunk->data[1]; + + j = get_chunk_count(mod, DUMB_ID('P','B','O','D')); + if (sigdata->n_patterns > (int)j) sigdata->n_patterns = (int)j; + + if (!sigdata->n_patterns) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + + sigdata->pattern = (IT_PATTERN *) malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + for (i = 0; i < (unsigned)sigdata->n_patterns; i++) + sigdata->pattern[i].entry = NULL; + + /* Read in the patterns */ + for (i = 0; i < (unsigned)sigdata->n_patterns; i++) { + chunk = get_chunk_by_type(mod, DUMB_ID('P','B','O','D'), i); + if (it_okt_read_pattern(&sigdata->pattern[i], chunk->data, chunk->size, n_channels) != 0) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + } + + /* And finally, the sample data */ + k = get_chunk_count(mod, DUMB_ID('S','B','O','D')); + for (i = 0, j = 0; i < (unsigned)sigdata->n_samples, j < k; i++) { + if (sigdata->sample[i].flags & IT_SAMPLE_EXISTS) { + chunk = get_chunk_by_type(mod, DUMB_ID('S','B','O','D'), j); + if (it_okt_read_sample_data(&sigdata->sample[i], (const char *)chunk->data, chunk->size)) { + _dumb_it_unload_sigdata(sigdata); + free_okt(mod); + return NULL; + } + j++; + } + } + for (; i < (unsigned)sigdata->n_samples; i++) { + sigdata->sample[i].flags = 0; + } + + chunk = get_chunk_by_type(mod, DUMB_ID('C','M','O','D'), 0); + + for (i = 0, j = 0; i < n_channels, j < 4; j++) { + k = (chunk->data[j * 2] << 8) | chunk->data[j * 2 + 1]; + l = (j == 1 || j == 2) ? 48 : 16; + if (k == 0) { + sigdata->channel_pan[i++] = l; + } + else if (k == 1) { + sigdata->channel_pan[i++] = l; + sigdata->channel_pan[i++] = l; + } + } + + free_okt(mod); + + /* Now let's initialise the remaining variables, and we're done! */ + sigdata->flags = IT_WAS_AN_OKT | IT_WAS_AN_XM | IT_WAS_A_MOD | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO; + + sigdata->global_volume = 128; + sigdata->mixing_volume = 48; + /* We want 50 ticks per second; 50/6 row advances per second; + * 50*10=500 row advances per minute; 500/4=125 beats per minute. + */ + sigdata->tempo = 125; + sigdata->pan_separation = 128; + + memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); + memset(sigdata->channel_pan + n_channels, 32, DUMB_IT_N_CHANNELS - n_channels); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *DUMBEXPORT dumb_read_okt_quick(DUMBFILE *f) +{ + sigdata_t *sigdata; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_okt_load_sigdata(f); + + if (!sigdata) + return NULL; + + { + const char *tag[1][2]; + tag[0][0] = "FORMAT"; + tag[0][1] = "Oktalyzer"; + return make_duh(-1, 1, (const char *const (*)[2])tag, 1, &descptr, &sigdata); + } +} diff --git a/dumb/src/it/readokt2.c b/dumb/src/it/readokt2.c new file mode 100644 index 000000000..66dd1f6d4 --- /dev/null +++ b/dumb/src/it/readokt2.c @@ -0,0 +1,29 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readokt2.c - Function to read an Oktalyzer / / \ \ + * module from an open file and do | < / \_ + * an initial run-through. | \/ /\ / + * \_ / > / + * | \ / / + * By Chris Moeller. | ' / + * \__/ + */ + +#include "dumb.h" + + + +DUH *DUMBEXPORT dumb_read_okt(DUMBFILE *f) +{ + DUH *duh = dumb_read_okt_quick(f); + dumb_it_do_initial_runthrough(duh); + return duh; +} diff --git a/dumb/src/it/readoldpsm.c b/dumb/src/it/readoldpsm.c index 3c764af25..5c0441c78 100644 --- a/dumb/src/it/readoldpsm.c +++ b/dumb/src/it/readoldpsm.c @@ -296,7 +296,7 @@ static int it_old_psm_read_patterns(IT_PATTERN * pattern, DUMBFILE * f, int num, if (flags & 0x80) { if ((*ptr < 60) && (channel < pchans)) { entry->mask |= IT_ENTRY_NOTE; - entry->note = *ptr + 36; + entry->note = *ptr + 35; } ptr++; if (*ptr) { diff --git a/dumb/src/it/reads3m.c b/dumb/src/it/reads3m.c index c9266ac6c..c55e9f615 100644 --- a/dumb/src/it/reads3m.c +++ b/dumb/src/it/reads3m.c @@ -557,8 +557,8 @@ static DUMB_IT_SIGDATA *it_s3m_load_sigdata(DUMBFILE *f, int * cwtv) return NULL; } - sigdata->global_volume = dumbfile_getc(f) << 1; - if ( !sigdata->global_volume || sigdata->global_volume > 128 ) sigdata->global_volume = 128; + sigdata->global_volume = dumbfile_getc(f) * 16 / 11; + if ( !sigdata->global_volume || sigdata->global_volume > 93 ) sigdata->global_volume = 93; sigdata->speed = dumbfile_getc(f); if (sigdata->speed == 0) sigdata->speed = 6; // Should we? What about tempo? sigdata->tempo = dumbfile_getc(f); diff --git a/dumb/src/it/readstm.c b/dumb/src/it/readstm.c index 8b8e8c82d..42f14e532 100644 --- a/dumb/src/it/readstm.c +++ b/dumb/src/it/readstm.c @@ -20,6 +20,7 @@ // IT_STEREO... :o #include #include +#include #include "dumb.h" #include "internal/it.h" @@ -28,14 +29,16 @@ #define strnicmp strncasecmp #endif -static int it_stm_read_sample_header( IT_SAMPLE *sample, DUMBFILE *f ) +static int it_stm_read_sample_header( IT_SAMPLE *sample, DUMBFILE *f, unsigned short *offset ) { dumbfile_getnc( sample->filename, 12, f ); sample->filename[12] = 0; memcpy( sample->name, sample->filename, 13 ); - dumbfile_skip( f, 2 + 2 ); + dumbfile_skip( f, 2 ); + + *offset = dumbfile_igetw( f ); sample->length = dumbfile_igetw( f ); sample->loop_start = dumbfile_igetw( f ); @@ -78,24 +81,15 @@ static int it_stm_read_sample_header( IT_SAMPLE *sample, DUMBFILE *f ) return dumbfile_error(f); } -static int it_stm_read_sample_data( IT_SAMPLE *sample, DUMBFILE *f ) +static int it_stm_read_sample_data( IT_SAMPLE *sample, void *data_block, long offset ) { - int32 n; - if ( ! sample->length ) return 0; - n = dumbfile_pos( f ); - if ( n & 15 ) { - if ( dumbfile_skip( f, 16 - ( n & 15 ) ) ) - return -1; - } - sample->data = malloc( sample->length ); if (!sample->data) return -1; - if ( dumbfile_getnc( sample->data, sample->length, f ) != sample->length ) - return -1; + memcpy( sample->data, (unsigned char*)data_block + offset, sample->length ); return 0; } @@ -141,10 +135,6 @@ static int it_stm_read_pattern( IT_PATTERN *pattern, DUMBFILE *f, unsigned char entry->effectvalue = buffer[ pos + 3 ]; if ( entry->instrument && entry->instrument < 32 ) entry->mask |= IT_ENTRY_INSTRUMENT; - if ( note == 0xFC || note == 0xFE ) { - entry->mask |= IT_ENTRY_NOTE; - entry->note = IT_NOTE_CUT; - } if ( note < 251 ) { entry->mask |= IT_ENTRY_NOTE; entry->note = ( note >> 4 ) * 12 + ( note & 0x0F ); @@ -153,9 +143,9 @@ static int it_stm_read_pattern( IT_PATTERN *pattern, DUMBFILE *f, unsigned char entry->mask |= IT_ENTRY_VOLPAN; entry->mask |= IT_ENTRY_EFFECT; switch ( entry->effect ) { - case IT_SET_SPEED: - entry->effectvalue >>= 4; - break; + case IT_SET_SPEED: + /* taken care of in the renderer */ + break; case IT_BREAK_TO_ROW: entry->effectvalue -= (entry->effectvalue >> 4) * 6; @@ -192,14 +182,20 @@ static int it_stm_read_pattern( IT_PATTERN *pattern, DUMBFILE *f, unsigned char -static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f /*, int * version*/) +static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f, int * version) { DUMB_IT_SIGDATA *sigdata; char tracker_name[ 8 ]; + unsigned short sample_offset[ 31 ]; + + void *data_block; + int n; + long o, p, q; + sigdata = malloc(sizeof(*sigdata)); if (!sigdata) return NULL; @@ -227,8 +223,7 @@ static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f /*, int * version*/) return NULL; } - /* *version = dumbfile_mgetw(f); */ - dumbfile_skip( f, 2 ); + *version = dumbfile_mgetw(f); sigdata->song_message = NULL; sigdata->order = NULL; @@ -242,16 +237,17 @@ static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f /*, int * version*/) sigdata->n_samples = 31; sigdata->n_pchannels = 4; - sigdata->tempo = 125; - sigdata->mixing_volume = 48; + sigdata->tempo = 125; + sigdata->mixing_volume = 48; sigdata->pan_separation = 128; /** WARNING: which ones? */ - sigdata->flags = IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_WAS_AN_S3M; + sigdata->flags = IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_WAS_AN_S3M | IT_WAS_AN_STM | IT_STEREO; - sigdata->speed = dumbfile_getc(f) >> 4; - if ( sigdata->speed < 1 ) sigdata->speed = 1; - sigdata->n_patterns = dumbfile_getc(f); + n = dumbfile_getc(f); + if ( n < 32 ) n = 32; + sigdata->speed = n; + sigdata->n_patterns = dumbfile_getc(f); sigdata->global_volume = dumbfile_getc(f) << 1; if ( sigdata->global_volume > 128 ) sigdata->global_volume = 128; @@ -287,7 +283,7 @@ static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f /*, int * version*/) sigdata->channel_pan[ 3 ] = 16; for ( n = 0; n < sigdata->n_samples; ++n ) { - if ( it_stm_read_sample_header( &sigdata->sample[ n ], f ) ) { + if ( it_stm_read_sample_header( &sigdata->sample[ n ], f, &sample_offset[ n ] ) ) { _dumb_it_unload_sigdata( sigdata ); return NULL; } @@ -300,7 +296,8 @@ static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f /*, int * version*/) } /* Orders, byte each, length = sigdata->n_orders (should be even) */ - dumbfile_getnc( sigdata->order, 128, f ); + dumbfile_getnc( sigdata->order, *version >= 0x200 ? 128 : 64, f ); + if (*version < 0x200) memset( sigdata->order + 64, 0xFF, 64 ); sigdata->restart_position = 0; for ( n = 127; n >= 0; --n ) { @@ -332,54 +329,105 @@ static DUMB_IT_SIGDATA *it_stm_load_sigdata(DUMBFILE *f /*, int * version*/) free( buffer ); } + o = LONG_MAX; + p = 0; + for ( n = 0; n < sigdata->n_samples; ++n ) { - if ( it_stm_read_sample_data( &sigdata->sample[ n ], f ) ) { + if ((sigdata->sample[ n ].flags & IT_SAMPLE_EXISTS) && sample_offset[ n ]) { + q = ((long)sample_offset[ n ]) * 16; + if (q < o) { + o = q; + } + if (q + sigdata->sample[ n ].length > p) { + p = q + sigdata->sample[ n ].length; + } + } + else { + sigdata->sample[ n ].flags = 0; + sigdata->sample[ n ].length = 0; + } + } + + data_block = malloc( p - o ); + if ( !data_block ) { + _dumb_it_unload_sigdata( sigdata ); + return NULL; + } + + for ( n = 0, q = o / 16; n < sigdata->n_samples; ++n ) { + if ( sample_offset[ n ] ) { + sample_offset[ n ] = (unsigned short)(sample_offset[ n ] - q); + } + } + + q = o - dumbfile_pos( f ); + p -= o; + o = 0; + if ( q >= 0 ) dumbfile_skip( f, q ); + else { + o = -q; + memset ( data_block, 0, o ); + } + if ( dumbfile_getnc( (char*)data_block + o, p - o, f ) != p - o ) { + free( data_block ); + _dumb_it_unload_sigdata( sigdata ); + return NULL; + } + + for ( n = 0; n < sigdata->n_samples; ++n ) { + if ( it_stm_read_sample_data( &sigdata->sample[ n ], data_block, ((long)sample_offset[ n ]) * 16 ) ) { + free( data_block ); _dumb_it_unload_sigdata( sigdata ); return NULL; } } + free( data_block ); + _dumb_it_fix_invalid_orders(sigdata); return sigdata; } -/*static char hexdigit(int in) -{ - if (in < 10) return in + '0'; - else return in + 'A' - 10; -}*/ - DUH *DUMBEXPORT dumb_read_stm_quick(DUMBFILE *f) { sigdata_t *sigdata; - /*int ver;*/ + int ver; DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; - sigdata = it_stm_load_sigdata(f /*, &ver*/); + sigdata = it_stm_load_sigdata(f , &ver); if (!sigdata) return NULL; { - /*char version[16];*/ + char version[16]; const char *tag[2][2]; tag[0][0] = "TITLE"; tag[0][1] = ((DUMB_IT_SIGDATA *)sigdata)->name; tag[1][0] = "FORMAT"; - tag[1][1] = "STM"; - /*version[0] = 'S'; + version[0] = 'S'; version[1] = 'T'; version[2] = 'M'; version[3] = ' '; version[4] = 'v'; - version[5] = hexdigit((ver >> 8) & 15); + version[5] = '0' + ((ver >> 8) & 15); version[6] = '.'; - version[7] = hexdigit((ver >> 4) & 15); - version[8] = hexdigit(ver & 15); - version[9] = 0; - tag[1][1] = (const char *) &version;*/ + if ((ver & 255) > 99) + { + version[7] = '0' + ((ver & 255) / 100 ); + version[8] = '0' + (((ver & 255) / 10) % 10); + version[9] = '0' + ((ver & 255) % 10); + version[10] = 0; + } + else + { + version[7] = '0' + ((ver & 255) / 10); + version[8] = '0' + ((ver & 255) % 10); + version[9] = 0; + } + tag[1][1] = (const char *) &version; return make_duh(-1, 2, (const char *const (*)[2])tag, 1, &descptr, &sigdata); } } diff --git a/dumb/src/it/readxm.c b/dumb/src/it/readxm.c index 7d1b4657b..25ca07108 100644 --- a/dumb/src/it/readxm.c +++ b/dumb/src/it/readxm.c @@ -111,11 +111,20 @@ typedef struct XM_INSTRUMENT_EXTRA int vibrato_sweep; /* 0-0xFF */ int vibrato_depth; /* 0-0x0F */ int vibrato_speed; /* 0-0x3F */ + int sample_header_size; } XM_INSTRUMENT_EXTRA; +/* Trims off trailing white space, usually added by the tracker on file creation + */ +static void trim_whitespace(char *ptr, size_t size) +{ + char *p = ptr + size - 1; + while (p >= ptr && *p <= 0x20) *p-- = '\0'; +} + /* Frees the original block if it can't resize it or if size is 0, and acts * as malloc if ptr is NULL. */ @@ -329,7 +338,7 @@ static int it_xm_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, int n_channels, static int it_xm_make_envelope(IT_ENVELOPE *envelope, const unsigned short *data, int y_offset) { - int i, pos; + int i, pos, val; if (envelope->n_nodes > 12) { /* XXX @@ -346,12 +355,13 @@ static int it_xm_make_envelope(IT_ENVELOPE *envelope, const unsigned short *data pos = 0; for (i = 0; i < envelope->n_nodes; i++) { envelope->node_t[i] = data[pos++]; - if (data[pos] > 64) { - TRACE("XM error: out-of-range envelope node (node_y[%d]=%d)\n", i, data[pos]); - envelope->n_nodes = 0; - return -1; + val = data[pos++]; + if (val > 64) { + TRACE("XM error: out-of-range envelope node (node_y[%d]=%d)\n", i, val); + /* FT2 seems to simply clip the value */ + val = 64; } - envelope->node_y[i] = (signed char)(data[pos++] + y_offset); + envelope->node_y[i] = (signed char)(val + y_offset); } return 0; @@ -359,21 +369,153 @@ static int it_xm_make_envelope(IT_ENVELOPE *envelope, const unsigned short *data +typedef struct LIMITED_XM LIMITED_XM; + +struct LIMITED_XM +{ + unsigned char *buffered; + long ptr, limit, allocated; + DUMBFILE *remaining; +}; + +/* XXX */ +struct DUMBFILE +{ + DUMBFILE_SYSTEM *dfs; + void *file; + long pos; +}; + +static int limit_xm_resize(void *f, long n) +{ + DUMBFILE *df = f; + LIMITED_XM *lx = df->file; + if (lx->buffered || n) { + if (n > lx->allocated) { + unsigned char *buffered = realloc( lx->buffered, n ); + if ( !buffered ) return -1; + lx->buffered = buffered; + memset( buffered + lx->allocated, 0, n - lx->allocated ); + lx->allocated = n; + } + if ( dumbfile_getnc( lx->buffered, n, lx->remaining ) < n ) return -1; + } else if (!n) { + if ( lx->buffered ) free( lx->buffered ); + lx->buffered = NULL; + lx->allocated = 0; + } + lx->limit = n; + lx->ptr = 0; + return 0; +} + +static int limit_xm_skip_end(void *f, int32 n) +{ + DUMBFILE *df = f; + LIMITED_XM *lx = df->file; + return dumbfile_skip( lx->remaining, n ); +} + +static int limit_xm_skip(void *f, int32 n) +{ + LIMITED_XM *lx = f; + lx->ptr += n; + return 0; +} + + + +static int limit_xm_getc(void *f) +{ + LIMITED_XM *lx = f; + if (lx->ptr >= lx->allocated) { + return 0; + } + return lx->buffered[lx->ptr++]; +} + + + +static long limit_xm_getnc(char *ptr, int32 n, void *f) +{ + LIMITED_XM *lx = f; + int left; + left = lx->allocated - lx->ptr; + if (n > left) { + if (left > 0) { + memcpy( ptr, lx->buffered + lx->ptr, left ); + memset( ptr + left, 0, n - left ); + } else { + memset( ptr, 0, n ); + } + } else { + memcpy( ptr, lx->buffered + lx->ptr, n ); + } + lx->ptr += n; + return n; +} + + + +static void limit_xm_close(void *f) +{ + LIMITED_XM *lx = f; + if (lx->buffered) free(lx->buffered); + /* Do NOT close lx->remaining */ + free(f); +} + + + +DUMBFILE_SYSTEM limit_xm_dfs = { + NULL, + &limit_xm_skip, + &limit_xm_getc, + &limit_xm_getnc, + &limit_xm_close +}; + +static DUMBFILE *dumbfile_limit_xm(DUMBFILE *f) +{ + LIMITED_XM * lx = malloc(sizeof(*lx)); + lx->remaining = f; + lx->buffered = NULL; + lx->ptr = 0; + lx->limit = 0; + lx->allocated = 0; + return dumbfile_open_ex( lx, &limit_xm_dfs ); +} + static int it_xm_read_instrument(IT_INSTRUMENT *instrument, XM_INSTRUMENT_EXTRA *extra, DUMBFILE *f) { uint32 size, bytes_read; unsigned short vol_points[24]; unsigned short pan_points[24]; int i, type; + const unsigned long max_size = 4 + 22 + 1 + 2 + 4 + 96 + 48 + 48 + 1 * 14 + 2 + 2; + unsigned long skip_end = 0; /* Header size. Tends to be more than the actual size of the structure. * So unread bytes must be skipped before reading the first sample * header. */ + + if ( limit_xm_resize( f, 4 ) < 0 ) return -1; + size = dumbfile_igetl(f); + if ( size == 0 ) size = max_size; + else if ( size > max_size ) + { + skip_end = size - max_size; + size = max_size; + } + + if ( limit_xm_resize( f, size - 4 ) < 0 ) return -1; + dumbfile_getnc(instrument->name, 22, f); instrument->name[22] = 0; + trim_whitespace(instrument->name, 22); instrument->filename[0] = 0; dumbfile_skip(f, 1); /* Instrument type. Should be 0, but seems random. */ extra->n_samples = dumbfile_igetw(f); @@ -385,12 +527,11 @@ static int it_xm_read_instrument(IT_INSTRUMENT *instrument, XM_INSTRUMENT_EXTRA if (extra->n_samples) { /* sample header size */ - dumbfile_skip(f, 4); // XXX can't be trusted, as there are trackers that write the wrong value here /*i = dumbfile_igetl(f); - if (i && i != 0x28) { // XXX some crap with 0 here - TRACE("XM error: unexpected sample header size\n"); - return -1; - }*/ + if (!i || i > 0x28) i = 0x28;*/ + dumbfile_skip(f, 4); + i = 0x28; + extra->sample_header_size = i; /* sample map */ for (i = 0; i < 96; i++) { @@ -476,7 +617,10 @@ static int it_xm_read_instrument(IT_INSTRUMENT *instrument, XM_INSTRUMENT_EXTRA for (i = 0; i < 96; i++) instrument->map_sample[i] = 0; - if (dumbfile_skip(f, size - bytes_read)) + if (size > bytes_read && dumbfile_skip(f, size - bytes_read)) + return -1; + + if (skip_end && limit_xm_skip_end(f, skip_end)) return -1; instrument->new_note_action = NNA_NOTE_CUT; @@ -531,6 +675,7 @@ static int it_xm_read_sample_header(IT_SAMPLE *sample, DUMBFILE *f) dumbfile_getnc(sample->name, 22, f); sample->name[22] = 0; + trim_whitespace(sample->name, 22); sample->filename[0] = 0; @@ -785,6 +930,7 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) return NULL; } sigdata->name[20] = 0; + trim_whitespace(sigdata->name, 20); if (dumbfile_getc(f) != 0x1A) { TRACE("XM error: 0x1A not found\n"); @@ -924,16 +1070,24 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) for (i = 0; i < sigdata->n_instruments; i++) { XM_INSTRUMENT_EXTRA extra; - if (it_xm_read_instrument(&sigdata->instrument[i], &extra, f) < 0) { + DUMBFILE * lf = dumbfile_limit_xm( f ); + if ( !lf ) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (it_xm_read_instrument(&sigdata->instrument[i], &extra, lf) < 0) { // XXX if ( ! i ) { TRACE("XM error: instrument %d\n", i+1); + dumbfile_close( lf ); _dumb_it_unload_sigdata(sigdata); return NULL; } else { + dumbfile_close( lf ); sigdata->n_instruments = i; break; } @@ -948,17 +1102,31 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) sigdata->sample = safe_realloc(sigdata->sample, sizeof(*sigdata->sample)*(total_samples+extra.n_samples)); if (!sigdata->sample) { + dumbfile_close( lf ); _dumb_it_unload_sigdata(sigdata); return NULL; } for (j = total_samples; j < total_samples+extra.n_samples; j++) sigdata->sample[j].data = NULL; + if ( limit_xm_resize( lf, 0 ) < 0 ) { + dumbfile_close( lf ); + _dumb_it_unload_sigdata( sigdata ); + return NULL; + } + /* read instrument's samples */ for (j = 0; j < extra.n_samples; j++) { IT_SAMPLE *sample = &sigdata->sample[total_samples+j]; - int b = it_xm_read_sample_header(sample, f); + int b; + if ( limit_xm_resize( lf, extra.sample_header_size ) < 0 ) { + dumbfile_close( lf ); + _dumb_it_unload_sigdata( sigdata ); + return NULL; + } + b = it_xm_read_sample_header(sample, lf); if (b < 0) { + dumbfile_close( lf ); _dumb_it_unload_sigdata(sigdata); return NULL; } @@ -975,12 +1143,15 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) } for (j = 0; j < extra.n_samples; j++) { if (it_xm_read_sample_data(&sigdata->sample[total_samples+j], roguebytes[j], f) != 0) { + dumbfile_close( lf ); _dumb_it_unload_sigdata(sigdata); return NULL; } } total_samples += extra.n_samples; } + + dumbfile_close( lf ); } sigdata->n_samples = total_samples; @@ -1012,8 +1183,16 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) for (i = 0; i < sigdata->n_instruments; i++) { XM_INSTRUMENT_EXTRA extra; - if (it_xm_read_instrument(&sigdata->instrument[i], &extra, f) < 0) { + DUMBFILE * lf = dumbfile_limit_xm( f ); + if ( !lf ) { + free(roguebytes); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (it_xm_read_instrument(&sigdata->instrument[i], &extra, lf) < 0) { TRACE("XM error: instrument %d\n", i+1); + dumbfile_close(lf); free(roguebytes); _dumb_it_unload_sigdata(sigdata); return NULL; @@ -1026,6 +1205,7 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) sigdata->sample = safe_realloc(sigdata->sample, sizeof(*sigdata->sample)*(total_samples+extra.n_samples)); if (!sigdata->sample) { + dumbfile_close( lf ); free(roguebytes); _dumb_it_unload_sigdata(sigdata); return NULL; @@ -1033,10 +1213,24 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) for (j = total_samples; j < total_samples+extra.n_samples; j++) sigdata->sample[j].data = NULL; + if ( limit_xm_resize( lf, 0 ) < 0 ) { + dumbfile_close( lf ); + free( roguebytes ); + _dumb_it_unload_sigdata( sigdata ); + return NULL; + } + /* read instrument's samples */ for (j = 0; j < extra.n_samples; j++) { IT_SAMPLE *sample = &sigdata->sample[total_samples+j]; - int b = it_xm_read_sample_header(sample, f); + int b; + if ( limit_xm_resize( lf, extra.sample_header_size ) < 0 ) { + dumbfile_close( lf ); + free( roguebytes ); + _dumb_it_unload_sigdata( sigdata ); + return NULL; + } + b = it_xm_read_sample_header(sample, lf); if (b < 0) { free(roguebytes); _dumb_it_unload_sigdata(sigdata); @@ -1055,6 +1249,8 @@ static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int * version) } total_samples += extra.n_samples; } + + dumbfile_close( lf ); } sigdata->n_samples = total_samples; diff --git a/dumb/src/it/xmeffect.c b/dumb/src/it/xmeffect.c index 2187a0b3f..1aa489222 100644 --- a/dumb/src/it/xmeffect.c +++ b/dumb/src/it/xmeffect.c @@ -167,6 +167,7 @@ if (log) printf(" - %2d %02X", effect, value); case XM_SET_GLOBAL_VOLUME: effect = IT_SET_GLOBAL_VOLUME; value *= 2; + if (value > 128) value = 128; break; case XM_KEY_OFF: @@ -187,6 +188,7 @@ if (log) printf(" - %2d %02X", effect, value); case EBASE+XM_E_SET_PANNING: effect = SBASE+IT_S_SET_PAN; break; case EBASE+XM_E_FINE_VOLSLIDE_UP: effect = IT_XM_FINE_VOLSLIDE_UP; break; case EBASE+XM_E_FINE_VOLSLIDE_DOWN: effect = IT_XM_FINE_VOLSLIDE_DOWN; break; + case EBASE+XM_E_SET_MIDI_MACRO: effect = SBASE+IT_S_SET_MIDI_MACRO; break; case EBASE + XM_E_FINE_PORTA_UP: effect = IT_PORTAMENTO_UP; diff --git a/dumb/vc6/dumb_static/dumb_static.vcproj b/dumb/vc6/dumb_static/dumb_static.vcproj index 128b3b9d9..3f1373cc3 100644 --- a/dumb/vc6/dumb_static/dumb_static.vcproj +++ b/dumb/vc6/dumb_static/dumb_static.vcproj @@ -877,6 +877,10 @@ RelativePath="..\..\src\helpers\barray.c" > + + @@ -1560,6 +1564,14 @@ RelativePath="..\..\src\it\loadmtm2.c" > + + + + @@ -1778,6 +1790,14 @@ RelativePath="..\..\src\it\readmtm.c" > + + + + @@ -1960,6 +1980,10 @@ + + diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 9d7bb3caf..bdd70322e 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -141,6 +141,9 @@ Note: All fields default to false unless mentioned otherwise. light = ; // This side's light level. Default is 0. lightabsolute = ; // true = 'light' is an absolute value. Default is // relative to the owning sector's light level. + lightfog = ; // true = This side's relative lighting is used even in + // foggy sectors. Default is to disable relative + // lighting in foggy sectors. nofakecontrast = ; // Disables use of fake contrast on this sidedef. smoothlighting = ; // Use smooth fake contrast. clipmidtex = ; // Side's mid textures are clipped to floor and ceiling. diff --git a/specs/usdf.txt b/specs/usdf.txt index fc955f02d..52b418d46 100644 --- a/specs/usdf.txt +++ b/specs/usdf.txt @@ -1,5 +1,5 @@ =============================================================================== -Universal Strife Dialog Format Specification v2.0 - 08/20/10 +Universal Strife Dialog Format Specification v2.1 - 01/06/13 Written by Braden "Blzut3" Obrzut - admin@maniacsvault.net @@ -11,7 +11,7 @@ Graf Zahl Quasar et al. - Copyright (c) 2010 Braden Obrzut. + Copyright (c) 2013 Braden Obrzut. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; @@ -19,6 +19,14 @@ et al. =============================================================================== +======================================= +Changes in v2.1 +======================================= + +* Pages are specified as starting as being indexed from 1 instead of 0. While + this technically renders the spec incompatible, all known implementations + used this convention as it was inline with the binary format. + ======================================= I. Grammar / Syntax ======================================= @@ -77,7 +85,7 @@ conversation // Starts a dialog. actor = ; // mobj for this conversation's actor. If previously // used, this will override the previous conversation. - page // Starts a new page. Pages are automatically numbered starting at 0. + page // Starts a new page. Pages are automatically numbered starting at 1. { name = ; // Name that goes in the upper left hand corner panel = ; // Name of lump to render as the background. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe982109d..331a19efd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -485,8 +485,10 @@ endif( BACKPATCH ) # Update svnrevision.h +get_target_property( UPDATEREVISION_EXE updaterevision LOCATION ) + add_custom_target( revision_check ALL - COMMAND ${CMAKE_BINARY_DIR}/tools/updaterevision/updaterevision . src/svnrevision.h + COMMAND ${UPDATEREVISION_EXE} . src/svnrevision.h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} DEPENDS updaterevision ) @@ -568,9 +570,12 @@ else( NO_ASM ) endif( X64 ) endif( NO_ASM ) +get_target_property( LEMON_EXE lemon LOCATION ) +get_target_property( RE2C_EXE re2c LOCATION ) + add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.h COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/xlat/xlat_parser.y . - COMMAND ${CMAKE_BINARY_DIR}/tools/lemon/lemon xlat_parser.y + COMMAND ${LEMON_EXE} xlat_parser.y WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/xlat/xlat_parser.y ) @@ -581,7 +586,7 @@ add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.c ${CMAKE_CURRE DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/zscript/zcc-parse.lemon ) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h - COMMAND ${CMAKE_BINARY_DIR}/tools/re2c/re2c --no-generation-date -s -o ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re + COMMAND ${RE2C_EXE} --no-generation-date -s -o ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re DEPENDS re2c ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) @@ -604,7 +609,37 @@ elseif( FLUIDSYNTH_FOUND ) add_definitions( -DHAVE_FLUIDSYNTH ) endif( DYN_FLUIDSYNTH ) +# Project files should be aware of the header files. We can GLOB these since +# there's generally a new cpp for every header so this file will get changed +if( WIN32 ) + set( EXTRA_HEADER_DIRS win32/*.h ) +else( WIN32 ) + set( EXTRA_HEADER_DIRS sdl/*.h ) +endif( WIN32 ) +file( GLOB HEADER_FILES + ${EXTRA_HEADER_DIRS} + fragglescript/*.h + g_doom/*.h + g_heretic/*.h + g_hexen/*.h + g_raven/*.h + g_shared/*.h + g_strife/*.h + intermission/*.h + menu/*.h + oplsynth/*.h + r_data/*.h + resourcefiles/*.h + sfmt/*.h + sound/*.h + textures/*.h + thingdef/*.h + xlat/*.h + *.h +) + add_executable( zdoom WIN32 + ${HEADER_FILES} autostart.cpp ${ASM_SOURCES} ${SYSTEM_SOURCES} @@ -818,6 +853,8 @@ add_executable( zdoom WIN32 oplsynth/music_opldumper_mididevice.cpp oplsynth/music_opl_mididevice.cpp oplsynth/opl_mus_player.cpp + oplsynth/dosbox/opl.cpp + oplsynth/OPL3.cpp resourcefiles/ancientzip.cpp resourcefiles/file_7z.cpp resourcefiles/file_grp.cpp @@ -965,3 +1002,7 @@ if( CMAKE_COMPILER_IS_GNUCXX ) set_source_files_properties( x86.cpp PROPERTIES COMPILE_FLAGS "-msse2 -mmmx" ) endif( SSE_MATTERS ) endif( CMAKE_COMPILER_IS_GNUCXX ) + +if( MSVC ) + set_target_properties(zdoom PROPERTIES LINK_FLAGS "/MANIFEST:NO") +endif( MSVC ) diff --git a/src/actionspecials.h b/src/actionspecials.h index b894a453a..1377def82 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -97,7 +97,7 @@ DEFINE_SPECIAL(FloorAndCeiling_LowerByValue, 95, 3, 3, 3) DEFINE_SPECIAL(FloorAndCeiling_RaiseByValue, 96, 3, 3, 3) DEFINE_SPECIAL(Ceiling_LowerAndCrushDist, 97, 3, 5, 5) DEFINE_SPECIAL(Sector_SetTranslucent, 98, 3, 4, 4) - +DEFINE_SPECIAL(Floor_RaiseAndCrushDoom, 99, 3, 4, 4) DEFINE_SPECIAL(Scroll_Texture_Left, 100, -1, -1, 2) DEFINE_SPECIAL(Scroll_Texture_Right, 101, -1, -1, 2) DEFINE_SPECIAL(Scroll_Texture_Up, 102, -1, -1, 2) diff --git a/src/actor.h b/src/actor.h index 970397d15..5e6641305 100644 --- a/src/actor.h +++ b/src/actor.h @@ -416,6 +416,7 @@ enum EBounceFlags // for them that are not present in ZDoom, so it is necessary to identify it properly. BOUNCE_MBF = 1<<12, // This in itself is not a valid mode, but replaces MBF's MF_BOUNCE flag. BOUNCE_AutoOffFloorOnly = 1<<13, // like BOUNCE_AutoOff, but only on floors + BOUNCE_UseBounceState = 1<<14, // Use Bounce[.*] states BOUNCE_TypeMask = BOUNCE_Walls | BOUNCE_Floors | BOUNCE_Ceilings | BOUNCE_Actors | BOUNCE_AutoOff | BOUNCE_HereticType | BOUNCE_MBF, @@ -666,7 +667,7 @@ public: virtual bool Massacre (); // Transforms the actor into a finely-ground paste - bool Grind(bool items); + virtual bool Grind(bool items); // Is the other actor on my team? bool IsTeammate (AActor *other); @@ -976,6 +977,11 @@ public: return GetClass()->FindState(2, names, exact); } + FState *FindState(int numnames, FName *names, bool exact = false) const + { + return GetClass()->FindState(numnames, names, exact); + } + bool HasSpecialDeathStates () const; }; diff --git a/src/am_map.cpp b/src/am_map.cpp index bb0215242..aa6634127 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -40,6 +40,8 @@ #include "c_bind.h" #include "farchive.h" #include "r_renderer.h" +#include "r_sky.h" +#include "sbar.h" #include "m_cheat.h" #include "i_system.h" @@ -1080,7 +1082,7 @@ void AM_Stop () { automapactive = false; stopped = true; - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); viewactive = true; } @@ -1179,7 +1181,7 @@ void AM_ToggleMap () if (dmflags2 & DF2_NO_AUTOMAP) return; - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); if (!automapactive) { AM_Start (); @@ -1190,7 +1192,7 @@ void AM_ToggleMap () if (am_overlay==1 && viewactive) { viewactive = false; - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); } else { @@ -1611,9 +1613,10 @@ void AM_drawSubsectors() angle_t rotation; sector_t tempsec; int floorlight, ceilinglight; + fixed_t scalex, scaley; double originx, originy; FDynamicColormap *colormap; - + mpoint_t originpt; for (int i = 0; i < numsubsectors; ++i) { @@ -1642,27 +1645,17 @@ void AM_drawSubsectors() // For lighting and texture determination sector_t *sec = Renderer->FakeFlat (subsectors[i].render_sector, &tempsec, &floorlight, &ceilinglight, false); // Find texture origin. - mpoint_t originpt = { -sec->GetXOffset(sector_t::floor) >> FRACTOMAPBITS, - sec->GetYOffset(sector_t::floor) >> FRACTOMAPBITS }; + originpt.x = -sec->GetXOffset(sector_t::floor) >> FRACTOMAPBITS; + originpt.y = sec->GetYOffset(sector_t::floor) >> FRACTOMAPBITS; rotation = 0 - sec->GetAngle(sector_t::floor); - // Apply the floor's rotation to the texture origin. - if (rotation != 0) - { - AM_rotate(&originpt.x, &originpt.y, rotation); - } - // Apply the automap's rotation to the texture origin. - if (am_rotate == 1 || (am_rotate == 2 && viewactive)) - { - rotation += ANG90 - players[consoleplayer].camera->angle; - AM_rotatePoint(&originpt.x, &originpt.y); - } - originx = f_x + ((originpt.x - m_x) * scale / float(1 << 24)); - originy = f_y + (f_h - (originpt.y - m_y) * scale / float(1 << 24)); // Coloring for the polygon colormap = sec->ColorMap; FTextureID maptex = sec->GetTexture(sector_t::floor); + scalex = sec->GetXScale(sector_t::floor); + scaley = sec->GetYScale(sector_t::floor); + #ifdef _3DFLOORS if (sec->e->XFloor.ffloors.Size()) @@ -1698,6 +1691,7 @@ void AM_drawSubsectors() F3DFloor *rover = sec->e->XFloor.ffloors[i]; if (!(rover->flags & FF_EXISTS)) continue; if (rover->flags & FF_FOG) continue; + if (!(rover->flags & FF_RENDERPLANES)) continue; if (rover->alpha == 0) continue; double roverz = rover->top.plane->ZatPoint(secx, secy); // Ignore 3D floors that are above or below the sector itself: @@ -1709,6 +1703,13 @@ void AM_drawSubsectors() { maptex = *(rover->top.texture); floorplane = rover->top.plane; + sector_t *model = rover->top.model; + int selector = (rover->flags & FF_INVERTPLANES) ? sector_t::floor : sector_t::ceiling; + rotation = 0 - model->GetAngle(selector); + scalex = model->GetXScale(selector); + scaley = model->GetYScale(selector); + originpt.x = -model->GetXOffset(selector) >> FRACTOMAPBITS; + originpt.y = model->GetYOffset(selector) >> FRACTOMAPBITS; break; } } @@ -1718,6 +1719,24 @@ void AM_drawSubsectors() colormap = light->extra_colormap; } #endif + if (maptex == skyflatnum) + { + continue; + } + + // Apply the floor's rotation to the texture origin. + if (rotation != 0) + { + AM_rotate(&originpt.x, &originpt.y, rotation); + } + // Apply the automap's rotation to the texture origin. + if (am_rotate == 1 || (am_rotate == 2 && viewactive)) + { + rotation += ANG90 - players[consoleplayer].camera->angle; + AM_rotatePoint(&originpt.x, &originpt.y); + } + originx = f_x + ((originpt.x - m_x) * scale / float(1 << 24)); + originy = f_y + (f_h - (originpt.y - m_y) * scale / float(1 << 24)); // If this subsector has not actually been seen yet (because you are cheating // to see it on the map), tint and desaturate it. @@ -1740,8 +1759,8 @@ void AM_drawSubsectors() screen->FillSimplePoly(TexMan(maptex), &points[0], points.Size(), originx, originy, - scale / (FIXED2FLOAT(sec->GetXScale(sector_t::floor)) * float(1 << MAPBITS)), - scale / (FIXED2FLOAT(sec->GetYScale(sector_t::floor)) * float(1 << MAPBITS)), + scale / (FIXED2DBL(scalex) * float(1 << MAPBITS)), + scale / (FIXED2DBL(scaley) * float(1 << MAPBITS)), rotation, colormap, floorlight diff --git a/src/b_bot.cpp b/src/b_bot.cpp index 5d18acea4..cf519fc1d 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -51,15 +51,16 @@ void FCajunMaster::ClearPlayer (int i, bool keepTeam) players[i].mo = NULL; } botinfo_t *bot = botinfo; - while (bot && stricmp (players[i].userinfo.netname, bot->name)) + while (bot && stricmp (players[i].userinfo.GetName(), bot->name)) bot = bot->next; if (bot) { bot->inuse = false; - bot->lastteam = keepTeam ? players[i].userinfo.team : TEAM_NONE; + bot->lastteam = keepTeam ? players[i].userinfo.GetTeam() : TEAM_NONE; } players[i].~player_t(); ::new(&players[i]) player_t; + players[i].userinfo.Reset(); playeringame[i] = false; } diff --git a/src/b_game.cpp b/src/b_game.cpp index e97253c53..a4c0a1842 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -140,7 +140,7 @@ void FCajunMaster::Main (int buf) //Check if player should go observer. Or un observe if (bot_observer && !observer && !netgame) { - Printf ("%s is now observer\n", players[consoleplayer].userinfo.netname); + Printf ("%s is now observer\n", players[consoleplayer].userinfo.GetName()); observer = true; players[consoleplayer].mo->UnlinkFromWorld (); players[consoleplayer].mo->flags = MF_DROPOFF|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOTDMATCH|MF_NOGRAVITY|MF_FRIENDLY; @@ -149,7 +149,7 @@ void FCajunMaster::Main (int buf) } else if (!bot_observer && observer && !netgame) //Go back { - Printf ("%s returned to the fray\n", players[consoleplayer].userinfo.netname); + Printf ("%s returned to the fray\n", players[consoleplayer].userinfo.GetName()); observer = false; players[consoleplayer].mo->UnlinkFromWorld (); players[consoleplayer].mo->flags = MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH|MF_FRIENDLY; @@ -218,7 +218,7 @@ void FCajunMaster::End () { if (deathmatch) { - getspawned.Push(players[i].userinfo.netname); + getspawned.Push(players[i].userinfo.GetName()); } CleanBotstuff (&players[i]); } @@ -353,7 +353,7 @@ void FCajunMaster::DoAddBot (int bnum, char *info) if (!deathmatch && playerstarts[bnum].type == 0) { Printf ("%s tried to join, but there was no player %d start\n", - players[bnum].userinfo.netname, bnum+1); + players[bnum].userinfo.GetName(), bnum+1); ClearPlayer (bnum, false); // Make the bot inactive again if (botnum > 0) { @@ -370,9 +370,9 @@ void FCajunMaster::DoAddBot (int bnum, char *info) botingame[bnum] = true; if (teamplay) - Printf ("%s joined the %s team\n", players[bnum].userinfo.netname,Teams[players[bnum].userinfo.team].GetName ()); + Printf ("%s joined the %s team\n", players[bnum].userinfo.GetName(), Teams[players[bnum].userinfo.GetTeam()].GetName()); else - Printf ("%s joined the game\n", players[bnum].userinfo.netname); + Printf ("%s joined the game\n", players[bnum].userinfo.GetName()); G_DoReborn (bnum, true); if (StatusBar != NULL) diff --git a/src/bbannouncer.cpp b/src/bbannouncer.cpp index b65671eaf..91583af8c 100644 --- a/src/bbannouncer.cpp +++ b/src/bbannouncer.cpp @@ -231,7 +231,7 @@ bool AnnounceKill (AActor *killer, AActor *killee) if (killer == NULL) { // The world killed the player - if (killee->player->userinfo.gender == GENDER_MALE) + if (killee->player->userinfo.GetGender() == GENDER_MALE) { // Only males have scrotums to separate choice = &WorldKillSounds[rannum % 3]; } @@ -244,11 +244,11 @@ bool AnnounceKill (AActor *killer, AActor *killee) else if (killer == killee) { // The player killed self choice = &SuicideSounds[rannum & 3]; - killerName = killer->player->userinfo.netname; + killerName = killer->player->userinfo.GetName(); } else { // Another player did the killing - if (killee->player->userinfo.gender == GENDER_MALE) + if (killee->player->userinfo.GetGender() == GENDER_MALE) { // Only males can be castrated choice = &KillSounds[rannum % countof(KillSounds)]; } @@ -256,7 +256,7 @@ bool AnnounceKill (AActor *killer, AActor *killee) { choice = &KillSounds[rannum % (countof(KillSounds) - 1)]; } - killerName = killer->player->userinfo.netname; + killerName = killer->player->userinfo.GetName(); // Blood only plays the announcement sound on the killer's // computer. I think it sounds neater to also hear it on @@ -269,8 +269,8 @@ bool AnnounceKill (AActor *killer, AActor *killee) { char assembled[1024]; - SexMessage (message, assembled, killee->player->userinfo.gender, - killee->player->userinfo.netname, killerName); + SexMessage (message, assembled, killee->player->userinfo.GetGender(), + killee->player->userinfo.GetName(), killerName); Printf (PRINT_MEDIUM, "%s\n", assembled); } if (playSound) @@ -301,8 +301,8 @@ bool AnnounceTelefrag (AActor *killer, AActor *killee) { char assembled[1024]; - SexMessage (message, assembled, killee->player->userinfo.gender, - killee->player->userinfo.netname, killer->player->userinfo.netname); + SexMessage (message, assembled, killee->player->userinfo.GetGender(), + killee->player->userinfo.GetName(), killer->player->userinfo.GetName()); Printf (PRINT_MEDIUM, "%s\n", assembled); } if (killee->CheckLocalView (consoleplayer) || diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index c67c2a24e..046da3bc7 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -327,7 +327,7 @@ CCMD (hxvisit) CCMD (changemap) { - if (who == NULL) + if (who == NULL || !usergame) { Printf ("Use the map command when not in a game.\n"); return; diff --git a/src/c_console.cpp b/src/c_console.cpp index 8e439df6c..3f4ae9e5f 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -1134,7 +1134,7 @@ void C_DrawConsole (bool hw2d) (viewwindowx || viewwindowy) && viewactive) { - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } oldbottom = ConBottom; @@ -1224,8 +1224,8 @@ void C_DrawConsole (bool hw2d) { screen->Dim (PalEntry ((unsigned char)(player->BlendR*255), (unsigned char)(player->BlendG*255), (unsigned char)(player->BlendB*255)), player->BlendA, 0, ConBottom, screen->GetWidth(), screen->GetHeight() - ConBottom); - SB_state = screen->GetPageCount (); - BorderNeedRefresh = screen->GetPageCount (); + ST_SetNeedRefresh(); + V_SetBorderNeedRefresh(); } } } diff --git a/src/c_cvars.cpp b/src/c_cvars.cpp index 51413c37d..19187353f 100644 --- a/src/c_cvars.cpp +++ b/src/c_cvars.cpp @@ -132,10 +132,10 @@ FBaseCVar::~FBaseCVar () } } -void FBaseCVar::ForceSet (UCVarValue value, ECVarType type) +void FBaseCVar::ForceSet (UCVarValue value, ECVarType type, bool nouserinfosend) { DoSet (value, type); - if (Flags & CVAR_USERINFO) + if ((Flags & CVAR_USERINFO) && !nouserinfosend && !(Flags & CVAR_IGNORE)) D_UserInfoChanged (this); if (m_UseCallback) Callback (); @@ -266,7 +266,7 @@ static GUID cGUID; static char truestr[] = "true"; static char falsestr[] = "false"; -char *FBaseCVar::ToString (UCVarValue value, ECVarType type) +const char *FBaseCVar::ToString (UCVarValue value, ECVarType type) { switch (type) { @@ -849,9 +849,7 @@ UCVarValue FStringCVar::GetFavoriteRepDefault (ECVarType *type) const void FStringCVar::SetGenericRepDefault (UCVarValue value, ECVarType type) { - if (DefaultValue) - delete[] DefaultValue; - DefaultValue = ToString (value, type); + ReplaceString(&DefaultValue, ToString(value, type)); if (Flags & CVAR_ISDEFAULT) { SetGenericRep (value, type); @@ -1274,52 +1272,56 @@ static int STACK_ARGS sortcvars (const void *a, const void *b) void FilterCompactCVars (TArray &cvars, DWORD filter) { - FBaseCVar *cvar = CVars; - while (cvar) + // Accumulate all cvars that match the filter flags. + for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->m_Next) { - if (cvar->Flags & filter) - cvars.Push (cvar); - cvar = cvar->m_Next; + if ((cvar->Flags & filter) && !(cvar->Flags & CVAR_IGNORE)) + cvars.Push(cvar); } - if (cvars.Size () > 0) + // Now sort them, so they're in a deterministic order and not whatever + // order the linker put them in. + if (cvars.Size() > 0) { - cvars.ShrinkToFit (); - qsort (&cvars[0], cvars.Size(), sizeof(FBaseCVar *), sortcvars); + qsort(&cvars[0], cvars.Size(), sizeof(FBaseCVar *), sortcvars); } } void C_WriteCVars (BYTE **demo_p, DWORD filter, bool compact) { - FBaseCVar *cvar = CVars; - BYTE *ptr = *demo_p; + FString dump = C_GetMassCVarString(filter, compact); + size_t dumplen = dump.Len() + 1; // include terminating \0 + memcpy(*demo_p, dump.GetChars(), dumplen); + *demo_p += dumplen; +} + +FString C_GetMassCVarString (DWORD filter, bool compact) +{ + FBaseCVar *cvar; + FString dump; if (compact) { TArray cvars; - ptr += sprintf ((char *)ptr, "\\\\%ux", filter); - FilterCompactCVars (cvars, filter); + dump.AppendFormat("\\\\%ux", filter); + FilterCompactCVars(cvars, filter); while (cvars.Pop (cvar)) { - UCVarValue val = cvar->GetGenericRep (CVAR_String); - ptr += sprintf ((char *)ptr, "\\%s", val.String); + UCVarValue val = cvar->GetGenericRep(CVAR_String); + dump << '\\' << val.String; } } else { - cvar = CVars; - while (cvar) + for (cvar = CVars; cvar != NULL; cvar = cvar->m_Next) { - if ((cvar->Flags & filter) && !(cvar->Flags & CVAR_NOSAVE)) + if ((cvar->Flags & filter) && !(cvar->Flags & (CVAR_NOSAVE|CVAR_IGNORE))) { - UCVarValue val = cvar->GetGenericRep (CVAR_String); - ptr += sprintf ((char *)ptr, "\\%s\\%s", - cvar->GetName (), val.String); + UCVarValue val = cvar->GetGenericRep(CVAR_String); + dump << '\\' << cvar->GetName() << '\\' << val.String; } - cvar = cvar->m_Next; } } - - *demo_p = ptr + 1; + return dump; } void C_ReadCVars (BYTE **demo_p) @@ -1390,58 +1392,42 @@ void C_ReadCVars (BYTE **demo_p) *demo_p += strlen (*((char **)demo_p)) + 1; } -static struct backup_s +struct FCVarBackup { - char *name, *string; -} CVarBackups[MAX_DEMOCVARS]; - -static int numbackedup = 0; + FString Name, String; +}; +static TArray CVarBackups; void C_BackupCVars (void) { - struct backup_s *backup = CVarBackups; - FBaseCVar *cvar = CVars; + assert(CVarBackups.Size() == 0); + CVarBackups.Clear(); - while (cvar) + FCVarBackup backup; + + for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->m_Next) { - if ((cvar->Flags & (CVAR_SERVERINFO|CVAR_DEMOSAVE)) - && !(cvar->Flags & CVAR_LATCH)) + if ((cvar->Flags & (CVAR_SERVERINFO|CVAR_DEMOSAVE)) && !(cvar->Flags & CVAR_LATCH)) { - if (backup == &CVarBackups[MAX_DEMOCVARS]) - I_Error ("C_BackupDemoCVars: Too many cvars to save (%d)", MAX_DEMOCVARS); - backup->name = copystring (cvar->GetName()); - backup->string = copystring (cvar->GetGenericRep (CVAR_String).String); - backup++; + backup.Name = cvar->GetName(); + backup.String = cvar->GetGenericRep(CVAR_String).String; + CVarBackups.Push(backup); } - cvar = cvar->m_Next; } - numbackedup = int(backup - CVarBackups); } void C_RestoreCVars (void) { - struct backup_s *backup = CVarBackups; - int i; - - for (i = numbackedup; i; i--, backup++) + for (unsigned int i = 0; i < CVarBackups.Size(); ++i) { - cvar_set (backup->name, backup->string); + cvar_set(CVarBackups[i].Name, CVarBackups[i].String); } C_ForgetCVars(); } void C_ForgetCVars (void) { - struct backup_s *backup = CVarBackups; - int i; - - for (i = numbackedup; i; i--, backup++) - { - delete[] backup->name; - delete[] backup->string; - backup->name = backup->string = NULL; - } - numbackedup = 0; + CVarBackups.Clear(); } FBaseCVar *FindCVar (const char *var_name, FBaseCVar **prev) @@ -1489,6 +1475,30 @@ FBaseCVar *FindCVarSub (const char *var_name, int namelen) return var; } +//=========================================================================== +// +// C_CreateCVar +// +// Create a new cvar with the specified name and type. It should not already +// exist. +// +//=========================================================================== + +FBaseCVar *C_CreateCVar(const char *var_name, ECVarType var_type, DWORD flags) +{ + assert(FindCVar(var_name, NULL) == NULL); + flags |= CVAR_AUTO; + switch (var_type) + { + case CVAR_Bool: return new FBoolCVar(var_name, 0, flags); + case CVAR_Int: return new FIntCVar(var_name, 0, flags); + case CVAR_Float: return new FFloatCVar(var_name, 0, flags); + case CVAR_String: return new FStringCVar(var_name, NULL, flags); + case CVAR_Color: return new FColorCVar(var_name, 0, flags); + default: return NULL; + } +} + void UnlatchCVars (void) { FLatchedValue var; @@ -1522,33 +1532,14 @@ void C_SetCVarsToDefaults (void) } } -void C_ArchiveCVars (FConfigFile *f, int type) +void C_ArchiveCVars (FConfigFile *f, uint32 filter) { - // type 0: Game-specific cvars - // type 1: Global cvars - // type 2: Unknown cvars - // type 3: Unknown global cvars - // type 4: User info cvars - // type 5: Server info cvars - static const DWORD filters[6] = - { - CVAR_ARCHIVE, - CVAR_ARCHIVE|CVAR_GLOBALCONFIG, - CVAR_ARCHIVE|CVAR_AUTO, - CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_AUTO, - CVAR_ARCHIVE|CVAR_USERINFO, - CVAR_ARCHIVE|CVAR_SERVERINFO - }; - FBaseCVar *cvar = CVars; - DWORD filter; - - filter = filters[type]; while (cvar) { if ((cvar->Flags & - (CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_AUTO|CVAR_USERINFO|CVAR_SERVERINFO|CVAR_NOSAVE)) + (CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_MOD|CVAR_AUTO|CVAR_USERINFO|CVAR_SERVERINFO|CVAR_NOSAVE)) == filter) { UCVarValue val; @@ -1679,14 +1670,16 @@ void FBaseCVar::ListVars (const char *filter, bool plain) else { ++count; - Printf ("%c%c%c %s : :%s\n", + Printf ("%c%c%c%c%c %s = %s\n", flags & CVAR_ARCHIVE ? 'A' : ' ', flags & CVAR_USERINFO ? 'U' : - flags & CVAR_SERVERINFO ? 'S' : - flags & CVAR_AUTO ? 'C' : ' ', + flags & CVAR_SERVERINFO ? 'S' : + flags & CVAR_AUTO ? 'C' : ' ', flags & CVAR_NOSET ? '-' : - flags & CVAR_LATCH ? 'L' : - flags & CVAR_UNSETTABLE ? '*' : ' ', + flags & CVAR_LATCH ? 'L' : + flags & CVAR_UNSETTABLE ? '*' : ' ', + flags & CVAR_MOD ? 'M' : ' ', + flags & CVAR_IGNORE ? 'X' : ' ', var->GetName(), var->GetGenericRep (CVAR_String).String); } diff --git a/src/c_cvars.h b/src/c_cvars.h index d0ac84fe5..17d3f7b1d 100644 --- a/src/c_cvars.h +++ b/src/c_cvars.h @@ -61,6 +61,8 @@ enum CVAR_GLOBALCONFIG = 1024, // cvar is saved to global config section CVAR_VIDEOCONFIG = 2048, // cvar is saved to video config section (not implemented) CVAR_NOSAVE = 4096, // when used with CVAR_SERVERINFO, do not save var to savegame + CVAR_MOD = 8192, // cvar was defined by a mod + CVAR_IGNORE = 16384,// do not send cvar across the network/inaccesible from ACS (dummy mod cvar) }; union UCVarValue @@ -68,7 +70,7 @@ union UCVarValue bool Bool; int Int; float Float; - char *String; + const char *String; const GUID *pGUID; }; @@ -96,9 +98,10 @@ public: inline const char *GetName () const { return Name; } inline uint32 GetFlags () const { return Flags; } + inline FBaseCVar *GetNext() const { return m_Next; } void CmdSet (const char *newval); - void ForceSet (UCVarValue value, ECVarType type); + void ForceSet (UCVarValue value, ECVarType type, bool nouserinfosend=false); void SetGenericRep (UCVarValue value, ECVarType type); void ResetToDefault (); void SetArchiveBit () { Flags |= CVAR_ARCHIVE; } @@ -129,7 +132,7 @@ protected: static bool ToBool (UCVarValue value, ECVarType type); static int ToInt (UCVarValue value, ECVarType type); static float ToFloat (UCVarValue value, ECVarType type); - static char *ToString (UCVarValue value, ECVarType type); + static const char *ToString (UCVarValue value, ECVarType type); static const GUID *ToGUID (UCVarValue value, ECVarType type); static UCVarValue FromBool (bool value, ECVarType type); static UCVarValue FromInt (int value, ECVarType type); @@ -150,18 +153,22 @@ private: static bool m_UseCallback; static bool m_DoNoSet; - friend void C_WriteCVars (BYTE **demo_p, uint32 filter, bool compact); + friend FString C_GetMassCVarString (uint32 filter, bool compact); friend void C_ReadCVars (BYTE **demo_p); friend void C_BackupCVars (void); friend FBaseCVar *FindCVar (const char *var_name, FBaseCVar **prev); friend FBaseCVar *FindCVarSub (const char *var_name, int namelen); friend void UnlatchCVars (void); - friend void C_ArchiveCVars (FConfigFile *f, int type); + friend void C_ArchiveCVars (FConfigFile *f, uint32 filter); friend void C_SetCVarsToDefaults (void); friend void FilterCompactCVars (TArray &cvars, uint32 filter); friend void C_DeinitConsole(); }; +// Returns a string with all cvars whose flags match filter. In compact mode, +// the cvar names are omitted to save space. +FString C_GetMassCVarString (uint32 filter, bool compact=false); + // Writes all cvars that could effect demo sync to *demo_p. These are // cvars that have either CVAR_SERVERINFO or CVAR_DEMOSAVE set. void C_WriteCVars (BYTE **demo_p, uint32 filter, bool compact=false); @@ -177,11 +184,14 @@ void C_BackupCVars (void); FBaseCVar *FindCVar (const char *var_name, FBaseCVar **prev); FBaseCVar *FindCVarSub (const char *var_name, int namelen); +// Create a new cvar with the specified name and type +FBaseCVar *C_CreateCVar(const char *var_name, ECVarType var_type, DWORD flags); + // Called from G_InitNew() void UnlatchCVars (void); // archive cvars to FILE f -void C_ArchiveCVars (FConfigFile *f, int type); +void C_ArchiveCVars (FConfigFile *f, uint32 filter); // initialize cvars to default values after they are created void C_SetCVarsToDefaults (void); @@ -404,10 +414,6 @@ inline FBaseCVar *cvar_forceset (const char *var_name, const BYTE *value) { retu -// Maximum number of cvars that can be saved across a demo. If you need -// to save more, bump this up. -#define MAX_DEMOCVARS 32 - // Restore demo cvars. Called after demo playback to restore all cvars // that might possibly have been changed during the course of demo playback. void C_RestoreCVars (void); @@ -425,5 +431,6 @@ void C_ForgetCVars (void); #define EXTERN_CVAR(type,name) extern F##type##CVar name; +extern FBaseCVar *CVars; #endif //__C_CVARS_H__ diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 83c6ae37d..7b701bd53 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -1019,32 +1019,7 @@ FConsoleAlias::~FConsoleAlias () m_Command[1] = m_Command[0] = FString(); } -FString BuildString (int argc, char **argv) -{ - if (argc == 1) - { - return *argv; - } - else - { - FString buf; - int arg; - - for (arg = 0; arg < argc; arg++) - { - if (strchr (argv[arg], ' ')) - { - buf.AppendFormat ("\"%s\" ", argv[arg]); - } - else - { - buf.AppendFormat ("%s ", argv[arg]); - } - } - return buf; - } -} - +// Given an argument vector, reconstitute the command line it could have been produced from. FString BuildString (int argc, FString *argv) { if (argc == 1) @@ -1058,8 +1033,23 @@ FString BuildString (int argc, FString *argv) for (arg = 0; arg < argc; arg++) { - if (strchr (argv[arg], ' ')) - { + if (strchr(argv[arg], '"')) + { // If it contains one or more quotes, we need to escape them. + buf << '"'; + long substr_start = 0, quotepos; + while ((quotepos = argv[arg].IndexOf('"', substr_start)) >= 0) + { + if (substr_start < quotepos) + { + buf << argv[arg].Mid(substr_start, quotepos - substr_start); + } + buf << "\\\""; + substr_start = quotepos + 1; + } + buf << argv[arg].Mid(substr_start) << "\" "; + } + else if (strchr(argv[arg], ' ')) + { // If it contains a space, it needs to be quoted. buf << '"' << argv[arg] << "\" "; } else diff --git a/src/c_dispatch.h b/src/c_dispatch.h index 5aa3f9870..12ea559de 100644 --- a/src/c_dispatch.h +++ b/src/c_dispatch.h @@ -61,7 +61,6 @@ void C_SetAlias (const char *name, const char *cmd); void C_ClearAliases (); // build a single string out of multiple strings -FString BuildString (int argc, char **argv); FString BuildString (int argc, FString *argv); // Class that can parse command lines diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp index e3766fd02..557c80cf8 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -537,6 +537,7 @@ void CreatePath(const char *fn) } if (mkdir(copy, 0755) == -1) { // failed + free(copy); return; } exists: if (p != NULL) @@ -755,7 +756,7 @@ FString strbin1 (const char *start) // //========================================================================== -void CleanseString(char *str) +char *CleanseString(char *str) { char *escape = strrchr(str, TEXTCOLOR_ESCAPE); if (escape != NULL) @@ -773,6 +774,7 @@ void CleanseString(char *str) } } } + return str; } //========================================================================== diff --git a/src/cmdlib.h b/src/cmdlib.h index 7c29644d5..e3a9d3d08 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -47,7 +47,7 @@ const char *myasctime (); int strbin (char *str); FString strbin1 (const char *start); -void CleanseString (char *str); +char *CleanseString (char *str); void CreatePath(const char * fn); diff --git a/src/compatibility.cpp b/src/compatibility.cpp index 2a721c33f..839d60dc5 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -50,6 +50,7 @@ #include "g_level.h" #include "p_lnspec.h" #include "r_state.h" +#include "w_wad.h" // MACROS ------------------------------------------------------------------ @@ -58,7 +59,7 @@ struct FCompatOption { const char *Name; - int CompatFlags; + DWORD CompatFlags; int WhichSlot; }; @@ -76,7 +77,9 @@ enum CP_SETFLAGS, CP_SETSPECIAL, CP_CLEARSPECIAL, - CP_SETACTIVATION + CP_SETACTIVATION, + CP_SECTORFLOOROFFSET, + CP_SETWALLYSCALE, }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -99,6 +102,7 @@ static FCompatOption Options[] = { "resetplayerspeed", BCOMPATF_RESETPLAYERSPEED, SLOT_BCOMPAT }, { "vileghosts", BCOMPATF_VILEGHOSTS, SLOT_BCOMPAT }, { "ignoreteleporttags", BCOMPATF_BADTELEPORTERS, SLOT_BCOMPAT }, + { "rebuildnodes", BCOMPATF_REBUILDNODES, SLOT_BCOMPAT }, // list copied from g_mapinfo.cpp { "shorttex", COMPATF_SHORTTEX, SLOT_COMPAT }, @@ -136,6 +140,16 @@ static FCompatOption Options[] = { NULL, 0, 0 } }; +static const char *const LineSides[] = +{ + "Front", "Back", NULL +}; + +static const char *const WallTiers[] = +{ + "Top", "Mid", "Bot", NULL +}; + static TArray CompatParams; static int ii_compatparams; @@ -258,6 +272,28 @@ void ParseCompatibility() sc.MustGetNumber(); CompatParams.Push(sc.Number); } + else if (sc.Compare("sectorflooroffset")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_SECTORFLOOROFFSET); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + sc.MustGetFloat(); + CompatParams.Push(FLOAT2FIXED(sc.Float)); + } + else if (sc.Compare("setwallyscale")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_SETWALLYSCALE); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + sc.MustGetString(); + CompatParams.Push(sc.MustMatchString(LineSides)); + sc.MustGetString(); + CompatParams.Push(sc.MustMatchString(WallTiers)); + sc.MustGetFloat(); + CompatParams.Push(FLOAT2FIXED(sc.Float)); + } else { sc.UnGet(); @@ -437,6 +473,30 @@ void SetCompatibilityParams() i += 3; break; } + case CP_SECTORFLOOROFFSET: + { + if (CompatParams[i+1] < numsectors) + { + sector_t *sec = §ors[CompatParams[i+1]]; + sec->floorplane.ChangeHeight(CompatParams[i+2]); + sec->ChangePlaneTexZ(sector_t::floor, CompatParams[i+2]); + } + i += 3; + break; + } + case CP_SETWALLYSCALE: + { + if (CompatParams[i+1] < numlines) + { + side_t *side = lines[CompatParams[i+1]].sidedef[CompatParams[i+2]]; + if (side != NULL) + { + side->SetTextureYScale(CompatParams[i+3], CompatParams[i+4]); + } + } + i += 5; + break; + } } } } diff --git a/src/configfile.cpp b/src/configfile.cpp index fcbed42c3..b8e37d91c 100644 --- a/src/configfile.cpp +++ b/src/configfile.cpp @@ -38,9 +38,12 @@ #include "doomtype.h" #include "configfile.h" +#include "m_random.h" #define READBUFFERSIZE 256 +static FRandom pr_endtag; + //==================================================================== // // FConfigFile Constructor @@ -679,13 +682,68 @@ bool FConfigFile::ReadConfig (void *file) whiteprobe++; } *(whiteprobe - 1) = 0; - NewConfigEntry (section, start, whiteprobe); + // Check for multi-line value + if (whiteprobe[0] == '<' && whiteprobe[1] == '<' && whiteprobe[2] == '<' && whiteprobe[3] != '\0') + { + ReadMultiLineValue (file, section, start, whiteprobe + 3); + } + else + { + NewConfigEntry (section, start, whiteprobe); + } } } } return true; } +//==================================================================== +// +// FConfigFile :: ReadMultiLineValue +// +// Reads a multi-line value, with format as follows: +// +// key=<<>>ENDTAG +// +// The final ENDTAG must be on a line all by itself. +// +//==================================================================== + +FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(void *file, FConfigSection *section, const char *key, const char *endtag) +{ + char readbuf[READBUFFERSIZE]; + FString value; + size_t endlen = strlen(endtag); + + // Keep on reading lines until we reach a line that matches >>>endtag + while (ReadLine(readbuf, READBUFFERSIZE, file) != NULL) + { + // Does the start of this line match the endtag? + if (readbuf[0] == '>' && readbuf[1] == '>' && readbuf[2] == '>' && + strncmp(readbuf + 3, endtag, endlen) == 0) + { // Is there nothing but line break characters after the match? + size_t i; + for (i = endlen + 3; readbuf[i] != '\0'; ++i) + { + if (readbuf[i] != '\n' && readbuf[i] != '\r') + { // Not a line break character + break; + } + } + if (readbuf[i] == '\0') + { // We're done; strip the previous line's line breaks, since it's not part of the value. + value.StripRight("\n\r"); + } + break; + } + // Append this line to the value. + value << readbuf; + } + return NewConfigEntry(section, key, value); +} + //==================================================================== // // FConfigFile :: ReadLine @@ -732,7 +790,16 @@ bool FConfigFile::WriteConfigFile () const fprintf (file, "[%s]\n", section->Name); while (entry != NULL) { - fprintf (file, "%s=%s\n", entry->Key, entry->Value); + if (strpbrk(entry->Value, "\r\n") == NULL) + { // Single-line value + fprintf (file, "%s=%s\n", entry->Key, entry->Value); + } + else + { // Multi-line value + const char *endtag = GenerateEndTag(entry->Value); + fprintf (file, "%s=<<<%s\n%s\n>>>%s\n", entry->Key, + endtag, entry->Value, endtag); + } entry = entry->Next; } section = section->Next; @@ -742,6 +809,44 @@ bool FConfigFile::WriteConfigFile () const return true; } +//==================================================================== +// +// FConfigFile :: GenerateEndTag +// +// Generates a terminator sequence for multi-line values that does +// not appear anywhere in the value. +// +//==================================================================== + +const char *FConfigFile::GenerateEndTag(const char *value) +{ + static const char Base64Table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; + static char EndTag[25] = "EOV-"; + + // Try different 20-character sequences until we find one that + // isn't in the value. We create the sequences by generating two + // 64-bit random numbers and Base64 encoding the first 15 bytes + // from them. + union { QWORD rand_num[2]; BYTE rand_bytes[16]; }; + do + { + rand_num[0] = pr_endtag.GenRand64(); + rand_num[1] = pr_endtag.GenRand64(); + + for (int i = 0; i < 5; ++i) + { + DWORD three_bytes = (rand_bytes[i*3] << 16) | (rand_bytes[i*3+1] << 8) | (rand_bytes[i*3+2]); + EndTag[4+i*4 ] = Base64Table[rand_bytes[i*3] >> 2]; + EndTag[4+i*4+1] = Base64Table[((rand_bytes[i*3] & 3) << 4) | (rand_bytes[i*3+1] >> 4)]; + EndTag[4+i*4+2] = Base64Table[((rand_bytes[i*3+1] & 15) << 2) | (rand_bytes[i*3+2] >> 6)]; + EndTag[4+i*4+3] = Base64Table[rand_bytes[i*3+2] & 63]; + } + } + while (strstr(value, EndTag) != NULL); + return EndTag; +} + //==================================================================== // // FConfigFile :: WriteCommentHeader diff --git a/src/configfile.h b/src/configfile.h index b704dd359..25c366f01 100644 --- a/src/configfile.h +++ b/src/configfile.h @@ -78,6 +78,7 @@ protected: virtual char *ReadLine (char *string, int n, void *file) const; bool ReadConfig (void *file); + static const char *GenerateEndTag(const char *value); bool OkayToWrite; bool FileExisted; @@ -110,6 +111,7 @@ private: FConfigEntry *FindEntry (FConfigSection *section, const char *key) const; FConfigSection *NewConfigSection (const char *name); FConfigEntry *NewConfigEntry (FConfigSection *section, const char *key, const char *value); + FConfigEntry *ReadMultiLineValue (void *file, FConfigSection *section, const char *key, const char *terminator); void SetSectionNote (FConfigSection *section, const char *note); public: diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 0bad0c3ed..e53a1d362 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -441,7 +441,8 @@ int FIWadManager::IdentifyVersion (TArray &wadfiles, const char *iwad, "heretic shadow of the serpent riders/base", "hexen/base", "hexen deathkings of the dark citadel/base", - "ultimate doom/base" + "ultimate doom/base", + "DOOM 3 BFG Edition/base/wads" }; steam_path += "/SteamApps/common/"; for (i = 0; i < countof(steam_dirs); ++i) diff --git a/src/d_main.cpp b/src/d_main.cpp index 049d0a933..7d3ecb746 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -158,7 +158,6 @@ EXTERN_CVAR (Bool, sv_unlimited_pickup) extern int testingmode; extern bool setmodeneeded; -extern bool netdemo; extern int NewWidth, NewHeight, NewBits, DisplayBits; EXTERN_CVAR (Bool, st_scale) extern bool gameisdead; @@ -698,21 +697,21 @@ void D_Display () if (screen->Lock (false)) { - SB_state = screen->GetPageCount (); - BorderNeedRefresh = screen->GetPageCount (); + ST_SetNeedRefresh(); + V_SetBorderNeedRefresh(); } // [RH] Allow temporarily disabling wipes if (NoWipe) { - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); NoWipe--; wipe = false; wipegamestate = gamestate; } else if (gamestate != wipegamestate && gamestate != GS_FULLCONSOLE && gamestate != GS_TITLELEVEL) { // save the current screen if about to wipe - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); switch (wipegamestate) { default: @@ -775,15 +774,14 @@ void D_Display () } screen->SetBlendingRect(viewwindowx, viewwindowy, viewwindowx + viewwidth, viewwindowy + viewheight); - P_CheckPlayerSprites(); P_PredictPlayer(&players[consoleplayer]); Renderer->RenderView(&players[consoleplayer]); P_UnPredictPlayer(); if ((hw2d = screen->Begin2D(viewactive))) { // Redraw everything every frame when using 2D accel - SB_state = screen->GetPageCount(); - BorderNeedRefresh = screen->GetPageCount(); + ST_SetNeedRefresh(); + V_SetBorderNeedRefresh(); } Renderer->DrawRemainingPlayerSprites(); screen->DrawBlendingRect(); @@ -804,9 +802,10 @@ void D_Display () if (hud_althud && viewheight == SCREENHEIGHT && screenblocks > 10) { - StatusBar->DrawBottomStuff (HUD_None); + StatusBar->DrawBottomStuff (HUD_AltHud); if (DrawFSHUD || automapactive) DrawHUD(); - StatusBar->DrawTopStuff (HUD_None); + StatusBar->Draw (HUD_AltHud); + StatusBar->DrawTopStuff (HUD_AltHud); } else if (viewheight == SCREENHEIGHT && viewactive && screenblocks > 10) @@ -1279,7 +1278,7 @@ void D_DoAdvanceDemo (void) Advisory = NULL; if (!M_DemoNoPlay) { - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); democount++; mysnprintf (demoname + 4, countof(demoname) - 4, "%d", democount); if (Wads.CheckNumForName (demoname) < 0) @@ -1359,6 +1358,136 @@ CCMD (endgame) } } +//========================================================================== +// +// ParseCVarInfo +// +//========================================================================== + +void ParseCVarInfo() +{ + int lump, lastlump = 0; + bool addedcvars = false; + + while ((lump = Wads.FindLump("CVARINFO", &lastlump)) != -1) + { + FScanner sc(lump); + sc.SetCMode(true); + + while (sc.GetToken()) + { + FString cvarname; + char *cvardefault = NULL; + ECVarType cvartype = CVAR_Dummy; + int cvarflags = CVAR_MOD|CVAR_ARCHIVE; + FBaseCVar *cvar; + + // Check for flag tokens. + while (sc.TokenType == TK_Identifier) + { + if (stricmp(sc.String, "server") == 0) + { + cvarflags |= CVAR_SERVERINFO; + } + else if (stricmp(sc.String, "user") == 0) + { + cvarflags |= CVAR_USERINFO; + } + else if (stricmp(sc.String, "noarchive") == 0) + { + cvarflags &= ~CVAR_ARCHIVE; + } + else + { + sc.ScriptError("Unknown cvar attribute '%s'", sc.String); + } + sc.MustGetAnyToken(); + } + // Do some sanity checks. + if ((cvarflags & (CVAR_SERVERINFO|CVAR_USERINFO)) == 0 || + (cvarflags & (CVAR_SERVERINFO|CVAR_USERINFO)) == (CVAR_SERVERINFO|CVAR_USERINFO)) + { + sc.ScriptError("One of 'server' or 'user' must be specified"); + } + // The next token must be the cvar type. + if (sc.TokenType == TK_Bool) + { + cvartype = CVAR_Bool; + } + else if (sc.TokenType == TK_Int) + { + cvartype = CVAR_Int; + } + else if (sc.TokenType == TK_Float) + { + cvartype = CVAR_Float; + } + else if (sc.TokenType == TK_Color) + { + cvartype = CVAR_Color; + } + else if (sc.TokenType == TK_String) + { + cvartype = CVAR_String; + } + else + { + sc.ScriptError("Bad cvar type '%s'", sc.String); + } + // The next token must be the cvar name. + sc.MustGetToken(TK_Identifier); + if (FindCVar(sc.String, NULL) != NULL) + { + sc.ScriptError("cvar '%s' already exists", sc.String); + } + cvarname = sc.String; + // A default value is optional and signalled by a '=' token. + if (sc.CheckToken('=')) + { + switch (cvartype) + { + case CVAR_Bool: + if (!sc.CheckToken(TK_True) && !sc.CheckToken(TK_False)) + { + sc.ScriptError("Expected true or false"); + } + cvardefault = sc.String; + break; + case CVAR_Int: + sc.MustGetNumber(); + cvardefault = sc.String; + break; + case CVAR_Float: + sc.MustGetFloat(); + cvardefault = sc.String; + break; + default: + sc.MustGetString(); + cvardefault = sc.String; + break; + } + } + // Now create the cvar. + cvar = C_CreateCVar(cvarname, cvartype, cvarflags); + if (cvardefault != NULL) + { + UCVarValue val; + val.String = cvardefault; + cvar->SetGenericRepDefault(val, CVAR_String); + } + // To be like C and ACS, require a semicolon after everything. + sc.MustGetToken(';'); + addedcvars = true; + } + } + // Only load mod cvars from the config if we defined some, so we don't + // clutter up the cvar space when not playing mods with custom cvars. + if (addedcvars) + { + GameConfig->DoModSetup (gameinfo.ConfigName); + } +} + //========================================================================== // // D_AddFile @@ -2167,7 +2296,10 @@ void D_DoomMain (void) allwads.Clear(); allwads.ShrinkToFit(); SetMapxxFlag(); - + + // Now that wads are loaded, define mod-specific cvars. + ParseCVarInfo(); + // [RH] Initialize localizable strings. GStrings.LoadStrings (false); diff --git a/src/d_net.cpp b/src/d_net.cpp index a108e8daf..e86642d91 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -108,6 +108,8 @@ int resendcount[MAXNETNODES]; unsigned int lastrecvtime[MAXPLAYERS]; // [RH] Used for pings unsigned int currrecvtime[MAXPLAYERS]; +unsigned int lastglobalrecvtime; // Identify the last time a packet was recieved. +bool hadlate; int nodeforplayer[MAXPLAYERS]; int playerfornode[MAXNETNODES]; @@ -311,6 +313,8 @@ void Net_ClearBuffers () oldentertics = entertic; gametic = 0; maketic = 0; + + lastglobalrecvtime = 0; } // @@ -596,12 +600,12 @@ void PlayerIsGone (int netnode, int netconsole) if (deathmatch) { Printf ("%s left the game with %d frags\n", - players[netconsole].userinfo.netname, - players[netconsole].fragcount); + players[netconsole].userinfo.GetName(), + players[netconsole].fragcount); } else { - Printf ("%s left the game\n", players[netconsole].userinfo.netname); + Printf ("%s left the game\n", players[netconsole].userinfo.GetName()); } // [RH] Revert each player to their own view if spying through the player who left @@ -646,7 +650,7 @@ void PlayerIsGone (int netnode, int netconsole) { Net_Arbitrator = i; players[i].settings_controller = true; - Printf ("%s is the new arbitrator\n", players[i].userinfo.netname); + Printf ("%s is the new arbitrator\n", players[i].userinfo.GetName()); break; } } @@ -700,6 +704,8 @@ void GetPackets (void) } continue; // extra setup packet } + + lastglobalrecvtime = I_GetTime (false); //Update the last time a packet was recieved netnode = doomcom.remotenode; netconsole = playerfornode[netnode] & ~PL_DRONE; @@ -1361,7 +1367,7 @@ bool DoArbitrate (void *userdata) data->playersdetected[0] |= 1 << netbuffer[1]; StartScreen->NetMessage ("Found %s (node %d, player %d)", - players[netbuffer[1]].userinfo.netname, + players[netbuffer[1]].userinfo.GetName(), node, netbuffer[1]+1); } } @@ -1820,6 +1826,33 @@ void TryRunTics (void) if (lowtic < gametic) I_Error ("TryRunTics: lowtic < gametic"); + // [Ed850] Check to see the last time a packet was recieved. + // If it's longer then 3 seconds, a node has likely stalled. Check which one and re-request its last packet. + if(I_GetTime(false) - lastglobalrecvtime >= TICRATE*3) + { + int latenode = 0; // Node 0 is the local player, and should always be the highest + lastglobalrecvtime = I_GetTime(false); //Bump the count + + if(NetMode == NET_PeerToPeer || consoleplayer == Net_Arbitrator) + { + for (i = 0; i < doomcom.numnodes; i++) + if (nodeingame[i] && nettics[i] < nettics[latenode]) + latenode = i; + } + else if (nodeingame[nodeforplayer[Net_Arbitrator]] && + nettics[nodeforplayer[Net_Arbitrator]] < nettics[0]) + { // Likely a packet server game. Only check the packet host. + latenode = Net_Arbitrator; + } + + if (debugfile) + fprintf (debugfile, "lost tics from %i (%i to %i)\n", + latenode, nettics[latenode], gametic); + + if(latenode != 0) // Send resend request to late node (if not yourself... somehow). Also mark the node as waiting to display it in the hud. + remoteresend[latenode] = players[playerfornode[latenode]].waiting = hadlate = true; + } + // don't stay in here forever -- give the menu a chance to work if (I_GetTime (false) - entertic >= TICRATE/3) { @@ -1829,6 +1862,13 @@ void TryRunTics (void) } } + if (hadlate) + { + hadlate = false; + for (i = 0; i < MAXPLAYERS; i++) + players[i].waiting = false; + } + // run the count tics if (counts > 0) { @@ -1968,12 +2008,12 @@ void Net_DoCommand (int type, BYTE **stream, int player) { case DEM_SAY: { - const char *name = players[player].userinfo.netname; + const char *name = players[player].userinfo.GetName(); BYTE who = ReadByte (stream); s = ReadString (stream); CleanseString (s); - if (((who & 1) == 0) || players[player].userinfo.team == TEAM_NONE) + if (((who & 1) == 0) || players[player].userinfo.GetTeam() == TEAM_NONE) { // Said to everyone if (who & 2) { @@ -1985,7 +2025,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) } S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.chatSound, 1, ATTN_NONE); } - else if (players[player].userinfo.team == players[consoleplayer].userinfo.team) + else if (players[player].userinfo.GetTeam() == players[consoleplayer].userinfo.GetTeam()) { // Said only to members of the player's team if (who & 2) { @@ -2245,7 +2285,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) paused = player + 1; S_PauseSound (false, false); } - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } break; @@ -2392,7 +2432,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) players[playernum].settings_controller = true; if (consoleplayer == playernum || consoleplayer == Net_Arbitrator) - Printf ("%s has been added to the controller list.\n", players[playernum].userinfo.netname); + Printf ("%s has been added to the controller list.\n", players[playernum].userinfo.GetName()); } break; @@ -2402,7 +2442,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) players[playernum].settings_controller = false; if (consoleplayer == playernum || consoleplayer == Net_Arbitrator) - Printf ("%s has been removed from the controller list.\n", players[playernum].userinfo.netname); + Printf ("%s has been removed from the controller list.\n", players[playernum].userinfo.GetName()); } break; @@ -2653,7 +2693,7 @@ CCMD (pings) for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) Printf ("% 4d %s\n", currrecvtime[i] - lastrecvtime[i], - players[i].userinfo.netname); + players[i].userinfo.GetName()); } //========================================================================== @@ -2675,13 +2715,13 @@ static void Network_Controller (int playernum, bool add) if (players[playernum].settings_controller && add) { - Printf ("%s is already on the setting controller list.\n", players[playernum].userinfo.netname); + Printf ("%s is already on the setting controller list.\n", players[playernum].userinfo.GetName()); return; } if (!players[playernum].settings_controller && !add) { - Printf ("%s is not on the setting controller list.\n", players[playernum].userinfo.netname); + Printf ("%s is not on the setting controller list.\n", players[playernum].userinfo.GetName()); return; } @@ -2780,7 +2820,7 @@ CCMD (net_listcontrollers) if (players[i].settings_controller) { - Printf ("- %s\n", players[i].userinfo.netname); + Printf ("- %s\n", players[i].userinfo.GetName()); } } } diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index f1b2a88c5..2499a03f8 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -92,22 +92,6 @@ enum const char *GenderNames[3] = { "male", "female", "other" }; -static const char *UserInfoStrings[] = -{ - "name", - "autoaim", - "color", - "skin", - "team", - "gender", - "neverswitchonpickup", - "movebob", - "stillbob", - "playerclass", - "colorset", - NULL -}; - // Replace \ with %/ and % with %% FString D_EscapeUserInfo (const char *str) { @@ -192,11 +176,12 @@ void D_GetPlayerColor (int player, float *h, float *s, float *v, FPlayerColorSet { userinfo_t *info = &players[player].userinfo; FPlayerColorSet *colorset = NULL; - int color; + uint32 color; + int team; if (players[player].mo != NULL) { - colorset = players[player].mo->GetClass()->GetColorSet(info->colorset); + colorset = players[player].mo->GetClass()->GetColorSet(info->GetColorSet()); } if (colorset != NULL) { @@ -204,25 +189,28 @@ void D_GetPlayerColor (int player, float *h, float *s, float *v, FPlayerColorSet } else { - color = info->color; + color = info->GetColor(); } RGBtoHSV (RPART(color)/255.f, GPART(color)/255.f, BPART(color)/255.f, h, s, v); - if (teamplay && TeamLibrary.IsValidTeam(info->team) && !Teams[info->team].GetAllowCustomPlayerColor ()) + if (teamplay && TeamLibrary.IsValidTeam((team = info->GetTeam())) && !Teams[team].GetAllowCustomPlayerColor()) { // In team play, force the player to use the team's hue // and adjust the saturation and value so that the team // hue is visible in the final color. float ts, tv; - int tcolor = Teams[info->team].GetPlayerColor (); + int tcolor = Teams[team].GetPlayerColor (); RGBtoHSV (RPART(tcolor)/255.f, GPART(tcolor)/255.f, BPART(tcolor)/255.f, h, &ts, &tv); *s = clamp(ts + *s * 0.15f - 0.075f, 0.f, 1.f); *v = clamp(tv + *v * 0.5f - 0.25f, 0.f, 1.f); + + // Make sure not to pass back any colorset in teamplay. + colorset = NULL; } if (set != NULL) { @@ -261,9 +249,10 @@ int D_PickRandomTeam () { if (playeringame[i]) { - if (TeamLibrary.IsValidTeam (players[i].userinfo.team)) + team = players[i].userinfo.GetTeam(); + if (TeamLibrary.IsValidTeam(team)) { - if (Teams[players[i].userinfo.team].m_iPresent++ == 0) + if (Teams[team].m_iPresent++ == 0) { numTeams++; } @@ -275,7 +264,7 @@ int D_PickRandomTeam () { do { - team = pr_pickteam() % Teams.Size (); + team = pr_pickteam() % Teams.Size(); } while (Teams[team].m_iPresent != 0); } else @@ -316,7 +305,7 @@ static void UpdateTeam (int pnum, int team, bool update) { userinfo_t *info = &players[pnum].userinfo; - if ((dmflags2 & DF2_NO_TEAM_SWITCH) && (alwaysapplydmflags || deathmatch) && TeamLibrary.IsValidTeam (info->team)) + if ((dmflags2 & DF2_NO_TEAM_SWITCH) && (alwaysapplydmflags || deathmatch) && TeamLibrary.IsValidTeam (info->GetTeam())) { Printf ("Team changing has been disabled!\n"); return; @@ -328,19 +317,15 @@ static void UpdateTeam (int pnum, int team, bool update) { team = TEAM_NONE; } - oldteam = info->team; - info->team = team; + oldteam = info->GetTeam(); + team = info->TeamChanged(team); - if (teamplay && !TeamLibrary.IsValidTeam (info->team)) - { // Force players onto teams in teamplay mode - info->team = D_PickRandomTeam (); - } - if (update && oldteam != info->team) + if (update && oldteam != team) { - if (TeamLibrary.IsValidTeam (info->team)) - Printf ("%s joined the %s team\n", info->netname, Teams[info->team].GetName ()); + if (TeamLibrary.IsValidTeam (team)) + Printf ("%s joined the %s team\n", info->GetName(), Teams[team].GetName ()); else - Printf ("%s is now a loner\n", info->netname); + Printf ("%s is now a loner\n", info->GetName()); } // Let the player take on the team's color R_BuildPlayerTranslation (pnum); @@ -348,25 +333,28 @@ static void UpdateTeam (int pnum, int team, bool update) { StatusBar->AttachToPlayer (&players[pnum]); } - if (!TeamLibrary.IsValidTeam (info->team)) - info->team = TEAM_NONE; + // Double-check + if (!TeamLibrary.IsValidTeam (team)) + { + *static_cast((*info)[NAME_Team]) = TEAM_NONE; + } } int D_GetFragCount (player_t *player) { - if (!teamplay || !TeamLibrary.IsValidTeam (player->userinfo.team)) + const int team = player->userinfo.GetTeam(); + if (!teamplay || !TeamLibrary.IsValidTeam(team)) { return player->fragcount; } else { // Count total frags for this player's team - const int team = player->userinfo.team; int count = 0; for (int i = 0; i < MAXPLAYERS; ++i) { - if (playeringame[i] && players[i].userinfo.team == team) + if (playeringame[i] && players[i].userinfo.GetTeam() == team) { count += players[i].fragcount; } @@ -378,37 +366,146 @@ int D_GetFragCount (player_t *player) void D_SetupUserInfo () { int i; - userinfo_t *coninfo = &players[consoleplayer].userinfo; + userinfo_t *coninfo; + // Reset everybody's userinfo to a default state. for (i = 0; i < MAXPLAYERS; i++) - memset (&players[i].userinfo, 0, sizeof(userinfo_t)); + { + players[i].userinfo.Reset(); + } + // Initialize the console player's user info + coninfo = &players[consoleplayer].userinfo; - strncpy (coninfo->netname, name, MAXPLAYERNAME); - if (teamplay && !TeamLibrary.IsValidTeam (team)) + for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->GetNext()) { - coninfo->team = D_PickRandomTeam (); + if ((cvar->GetFlags() & (CVAR_USERINFO|CVAR_IGNORE)) == CVAR_USERINFO) + { + FBaseCVar **newcvar; + FName cvarname(cvar->GetName()); + + switch (cvarname.GetIndex()) + { + // Some cvars don't copy their original value directly. + case NAME_Team: coninfo->TeamChanged(team); break; + case NAME_Skin: coninfo->SkinChanged(skin); break; + case NAME_Gender: coninfo->GenderChanged(gender); break; + case NAME_PlayerClass: coninfo->PlayerClassChanged(playerclass); break; + // The rest do. + default: + newcvar = coninfo->CheckKey(cvarname); + (*newcvar)->SetGenericRep(cvar->GetGenericRep(CVAR_String), CVAR_String); + break; + } + } } - else + R_BuildPlayerTranslation(consoleplayer); +} + +void userinfo_t::Reset() +{ + // Clear this player's userinfo. + TMapIterator it(*this); + TMap::Pair *pair; + + while (it.NextPair(pair)) { - coninfo->team = team; + delete pair->Value; } - if (autoaim > 35.f || autoaim < 0.f) + Clear(); + + // Create userinfo vars for this player, initialized to their defaults. + for (FBaseCVar *cvar = CVars; cvar != NULL; cvar = cvar->GetNext()) { - coninfo->aimdist = ANGLE_1*35; + if ((cvar->GetFlags() & (CVAR_USERINFO|CVAR_IGNORE)) == CVAR_USERINFO) + { + ECVarType type; + FName cvarname(cvar->GetName()); + FBaseCVar *newcvar; + + // Some cvars have different types for their shadow copies. + switch (cvarname.GetIndex()) + { + case NAME_Skin: type = CVAR_Int; break; + case NAME_Gender: type = CVAR_Int; break; + case NAME_PlayerClass: type = CVAR_Int; break; + default: type = cvar->GetRealType(); break; + } + newcvar = C_CreateCVar(NULL, type, cvar->GetFlags() & CVAR_MOD); + newcvar->SetGenericRepDefault(cvar->GetGenericRepDefault(CVAR_String), CVAR_String); + Insert(cvarname, newcvar); + } } - else - { - coninfo->aimdist = abs ((int)(autoaim * (float)ANGLE_1)); +} + +int userinfo_t::TeamChanged(int team) +{ + if (teamplay && !TeamLibrary.IsValidTeam(team)) + { // Force players onto teams in teamplay mode + team = D_PickRandomTeam(); } - coninfo->color = color; - coninfo->colorset = colorset; - coninfo->skin = R_FindSkin (skin, 0); - coninfo->gender = D_GenderToInt (gender); - coninfo->neverswitch = neverswitchonpickup; - coninfo->MoveBob = (fixed_t)(65536.f * movebob); - coninfo->StillBob = (fixed_t)(65536.f * stillbob); - coninfo->PlayerClass = D_PlayerClassToInt (playerclass); - R_BuildPlayerTranslation (consoleplayer); + *static_cast((*this)[NAME_Team]) = team; + return team; +} + +int userinfo_t::SkinChanged(const char *skinname) +{ + int skinnum = R_FindSkin(skinname, 0); + *static_cast((*this)[NAME_Skin]) = skinnum; + return skinnum; +} + +int userinfo_t::SkinNumChanged(int skinnum) +{ + *static_cast((*this)[NAME_Skin]) = skinnum; + return skinnum; +} + +int userinfo_t::GenderChanged(const char *gendername) +{ + int gendernum = D_GenderToInt(gendername); + *static_cast((*this)[NAME_Gender]) = gendernum; + return gendernum; +} + +int userinfo_t::PlayerClassChanged(const char *classname) +{ + int classnum = D_PlayerClassToInt(classname); + *static_cast((*this)[NAME_PlayerClass]) = classnum; + return classnum; +} + +int userinfo_t::PlayerClassNumChanged(int classnum) +{ + *static_cast((*this)[NAME_PlayerClass]) = classnum; + return classnum; +} + +int userinfo_t::ColorSetChanged(int setnum) +{ + *static_cast((*this)[NAME_ColorSet]) = setnum; + return setnum; +} + +uint32 userinfo_t::ColorChanged(const char *colorname) +{ + FColorCVar *color = static_cast((*this)[NAME_Color]); + assert(color != NULL); + UCVarValue val; + val.String = const_cast(colorname); + color->SetGenericRep(val, CVAR_String); + *static_cast((*this)[NAME_ColorSet]) = -1; + return *color; +} + +uint32 userinfo_t::ColorChanged(uint32 colorval) +{ + FColorCVar *color = static_cast((*this)[NAME_Color]); + assert(color != NULL); + UCVarValue val; + val.Int = colorval; + color->SetGenericRep(val, CVAR_Int); + // This version is called by the menu code. Do not implicitly set colorset. + return colorval; } void D_UserInfoChanged (FBaseCVar *cvar) @@ -500,7 +597,7 @@ static const char *SetServerVar (char *name, ECVarType type, BYTE **stream, bool { if (playeringame[i]) { - UpdateTeam (i, players[i].userinfo.team, true); + UpdateTeam (i, players[i].userinfo.GetTeam(), true); } } } @@ -569,111 +666,131 @@ void D_DoServerInfoChange (BYTE **stream, bool singlebit) } } -void D_WriteUserInfoStrings (int i, BYTE **stream, bool compact) +static int STACK_ARGS userinfosortfunc(const void *a, const void *b) { - if (i >= MAXPLAYERS) - { - WriteByte (0, stream); - } - else - { - userinfo_t *info = &players[i].userinfo; - - PClassPlayerPawn *type = PlayerClasses[info->PlayerClass].Type; - - if (!compact) - { - sprintf (*((char **)stream), - "\\name\\%s" - "\\autoaim\\%g" - "\\color\\%x %x %x" - "\\colorset\\%d" - "\\skin\\%s" - "\\team\\%d" - "\\gender\\%s" - "\\neverswitchonpickup\\%d" - "\\movebob\\%g" - "\\stillbob\\%g" - "\\playerclass\\%s" - , - D_EscapeUserInfo(info->netname).GetChars(), - (double)info->aimdist / (float)ANGLE_1, - RPART(info->color), GPART(info->color), BPART(info->color), - info->colorset, - D_EscapeUserInfo(skins[info->skin].name).GetChars(), - info->team, - info->gender == GENDER_FEMALE ? "female" : - info->gender == GENDER_NEUTER ? "other" : "male", - info->neverswitch, - (float)(info->MoveBob) / 65536.f, - (float)(info->StillBob) / 65536.f, - info->PlayerClass == -1 ? "Random" : D_EscapeUserInfo(type->DisplayName).GetChars() - ); - } - else - { - sprintf (*((char **)stream), - "\\" - "\\%s" // name - "\\%g" // autoaim - "\\%x %x %x" // color - "\\%s" // skin - "\\%d" // team - "\\%s" // gender - "\\%d" // neverswitchonpickup - "\\%g" // movebob - "\\%g" // stillbob - "\\%s" // playerclass - "\\%d" // colorset - , - D_EscapeUserInfo(info->netname).GetChars(), - (double)info->aimdist / (float)ANGLE_1, - RPART(info->color), GPART(info->color), BPART(info->color), - D_EscapeUserInfo(skins[info->skin].name).GetChars(), - info->team, - info->gender == GENDER_FEMALE ? "female" : - info->gender == GENDER_NEUTER ? "other" : "male", - info->neverswitch, - (float)(info->MoveBob) / 65536.f, - (float)(info->StillBob) / 65536.f, - info->PlayerClass == -1 ? "Random" : D_EscapeUserInfo(type->DisplayName).GetChars(), - info->colorset - ); - } - } - - *stream += strlen (*((char **)stream)) + 1; + TMap::ConstPair *pair1 = *(TMap::ConstPair **)a; + TMap::ConstPair *pair2 = *(TMap::ConstPair **)b; + return stricmp(pair1->Key.GetChars(), pair2->Key.GetChars()); } -void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) +static int STACK_ARGS namesortfunc(const void *a, const void *b) { - userinfo_t *info = &players[i].userinfo; + FName *name1 = (FName *)a; + FName *name2 = (FName *)b; + return stricmp(name1->GetChars(), name2->GetChars()); +} + +void D_WriteUserInfoStrings (int pnum, BYTE **stream, bool compact) +{ + if (pnum >= MAXPLAYERS) + { + WriteByte (0, stream); + return; + } + + userinfo_t *info = &players[pnum].userinfo; + TArray::Pair *> userinfo_pairs(info->CountUsed()); + TMap::Iterator it(*info); + TMap::Pair *pair; + UCVarValue cval; + + // Create a simple array of all userinfo cvars + while (it.NextPair(pair)) + { + userinfo_pairs.Push(pair); + } + // For compact mode, these need to be sorted. Verbose mode doesn't matter. + if (compact) + { + qsort(&userinfo_pairs[0], userinfo_pairs.Size(), sizeof(pair), userinfosortfunc); + // Compact mode is signified by starting the string with two backslash characters. + // We output one now. The second will be output as part of the first value. + *(*stream)++ = '\\'; + } + for (unsigned int i = 0; i < userinfo_pairs.Size(); ++i) + { + pair = userinfo_pairs[i]; + + if (!compact) + { // In verbose mode, prepend the cvar's name + *stream += sprintf(*((char **)stream), "\\%s\\", pair->Key.GetChars()); + } + // A few of these need special handling for compatibility reasons. + switch (pair->Key.GetIndex()) + { + case NAME_Gender: + *stream += sprintf(*((char **)stream), "\\%s", + *static_cast(pair->Value) == GENDER_FEMALE ? "female" : + *static_cast(pair->Value) == GENDER_NEUTER ? "other" : "male"); + break; + + case NAME_PlayerClass: + *stream += sprintf(*((char **)stream), "\\%s", info->GetPlayerClassNum() == -1 ? "Random" : + D_EscapeUserInfo(info->GetPlayerClassType()->DisplayName.GetChars())); + break; + + case NAME_Skin: + *stream += sprintf(*((char **)stream), "\\%s", D_EscapeUserInfo(skins[info->GetSkin()].name).GetChars()); + break; + + default: + cval = pair->Value->GetGenericRep(CVAR_String); + *stream += sprintf(*((char **)stream), "\\%s", cval.String); + break; + } + } + *(*stream)++ = '\0'; +} + +void D_ReadUserInfoStrings (int pnum, BYTE **stream, bool update) +{ + userinfo_t *info = &players[pnum].userinfo; + TArray compact_names(info->CountUsed()); + FBaseCVar **cvar_ptr; const char *ptr = *((const char **)stream); const char *breakpt; FString value; bool compact; - int infotype = -1; + FName keyname; + unsigned int infotype = 0; if (*ptr++ != '\\') return; compact = (*ptr == '\\') ? ptr++, true : false; - if (i < MAXPLAYERS) + // We need the cvar names in sorted order for compact mode + if (compact) { - for (;;) - { - int j; + TMap::Iterator it(*info); + TMap::Pair *pair; - breakpt = strchr (ptr, '\\'); + while (it.NextPair(pair)) + { + compact_names.Push(pair->Key); + } + qsort(&compact_names[0], compact_names.Size(), sizeof(FName), namesortfunc); + } + + if (pnum < MAXPLAYERS) + { + for (breakpt = ptr; breakpt != NULL; ptr = breakpt + 1) + { + breakpt = strchr(ptr, '\\'); if (compact) { + // Compact has just the value. + if (infotype >= compact_names.Size()) + { // Too many entries! OMG! + break; + } + keyname = compact_names[infotype++]; value = D_UnescapeUserInfo(ptr, breakpt != NULL ? breakpt - ptr : strlen(ptr)); - infotype++; } else { + // Verbose has both the key name and its value. assert(breakpt != NULL); // A malicious remote machine could invalidate the above assert. if (breakpt == NULL) @@ -689,156 +806,187 @@ void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) { value = D_UnescapeUserInfo(valstart, strlen(valstart)); } - - for (j = 0; - UserInfoStrings[j] && strnicmp (UserInfoStrings[j], ptr, valstart - ptr - 1) != 0; - ++j) - { } - if (UserInfoStrings[j] == NULL) - { - infotype = -1; - } - else - { - infotype = j; - } + keyname = FName(ptr, valstart - ptr - 1, true); } - switch (infotype) + + // A few of these need special handling. + switch (keyname) { - case INFO_Autoaim: { - double angles; - - angles = atof (value); - if (angles > 35.f || angles < 0.f) - { - info->aimdist = ANGLE_1*35; - } - else - { - info->aimdist = abs ((int)(angles * (float)ANGLE_1)); - } - } + case NAME_Gender: + info->GenderChanged(value); break; - case INFO_Name: - { - char oldname[MAXPLAYERNAME+1]; - - strcpy (oldname, info->netname); - strncpy (info->netname, value, MAXPLAYERNAME); - info->netname[MAXPLAYERNAME] = 0; - CleanseString(info->netname); - - if (update && strcmp (oldname, info->netname) != 0) - { - Printf ("%s is now known as %s\n", oldname, info->netname); - } - } + case NAME_PlayerClass: + info->PlayerClassChanged(value); break; - case INFO_Team: - UpdateTeam (i, atoi(value), update); - break; - - case INFO_Color: - case INFO_ColorSet: - if (infotype == INFO_Color) + case NAME_Skin: + info->SkinChanged(value); + if (players[pnum].mo != NULL) { - info->color = V_GetColorFromString (NULL, value); - info->colorset = -1; - } - else - { - info->colorset = atoi(value); - } - R_BuildPlayerTranslation (i); - if (StatusBar != NULL && i == StatusBar->GetPlayer()) - { - StatusBar->AttachToPlayer (&players[i]); - } - break; - - case INFO_Skin: - info->skin = R_FindSkin (value, players[i].CurrentPlayerClass); - if (players[i].mo != NULL) - { - if (players[i].cls != NULL && - !(players[i].mo->flags4 & MF4_NOSKIN) && - players[i].mo->state->sprite == - GetDefaultByType (players[i].cls)->SpawnState->sprite) + if (players[pnum].cls != NULL && + !(players[pnum].mo->flags4 & MF4_NOSKIN) && + players[pnum].mo->state->sprite == + GetDefaultByType (players[pnum].cls)->SpawnState->sprite) { // Only change the sprite if the player is using a standard one - players[i].mo->sprite = skins[info->skin].sprite; - players[i].mo->scaleX = skins[info->skin].ScaleX; - players[i].mo->scaleY = skins[info->skin].ScaleY; + players[pnum].mo->sprite = skins[info->GetSkin()].sprite; } } // Rebuild translation in case the new skin uses a different range // than the old one. - R_BuildPlayerTranslation (i); + R_BuildPlayerTranslation(pnum); break; - case INFO_Gender: - info->gender = D_GenderToInt (value); + case NAME_Team: + UpdateTeam(pnum, atoi(value), update); break; - case INFO_NeverSwitchOnPickup: - if (value[0] >= '0' && value[0] <= '9') - { - info->neverswitch = atoi (value) ? true : false; - } - else if (stricmp (value, "true") == 0) - { - info->neverswitch = 1; - } - else - { - info->neverswitch = 0; - } - break; - - case INFO_MoveBob: - info->MoveBob = (fixed_t)(atof (value) * 65536.f); - break; - - case INFO_StillBob: - info->StillBob = (fixed_t)(atof (value) * 65536.f); - break; - - case INFO_PlayerClass: - info->PlayerClass = D_PlayerClassToInt (value); + case NAME_Color: + info->ColorChanged(value); break; default: + cvar_ptr = info->CheckKey(keyname); + if (cvar_ptr != NULL) + { + assert(*cvar_ptr != NULL); + UCVarValue val; + FString oldname; + + if (keyname == NAME_Name) + { + val = (*cvar_ptr)->GetGenericRep(CVAR_String); + oldname = val.String; + } + val.String = CleanseString(value.LockBuffer()); + (*cvar_ptr)->SetGenericRep(val, CVAR_String); + value.UnlockBuffer(); + if (keyname == NAME_Name && update && oldname != value) + { + Printf("%s is now known as %s\n", oldname.GetChars(), value.GetChars()); + } + } break; } - - if (breakpt) + if (keyname == NAME_Color || keyname == NAME_ColorSet) { - ptr = breakpt + 1; + R_BuildPlayerTranslation(pnum); + if (StatusBar != NULL && pnum == StatusBar->GetPlayer()) + { + StatusBar->AttachToPlayer(&players[pnum]); + } } - else + } + } + *stream += strlen (*((char **)stream)) + 1; +} + +void ReadCompatibleUserInfo(FArchive &arc, userinfo_t &info) +{ + char netname[MAXPLAYERNAME + 1]; + BYTE team; + int aimdist, color, colorset, skin, gender; + bool neverswitch; + //fixed_t movebob, stillbob; These were never serialized! + //int playerclass; " + + info.Reset(); + + arc.Read(&netname, sizeof(netname)); + arc << team << aimdist << color << skin << gender << neverswitch << colorset; + + *static_cast(info[NAME_Name]) = netname; + *static_cast(info[NAME_Team]) = team; + *static_cast(info[NAME_Autoaim]) = (float)aimdist / ANGLE_1; + *static_cast(info[NAME_Skin]) = skin; + *static_cast(info[NAME_Gender]) = gender; + *static_cast(info[NAME_NeverSwitchOnPickup]) = neverswitch; + *static_cast(info[NAME_ColorSet]) = colorset; + + UCVarValue val; + val.Int = color; + static_cast(info[NAME_Color])->SetGenericRep(val, CVAR_Int); +} + +void WriteUserInfo(FArchive &arc, userinfo_t &info) +{ + TMapIterator it(info); + TMap::Pair *pair; + FName name; + UCVarValue val; + int i; + + while (it.NextPair(pair)) + { + name = pair->Key; + arc << name; + switch (name.GetIndex()) + { + case NAME_Skin: + arc.WriteString(skins[info.GetSkin()].name); + break; + + case NAME_PlayerClass: + i = info.GetPlayerClassNum(); + arc.WriteString(i == -1 ? "Random" : PlayerClasses[i].Type->DisplayName); + break; + + default: + val = pair->Value->GetGenericRep(CVAR_String); + arc.WriteString(val.String); + break; + } + } + name = NAME_None; + arc << name; +} + +void ReadUserInfo(FArchive &arc, userinfo_t &info) +{ + FName name; + FBaseCVar **cvar; + char *str = NULL; + UCVarValue val; + + info.Reset(); + for (arc << name; name != NAME_None; arc << name) + { + cvar = info.CheckKey(name); + arc << str; + if (cvar != NULL && *cvar != NULL) + { + switch (name) { + case NAME_Team: info.TeamChanged(atoi(str)); break; + case NAME_Skin: info.SkinChanged(str); break; + case NAME_PlayerClass: info.PlayerClassChanged(str); break; + default: + val.String = str; + (*cvar)->SetGenericRep(val, CVAR_String); break; } } } - - *stream += strlen (*((char **)stream)) + 1; + if (str != NULL) + { + delete[] str; + } } FArchive &operator<< (FArchive &arc, userinfo_t &info) { - if (arc.IsStoring ()) + if (SaveVersion < 4253) { - arc.Write (&info.netname, sizeof(info.netname)); + ReadCompatibleUserInfo(arc, info); + } + else if (arc.IsStoring()) + { + WriteUserInfo(arc, info); } else { - arc.Read (&info.netname, sizeof(info.netname)); + ReadUserInfo(arc, info); } - arc << info.team << info.aimdist << info.color - << info.skin << info.gender << info.neverswitch - << info.colorset; return arc; } @@ -852,27 +1000,51 @@ CCMD (playerinfo) { if (playeringame[i]) { - Printf ("%d. %s\n", i, players[i].userinfo.netname); + Printf("%d. %s\n", i, players[i].userinfo.GetName()); } } } else { - int i = atoi (argv[1]); + int i = atoi(argv[1]); + + if (i < 0 || i >= MAXPLAYERS) + { + Printf("Bad player number\n"); + return; + } userinfo_t *ui = &players[i].userinfo; - Printf ("Name: %s\n", ui->netname); - Printf ("Team: %s (%d)\n", ui->team == TEAM_NONE ? "None" : Teams[ui->team].GetName (), ui->team); - Printf ("Aimdist: %d\n", ui->aimdist); - Printf ("Color: %06x\n", ui->color); - Printf ("ColorSet: %d\n", ui->colorset); - Printf ("Skin: %s (%d)\n", skins[ui->skin].name, ui->skin); - Printf ("Gender: %s (%d)\n", GenderNames[ui->gender], ui->gender); - Printf ("NeverSwitch: %d\n", ui->neverswitch); - Printf ("MoveBob: %g\n", ui->MoveBob/65536.f); - Printf ("StillBob: %g\n", ui->StillBob/65536.f); - Printf ("PlayerClass: %s (%d)\n", - ui->PlayerClass == -1 ? "Random" : PlayerClasses[ui->PlayerClass].Type->DisplayName.GetChars(), - ui->PlayerClass); - if (argv.argc() > 2) PrintMiscActorInfo(players[i].mo); + + if (!playeringame[i]) + { + Printf(TEXTCOLOR_ORANGE "Player %d is not in the game\n", i); + } + + // Print special info + Printf("%20s: %s\n", "Name", ui->GetName()); + Printf("%20s: %s (%d)\n", "Team", ui->GetTeam() == TEAM_NONE ? "None" : Teams[ui->GetTeam()].GetName(), ui->GetTeam()); + Printf("%20s: %s (%d)\n", "Skin", skins[ui->GetSkin()].name, ui->GetSkin()); + Printf("%20s: %s (%d)\n", "Gender", GenderNames[ui->GetGender()], ui->GetGender()); + Printf("%20s: %s (%d)\n", "PlayerClass", + ui->GetPlayerClassNum() == -1 ? "Random" : ui->GetPlayerClassType()->DisplayName.GetChars(), + ui->GetPlayerClassNum()); + + // Print generic info + TMapIterator it(*ui); + TMap::Pair *pair; + + while (it.NextPair(pair)) + { + if (pair->Key != NAME_Name && pair->Key != NAME_Team && pair->Key != NAME_Skin && + pair->Key != NAME_Gender && pair->Key != NAME_PlayerClass) + { + UCVarValue val = pair->Value->GetGenericRep(CVAR_String); + Printf("%20s: %s\n", pair->Key.GetChars(), val.String); + } + } + if (argv.argc() > 2) + { + PrintMiscActorInfo(players[i].mo); + } } } diff --git a/src/d_player.h b/src/d_player.h index 244aa192a..260cf37dc 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -183,6 +183,7 @@ enum { PPF_NOTHRUSTWHENINVUL = 1, // Attacks do not thrust the player if they are invulnerable. PPF_CANSUPERMORPH = 2, // Being remorphed into this class can give you a Tome of Power + PPF_CROUCHABLEMORPH = 4, // This morphed player can crouch }; // @@ -215,7 +216,6 @@ typedef enum CF_INSTANTWEAPSWITCH= 1 << 11, // [RH] Switch weapons instantly CF_TOTALLYFROZEN = 1 << 12, // [RH] All players can do is press +use CF_PREDICTING = 1 << 13, // [RH] Player movement is being predicted - CF_WEAPONREADY = 1 << 14, // [RH] Weapon is in the ready state and can fire its primary attack CF_DRAIN = 1 << 16, // Player owns a drain powerup CF_HIGHJUMP = 1 << 18, // more Skulltag flags. Implementation not guaranteed though. ;) CF_REFLECTION = 1 << 19, @@ -223,15 +223,22 @@ typedef enum CF_DOUBLEFIRINGSPEED= 1 << 21, // Player owns a double firing speed artifact CF_EXTREMELYDEAD = 1 << 22, // [RH] Reliably let the status bar know about extreme deaths. CF_INFINITEAMMO = 1 << 23, // Player owns an infinite ammo artifact - CF_WEAPONBOBBING = 1 << 24, // [HW] Bob weapon while the player is moving - CF_WEAPONREADYALT = 1 << 25, // Weapon can fire its secondary attack - CF_WEAPONSWITCHOK = 1 << 26, // It is okay to switch away from this weapon CF_BUDDHA = 1 << 27, // [SP] Buddha mode - take damage, but don't die - CF_WEAPONRELOADOK = 1 << 28, // [XA] Okay to reload this weapon. - CF_WEAPONZOOMOK = 1 << 29, // [XA] Okay to use weapon zoom function. CF_NOCLIP2 = 1 << 30, // [RH] More Quake-like noclip } cheat_t; +enum +{ + WF_WEAPONREADY = 1 << 0, // [RH] Weapon is in the ready state and can fire its primary attack + WF_WEAPONBOBBING = 1 << 1, // [HW] Bob weapon while the player is moving + WF_WEAPONREADYALT = 1 << 2, // Weapon can fire its secondary attack + WF_WEAPONSWITCHOK = 1 << 3, // It is okay to switch away from this weapon + WF_DISABLESWITCH = 1 << 4, // Disable weapon switching completely + WF_WEAPONRELOADOK = 1 << 5, // [XA] Okay to reload this weapon. + WF_WEAPONZOOMOK = 1 << 6, // [XA] Okay to use weapon zoom function. + WF_REFIRESWITCHOK = 1 << 7, // Mirror WF_WEAPONSWITCHOK for A_ReFire +}; + #define WPIECE1 1 #define WPIECE2 2 #define WPIECE3 4 @@ -241,6 +248,29 @@ typedef enum #define MAXPLAYERNAME 15 +// [GRB] Custom player classes +enum +{ + PCF_NOMENU = 1, // Hide in new game menu +}; + +class FPlayerClass +{ +public: + FPlayerClass (); + FPlayerClass (const FPlayerClass &other); + ~FPlayerClass (); + + bool CheckSkin (int skin); + + PClassPlayerPawn *Type; + DWORD Flags; + TArray Skins; +}; + +extern TArray PlayerClasses; + +// User info (per-player copies of each CVAR_USERINFO cvar) enum { GENDER_MALE, @@ -248,20 +278,80 @@ enum GENDER_NEUTER }; -struct userinfo_t +struct userinfo_t : TMap { - char netname[MAXPLAYERNAME+1]; - BYTE team; - int aimdist; - int color; - int colorset; - int skin; - int gender; - bool neverswitch; - fixed_t MoveBob, StillBob; - int PlayerClass; + int GetAimDist() const + { + if (dmflags2 & DF2_NOAUTOAIM) + { + return 0; + } - int GetAimDist() const { return (dmflags2 & DF2_NOAUTOAIM)? 0 : aimdist; } + float aim = *static_cast(*CheckKey(NAME_Autoaim)); + if (aim > 35 || aim < 0) + { + return ANGLE_1*35; + } + else + { + return xs_RoundToInt(fabs(aim * ANGLE_1)); + } + } + const char *GetName() const + { + return *static_cast(*CheckKey(NAME_Name)); + } + int GetTeam() const + { + return *static_cast(*CheckKey(NAME_Team)); + } + int GetColorSet() const + { + return *static_cast(*CheckKey(NAME_ColorSet)); + } + uint32 GetColor() const + { + return *static_cast(*CheckKey(NAME_Color)); + } + bool GetNeverSwitch() const + { + return *static_cast(*CheckKey(NAME_NeverSwitchOnPickup)); + } + fixed_t GetMoveBob() const + { + return FLOAT2FIXED(*static_cast(*CheckKey(NAME_MoveBob))); + } + fixed_t GetStillBob() const + { + return FLOAT2FIXED(*static_cast(*CheckKey(NAME_StillBob))); + } + int GetPlayerClassNum() const + { + return *static_cast(*CheckKey(NAME_PlayerClass)); + } + PClassPlayerPawn *GetPlayerClassType() const + { + return PlayerClasses[GetPlayerClassNum()].Type; + } + int GetSkin() const + { + return *static_cast(*CheckKey(NAME_Skin)); + } + int GetGender() const + { + return *static_cast(*CheckKey(NAME_Gender)); + } + + void Reset(); + int TeamChanged(int team); + int SkinChanged(const char *skinname); + int SkinNumChanged(int skinnum); + int GenderChanged(const char *gendername); + int PlayerClassChanged(const char *classname); + int PlayerClassNumChanged(int classnum); + uint32 ColorChanged(const char *colorname); + uint32 ColorChanged(uint32 colorval); + int ColorSetChanged(int setnum); }; FArchive &operator<< (FArchive &arc, userinfo_t &info); @@ -325,6 +415,7 @@ public: int lastkilltime; // [RH] For multikills BYTE multicount; BYTE spreecount; // [RH] Keep track of killing sprees + BYTE WeaponState; AWeapon *ReadyWeapon; AWeapon *PendingWeapon; // WP_NOCHANGE if not changing @@ -333,6 +424,7 @@ public: int timefreezer; // Player has an active time freezer short refire; // refired shots are less accurate short inconsistant; + bool waiting; int killcount, itemcount, secretcount; // for intermission int damagecount, bonuscount;// for screen flashing int hazardcount; // for delayed Strife damage @@ -438,6 +530,11 @@ public: crouching = 0; crouchviewdelta = 0; } + + bool CanCrouch() const + { + return morphTics == 0 || mo->PlayerFlags & PPF_CROUCHABLEMORPH; + } int GetSpawnClass(); }; @@ -447,7 +544,7 @@ extern player_t players[MAXPLAYERS]; FArchive &operator<< (FArchive &arc, player_t *&p); -void P_CheckPlayerSprites(); +void P_CheckPlayerSprite(AActor *mo, int &spritenum, fixed_t &scalex, fixed_t &scaley); inline void AActor::SetFriendPlayer(player_t *player) { @@ -474,26 +571,4 @@ inline bool AActor::IsNoClip2() const bool P_IsPlayerTotallyFrozen(const player_t *player); -// [GRB] Custom player classes -enum -{ - PCF_NOMENU = 1, // Hide in new game menu -}; - -class FPlayerClass -{ -public: - FPlayerClass (); - FPlayerClass (const FPlayerClass &other); - ~FPlayerClass (); - - bool CheckSkin (int skin); - - PClassPlayerPawn *Type; - DWORD Flags; - TArray Skins; -}; - -extern TArray PlayerClasses; - #endif // __D_PLAYER_H__ diff --git a/src/decallib.cpp b/src/decallib.cpp index 40d6cc627..ca9e06680 100644 --- a/src/decallib.cpp +++ b/src/decallib.cpp @@ -631,7 +631,10 @@ void FDecalLib::ParseGenerator (FScanner &sc) } actor->DecalGenerator = decal; - decal->Users.Push (type); + if (decal != NULL) + { + decal->Users.Push (type); + } } void FDecalLib::ParseFader (FScanner &sc) diff --git a/src/dobjgc.cpp b/src/dobjgc.cpp index 05cc73aed..da3d28ac1 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -325,6 +325,7 @@ static void MarkRoot() DThinker::MarkRoots(); FCanvasTextureInfo::Mark(); Mark(DACSThinker::ActiveThinker); + Mark(level.DefaultSkybox); // Mark dead bodies. for (i = 0; i < BODYQUESIZE; ++i) { diff --git a/src/doomdef.h b/src/doomdef.h index 114f8c701..3a3c31e6a 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -333,7 +333,7 @@ enum COMPATF_HITSCAN = 1 << 28, // Hitscans use original blockmap anf hit check code. COMPATF_LIGHT = 1 << 29, // Find neighboring light level like Doom COMPATF_POLYOBJ = 1 << 30, // Draw polyobjects the old fashioned way - COMPATF_MASKEDMIDTEX = 1 << 31, // Ignore compositing when drawing masked midtextures + COMPATF_MASKEDMIDTEX = 1u << 31, // Ignore compositing when drawing masked midtextures COMPATF2_BADANGLES = 1 << 0, // It is impossible to face directly NSEW. COMPATF2_FLOORMOVE = 1 << 1, // Use the same floor motion behavior as Doom. @@ -348,6 +348,7 @@ enum BCOMPATF_VILEGHOSTS = 1 << 2, // Monsters' radius and height aren't restored properly when resurrected. BCOMPATF_BADTELEPORTERS = 1 << 3, // Ignore tags on Teleport specials BCOMPATF_BADPORTALS = 1 << 4, // Restores the old unstable portal behavior + BCOMPATF_REBUILDNODES = 1 << 5, // Force node rebuild }; // phares 3/20/98: diff --git a/src/dthinker.cpp b/src/dthinker.cpp index fa2e17a64..704d7a2d9 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -450,7 +450,7 @@ int DThinker::TickThinkers (FThinkerList *list, FThinkerList *dest) NextToThink = node->NextThinker; if (node->ObjectFlags & OF_JustSpawned) { - node->ObjectFlags &= ~OF_JustSpawned; + // Leave OF_JustSpawn set until after Tick() so the ticker can check it. if (dest != NULL) { // Move thinker from this list to the destination list node->Remove(); @@ -465,7 +465,8 @@ int DThinker::TickThinkers (FThinkerList *list, FThinkerList *dest) if (!(node->ObjectFlags & OF_EuthanizeMe)) { // Only tick thinkers not scheduled for destruction - node->Tick (); + node->Tick(); + node->ObjectFlags &= ~OF_JustSpawned; GC::CheckGC(); } node = NextToThink; diff --git a/src/fragglescript/t_func.cpp b/src/fragglescript/t_func.cpp index 2e23ed066..93e50dd8b 100644 --- a/src/fragglescript/t_func.cpp +++ b/src/fragglescript/t_func.cpp @@ -774,7 +774,7 @@ void FParser::SF_PlayerName(void) if(plnum !=-1) { t_return.type = svt_string; - t_return.string = players[plnum].userinfo.netname; + t_return.string = players[plnum].userinfo.GetName(); } else { @@ -4256,7 +4256,7 @@ void FParser::SF_SpawnShot2(void) S_Sound (mo, CHAN_VOICE, mo->SeeSound, 1, ATTN_NORM); mo->target = source; P_ThrustMobj(mo, mo->angle = source->angle, mo->Speed); - if (!P_CheckMissileSpawn(mo)) mo = NULL; + if (!P_CheckMissileSpawn(mo, source->radius)) mo = NULL; } t_return.value.mobj = mo; } diff --git a/src/g_doom/a_archvile.cpp b/src/g_doom/a_archvile.cpp index 86ccf51f5..1e69bb555 100644 --- a/src/g_doom/a_archvile.cpp +++ b/src/g_doom/a_archvile.cpp @@ -132,8 +132,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_VileAttack) return 0; S_Sound (self, CHAN_WEAPON, snd, 1, ATTN_NORM); - P_TraceBleed (dmg, target); - P_DamageMobj (target, self, self, dmg, NAME_None); + int newdam = P_DamageMobj (target, self, self, dmg, NAME_None); + P_TraceBleed (newdam > 0 ? newdam : dmg, target); an = self->angle >> ANGLETOFINESHIFT; fire = self->tracer; diff --git a/src/g_doom/a_bossbrain.cpp b/src/g_doom/a_bossbrain.cpp index 51d7f0e62..b7dd6193e 100644 --- a/src/g_doom/a_bossbrain.cpp +++ b/src/g_doom/a_bossbrain.cpp @@ -88,16 +88,22 @@ DEFINE_ACTION_FUNCTION(AActor, A_BrainDie) // New dmflag: Kill all boss spawned monsters before ending the level. if (dmflags2 & DF2_KILLBOSSMONST) { - TThinkerIterator it; - AActor *mo; - while ((mo = it.Next())) + int count; // Repeat until we have no more boss-spawned monsters. + do // (e.g. Pain Elementals can spawn more to kill upon death.) { - if (mo->flags4 & MF4_BOSSSPAWNED) + TThinkerIterator it; + AActor *mo; + count = 0; + while ((mo = it.Next())) { - P_DamageMobj(mo, self, self, mo->health, NAME_None, - DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR); + if (mo->health > 0 && mo->flags4 & MF4_BOSSSPAWNED) + { + P_DamageMobj(mo, self, self, mo->health, NAME_None, + DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR); + count++; + } } - } + } while (count != 0); } G_ExitLevel (0, false); diff --git a/src/g_doom/a_bruiser.cpp b/src/g_doom/a_bruiser.cpp index 97dc64775..abcec8e6f 100644 --- a/src/g_doom/a_bruiser.cpp +++ b/src/g_doom/a_bruiser.cpp @@ -12,8 +12,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_BruisAttack) { int damage = (pr_bruisattack()%8+1)*10; S_Sound (self, CHAN_WEAPON, "baron/melee", 1, ATTN_NORM); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } diff --git a/src/g_doom/a_cacodemon.cpp b/src/g_doom/a_cacodemon.cpp index d405ea425..d8f03509e 100644 --- a/src/g_doom/a_cacodemon.cpp +++ b/src/g_doom/a_cacodemon.cpp @@ -24,8 +24,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_HeadAttack) { int damage = (pr_headattack()%6+1)*10; S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } diff --git a/src/g_doom/a_demon.cpp b/src/g_doom/a_demon.cpp index c8df07cca..07b297005 100644 --- a/src/g_doom/a_demon.cpp +++ b/src/g_doom/a_demon.cpp @@ -22,8 +22,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_SargAttack) if (self->CheckMeleeRange ()) { int damage = ((pr_sargattack()%10)+1)*4; - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } return 0; } diff --git a/src/g_doom/a_doomimp.cpp b/src/g_doom/a_doomimp.cpp index 681d1c79d..35179c5b2 100644 --- a/src/g_doom/a_doomimp.cpp +++ b/src/g_doom/a_doomimp.cpp @@ -27,8 +27,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_TroopAttack) { int damage = (pr_troopattack()%8+1)*3; S_Sound (self, CHAN_WEAPON, "imp/melee", 1, ATTN_NORM); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } diff --git a/src/g_doom/a_doomweaps.cpp b/src/g_doom/a_doomweaps.cpp index 6c7182908..c69fb42e2 100644 --- a/src/g_doom/a_doomweaps.cpp +++ b/src/g_doom/a_doomweaps.cpp @@ -367,9 +367,13 @@ void P_SetSafeFlash(AWeapon *weapon, player_t *player, FState *flashstate, int i } // if we get here the state doesn't seem to belong to any class in the inheritance chain // This can happen with Dehacked if the flash states are remapped. - // The only way to check this would be to go through all Dehacked modifiable actors and - // find the correct one. - // For now let's assume that it will work. + // The only way to check this would be to go through all Dehacked modifiable actors, convert + // their states into a single flat array and find the correct one. + // Rather than that, just check to make sure it belongs to something. + if (FState::StaticFindStateOwner(flashstate + index) == NULL) + { // Invalid state. With no index offset, it should at least be valid. + index = 0; + } P_SetPsprite (player, ps_flash, flashstate + index); } @@ -506,7 +510,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FirePlasma) // // [RH] A_FireRailgun // -static void FireRailgun(AActor *self, int RailOffset) +static void FireRailgun(AActor *self, int offset_xy) { int damage; player_t *player; @@ -531,7 +535,7 @@ static void FireRailgun(AActor *self, int RailOffset) damage = deathmatch ? 100 : 150; - P_RailAttack (self, damage, RailOffset); + P_RailAttack (self, damage, offset_xy); } @@ -638,8 +642,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BFGSpray) damage += (pr_bfgspray() & 7) + 1; thingToHit = linetarget; - P_DamageMobj (thingToHit, self->target, self->target, damage, spray != NULL? FName(spray->DamageType) : FName(NAME_BFGSplash)); - P_TraceBleed (damage, thingToHit, self->target); + int newdam = P_DamageMobj (thingToHit, self->target, self->target, damage, spray != NULL? FName(spray->DamageType) : FName(NAME_BFGSplash)); + P_TraceBleed (newdam > 0 ? newdam : damage, thingToHit, self->target); } return 0; } diff --git a/src/g_doom/a_lostsoul.cpp b/src/g_doom/a_lostsoul.cpp index a8fb44c0a..a7bea635b 100644 --- a/src/g_doom/a_lostsoul.cpp +++ b/src/g_doom/a_lostsoul.cpp @@ -12,7 +12,6 @@ #include "thingdef/thingdef.h" */ - FRandom pr_lost ("LostMissileRange"); FRandom pr_oldsoul ("BetaLostSoul"); // diff --git a/src/g_doom/a_painelemental.cpp b/src/g_doom/a_painelemental.cpp index 7698e9f7c..72b4c608d 100644 --- a/src/g_doom/a_painelemental.cpp +++ b/src/g_doom/a_painelemental.cpp @@ -120,7 +120,6 @@ void A_PainShootSkull (AActor *self, angle_t angle, PClassActor *spawntype, int other = Spawn (spawntype, x, y, z, ALLOW_REPLACE); - // Check to see if the new Lost Soul's z value is above the // ceiling of its new sector, or below the floor. If so, kill it. diff --git a/src/g_doom/a_revenant.cpp b/src/g_doom/a_revenant.cpp index 59aa83fe2..414f8f11b 100644 --- a/src/g_doom/a_revenant.cpp +++ b/src/g_doom/a_revenant.cpp @@ -157,8 +157,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_SkelFist) { int damage = ((pr_skelfist()%10)+1)*6; S_Sound (self, CHAN_WEAPON, "skeleton/melee", 1, ATTN_NORM); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } return 0; } diff --git a/src/g_doom/a_scriptedmarine.cpp b/src/g_doom/a_scriptedmarine.cpp index 92fa5b2fb..6fbf77cfd 100644 --- a/src/g_doom/a_scriptedmarine.cpp +++ b/src/g_doom/a_scriptedmarine.cpp @@ -153,9 +153,10 @@ void AScriptedMarine::Tick () // //============================================================================ -DEFINE_ACTION_FUNCTION(AActor, A_M_Refire) +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Refire) { PARAM_ACTION_PROLOGUE; + PARAM_BOOL_OPT(ignoremissile) { ignoremissile = false; } if (self->target == NULL || self->target->health <= 0) { @@ -169,7 +170,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_M_Refire) self->SetState (self->state + 1); return 0; } - if ((self->MissileState == NULL && !self->CheckMeleeRange ()) || + if (((ignoremissile || self->MissileState == NULL) && !self->CheckMeleeRange ()) || !P_CheckSight (self, self->target) || pr_m_refire() < 4) // Small chance of stopping even when target not dead { diff --git a/src/g_game.cpp b/src/g_game.cpp index 724ac7e3f..e48f64f6e 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -161,10 +161,9 @@ int consoleplayer; // player taking events int gametic; CVAR(Bool, demo_compress, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); -char demoname[256]; +FString demoname; bool demorecording; bool demoplayback; -bool netdemo; bool demonew; // [RH] Only used around G_InitNew for demos int demover; BYTE* demobuffer; @@ -415,7 +414,7 @@ CCMD (invuseall) CCMD (invuse) { - if (players[consoleplayer].inventorytics == 0 || gameinfo.gametype == GAME_Strife) + if (players[consoleplayer].inventorytics == 0) { if (players[consoleplayer].mo) SendItemUse = players[consoleplayer].mo->InvSel; } @@ -1131,10 +1130,10 @@ void G_Ticker () if (cmd->ucmd.forwardmove > TURBOTHRESHOLD && !(gametic&31) && ((gametic>>5)&(MAXPLAYERS-1)) == i ) { - Printf ("%s is turbo!\n", players[i].userinfo.netname); + Printf ("%s is turbo!\n", players[i].userinfo.GetName()); } - if (netgame && !players[i].isbot && !netdemo && (gametic%ticdup) == 0) + if (netgame && !players[i].isbot && !demoplayback && (gametic%ticdup) == 0) { //players[i].inconsistant = 0; if (gametic > BACKUPTICS*ticdup && consistancy[i][buf] != cmd->consistancy) @@ -1316,7 +1315,7 @@ void G_PlayerReborn (int player) int chasecam; BYTE currclass; userinfo_t userinfo; // [RH] Save userinfo - botskill_t b_skill;//Added by MC: + botskill_t b_skill; //Added by MC: APlayerPawn *actor; PClassPlayerPawn *cls; FString log; @@ -1330,7 +1329,7 @@ void G_PlayerReborn (int player) secretcount = p->secretcount; currclass = p->CurrentPlayerClass; b_skill = p->skill; //Added by MC: - memcpy (&userinfo, &p->userinfo, sizeof(userinfo)); + userinfo.TransferFrom(p->userinfo); actor = p->mo; cls = p->cls; log = p->LogText; @@ -1346,7 +1345,7 @@ void G_PlayerReborn (int player) p->itemcount = itemcount; p->secretcount = secretcount; p->CurrentPlayerClass = currclass; - memcpy (&p->userinfo, &userinfo, sizeof(userinfo)); + p->userinfo.TransferFrom(userinfo); p->mo = actor; p->cls = cls; p->LogText = log; @@ -1389,6 +1388,10 @@ bool G_CheckSpot (int playernum, FPlayerStart *mthing) y = mthing->y; z = mthing->z; + if (!(level.flags & LEVEL_USEPLAYERSTARTZ)) + { + z = 0; + } z += P_PointInSector (x, y)->floorplane.ZatPoint (x, y); if (!players[playernum].mo) @@ -2052,7 +2055,6 @@ static void PutSavePic (FILE *file, int width, int height) } else { - P_CheckPlayerSprites(); Renderer->WriteSavePic(&players[consoleplayer], file, width, height); } } @@ -2097,11 +2099,8 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio G_WriteHubInfo(stdfile); { - BYTE vars[4096], *vars_p; - vars_p = vars; - C_WriteCVars (&vars_p, CVAR_SERVERINFO); - *vars_p = 0; - M_AppendPNGText (stdfile, "Important CVARs", (char *)vars); + FString vars = C_GetMassCVarString(CVAR_SERVERINFO); + M_AppendPNGText (stdfile, "Important CVARs", vars.GetChars()); } if (level.time != 0 || level.maptime != 0) @@ -2274,7 +2273,7 @@ void G_WriteDemoTiccmd (ticcmd_t *cmd, int player, int buf) void G_RecordDemo (const char* name) { usergame = false; - strcpy (demoname, name); + demoname = name; FixPathSeperator (demoname); DefaultExtension (demoname, ".lmp"); maxdemosize = 0x20000; @@ -2507,7 +2506,7 @@ bool G_ProcessIFFDemo (char *mapname) } if (numPlayers > 1) - multiplayer = netgame = netdemo = true; + multiplayer = netgame = true; if (uncompSize > 0) { @@ -2645,7 +2644,6 @@ bool G_CheckDemoStatus (void) P_SetupWeapons_ntohton(); demoplayback = false; - netdemo = false; netgame = false; multiplayer = false; singletics = false; @@ -2713,11 +2711,18 @@ bool G_CheckDemoStatus (void) formlen = demobuffer + 4; WriteLong (int(demo_p - demobuffer - 8), &formlen); - M_WriteFile (demoname, demobuffer, int(demo_p - demobuffer)); + bool saved = M_WriteFile (demoname, demobuffer, int(demo_p - demobuffer)); M_Free (demobuffer); demorecording = false; stoprecording = false; - Printf ("Demo %s recorded\n", demoname); + if (saved) + { + Printf ("Demo %s recorded\n", demoname.GetChars()); + } + else + { + Printf ("Demo %s could not be saved\n", demoname.GetChars()); + } } return false; diff --git a/src/g_heretic/a_chicken.cpp b/src/g_heretic/a_chicken.cpp index 8fb626ab5..f5c7ff29f 100644 --- a/src/g_heretic/a_chicken.cpp +++ b/src/g_heretic/a_chicken.cpp @@ -75,8 +75,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_ChicAttack) if (self->CheckMeleeRange()) { int damage = 1 + (pr_chicattack() & 1); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } return 0; } diff --git a/src/g_heretic/a_dsparil.cpp b/src/g_heretic/a_dsparil.cpp index 24bc06b4b..f14ce98fb 100644 --- a/src/g_heretic/a_dsparil.cpp +++ b/src/g_heretic/a_dsparil.cpp @@ -78,8 +78,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_Srcr1Attack) if (self->CheckMeleeRange ()) { int damage = pr_scrc1atk.HitDice (8); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } @@ -219,8 +219,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_Srcr2Attack) if (self->CheckMeleeRange()) { int damage = pr_s2a.HitDice (20); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } chance = self->health < self->SpawnHealth()/2 ? 96 : 48; diff --git a/src/g_heretic/a_hereticmisc.cpp b/src/g_heretic/a_hereticmisc.cpp index 773f3ac53..fa33c1297 100644 --- a/src/g_heretic/a_hereticmisc.cpp +++ b/src/g_heretic/a_hereticmisc.cpp @@ -187,7 +187,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_VolcanoBlast) blast->vely = FixedMul (1*FRACUNIT, finesine[angle]); blast->velz = (FRACUNIT*5/2) + (pr_blast() << 10); S_Sound (blast, CHAN_BODY, "world/volcano/shoot", 1, ATTN_NORM); - P_CheckMissileSpawn (blast); + P_CheckMissileSpawn (blast, self->radius); } return 0; } @@ -224,7 +224,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_VolcBallImpact) tiny->velx = FixedMul (FRACUNIT*7/10, finecosine[angle]); tiny->vely = FixedMul (FRACUNIT*7/10, finesine[angle]); tiny->velz = FRACUNIT + (pr_volcimpact() << 9); - P_CheckMissileSpawn (tiny); + P_CheckMissileSpawn (tiny, self->radius); } return 0; } diff --git a/src/g_heretic/a_hereticweaps.cpp b/src/g_heretic/a_hereticweaps.cpp index 927978d5e..046210fd8 100644 --- a/src/g_heretic/a_hereticweaps.cpp +++ b/src/g_heretic/a_hereticweaps.cpp @@ -414,7 +414,7 @@ void FireMacePL1B (AActor *actor) ball->velx = (actor->velx>>1) + FixedMul(ball->Speed, finecosine[angle]); ball->vely = (actor->vely>>1) + FixedMul(ball->Speed, finesine[angle]); S_Sound (ball, CHAN_BODY, "weapons/maceshoot", 1, ATTN_NORM); - P_CheckMissileSpawn (ball); + P_CheckMissileSpawn (ball, actor->radius); } //---------------------------------------------------------------------------- @@ -568,7 +568,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MaceBallImpact2) tiny->velx = (self->velx>>1) + FixedMul(self->velz-FRACUNIT, finecosine[angle]); tiny->vely = (self->vely>>1) + FixedMul(self->velz-FRACUNIT, finesine[angle]); tiny->velz = self->velz; - P_CheckMissileSpawn (tiny); + P_CheckMissileSpawn (tiny, self->radius); tiny = Spawn("MaceFX3", self->x, self->y, self->z, ALLOW_REPLACE); angle = self->angle-ANG90; @@ -578,7 +578,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MaceBallImpact2) tiny->velx = (self->velx>>1) + FixedMul(self->velz-FRACUNIT, finecosine[angle]); tiny->vely = (self->vely>>1) + FixedMul(self->velz-FRACUNIT, finesine[angle]); tiny->velz = self->velz; - P_CheckMissileSpawn (tiny); + P_CheckMissileSpawn (tiny, self->radius); } else { // Explode @@ -851,7 +851,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SpawnRippers) angle >>= ANGLETOFINESHIFT; ripper->velx = FixedMul (ripper->Speed, finecosine[angle]); ripper->vely = FixedMul (ripper->Speed, finesine[angle]); - P_CheckMissileSpawn (ripper); + P_CheckMissileSpawn (ripper, self->radius); } return 0; } @@ -1118,7 +1118,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SkullRodStorm) mo->velx = 1; // Force collision detection mo->velz = -mo->Speed; mo->special2 = self->special2; // Transfer player number - P_CheckMissileSpawn (mo); + P_CheckMissileSpawn (mo, self->radius); if (self->special1 != -1 && !S_IsActorPlayingSomething (self, CHAN_BODY, -1)) { S_Sound (self, CHAN_BODY|CHAN_LOOP, self->special1, 1, ATTN_NORM); @@ -1386,7 +1386,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FirePhoenixPL2) { S_Sound (self, CHAN_WEAPON|CHAN_LOOP, soundid, 1, ATTN_NORM); } - P_CheckMissileSpawn (mo); + P_CheckMissileSpawn (mo, self->radius); return 0; } diff --git a/src/g_heretic/a_ironlich.cpp b/src/g_heretic/a_ironlich.cpp index 852f3d258..2dae04546 100644 --- a/src/g_heretic/a_ironlich.cpp +++ b/src/g_heretic/a_ironlich.cpp @@ -85,8 +85,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_LichAttack) if (self->CheckMeleeRange ()) { int damage = pr_atk.HitDice (6); - P_DamageMobj (target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, target, self); + int newdam = P_DamageMobj (target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, target, self); return 0; } dist = P_AproxDistance (self->x-target->x, self->y-target->y) @@ -118,7 +118,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LichAttack) fire->velz = baseFire->velz; fire->Damage = NULL; fire->health = (i+1) * 2; - P_CheckMissileSpawn (fire); + P_CheckMissileSpawn (fire, self->radius); } } } @@ -193,7 +193,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LichIceImpact) shard->velx = FixedMul (shard->Speed, finecosine[angle]); shard->vely = FixedMul (shard->Speed, finesine[angle]); shard->velz = -FRACUNIT*6/10; - P_CheckMissileSpawn (shard); + P_CheckMissileSpawn (shard, self->radius); } return 0; } diff --git a/src/g_heretic/a_knight.cpp b/src/g_heretic/a_knight.cpp index 36e83488b..bd8ba4aa0 100644 --- a/src/g_heretic/a_knight.cpp +++ b/src/g_heretic/a_knight.cpp @@ -52,8 +52,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_KnightAttack) if (self->CheckMeleeRange ()) { int damage = pr_knightatk.HitDice (3); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); S_Sound (self, CHAN_BODY, "hknight/melee", 1, ATTN_NORM); return 0; } diff --git a/src/g_heretic/a_wizard.cpp b/src/g_heretic/a_wizard.cpp index 458cb8afe..b6b386112 100644 --- a/src/g_heretic/a_wizard.cpp +++ b/src/g_heretic/a_wizard.cpp @@ -80,8 +80,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_WizAtk3) if (self->CheckMeleeRange()) { int damage = pr_wizatk3.HitDice (4); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } PClassActor *fx = PClass::FindActor("WizardFX1"); diff --git a/src/g_hexen/a_bishop.cpp b/src/g_hexen/a_bishop.cpp index 76a8dfd99..3ad28d86e 100644 --- a/src/g_hexen/a_bishop.cpp +++ b/src/g_hexen/a_bishop.cpp @@ -34,8 +34,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_BishopAttack) if (self->CheckMeleeRange()) { int damage = pr_atk.HitDice (4); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } self->special1 = (pr_atk() & 3) + 5; diff --git a/src/g_hexen/a_dragon.cpp b/src/g_hexen/a_dragon.cpp index 7b0467dc8..5a8c5f062 100644 --- a/src/g_hexen/a_dragon.cpp +++ b/src/g_hexen/a_dragon.cpp @@ -87,8 +87,8 @@ static void DragonSeek (AActor *actor, angle_t thresh, angle_t turnMax) if (actor->CheckMeleeRange ()) { int damage = pr_dragonseek.HitDice (10); - P_DamageMobj (actor->target, actor, actor, damage, NAME_Melee); - P_TraceBleed (damage, actor->target, actor); + int newdam = P_DamageMobj (actor->target, actor, actor, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, actor->target, actor); S_Sound (actor, CHAN_WEAPON, actor->AttackSound, 1, ATTN_NORM); } else if (pr_dragonseek() < 128 && P_CheckMissileRange(actor)) @@ -206,8 +206,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_DragonFlight) if (abs(self->angle-angle) < ANGLE_45/2 && self->CheckMeleeRange()) { int damage = pr_dragonflight.HitDice (8); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); } else if (abs(self->angle-angle) <= ANGLE_1*20) diff --git a/src/g_hexen/a_flechette.cpp b/src/g_hexen/a_flechette.cpp index ac1b4b024..84a7b6dcb 100644 --- a/src/g_hexen/a_flechette.cpp +++ b/src/g_hexen/a_flechette.cpp @@ -128,7 +128,7 @@ bool AArtiPoisonBag3::Use (bool pickup) mo->target = Owner; mo->tics -= pr_poisonbag()&3; - P_CheckMissileSpawn(mo); + P_CheckMissileSpawn(mo, Owner->radius); return true; } return false; diff --git a/src/g_hexen/a_hexenspecialdecs.cpp b/src/g_hexen/a_hexenspecialdecs.cpp index 9695a9288..2449b6db3 100644 --- a/src/g_hexen/a_hexenspecialdecs.cpp +++ b/src/g_hexen/a_hexenspecialdecs.cpp @@ -72,13 +72,14 @@ DEFINE_ACTION_FUNCTION(AActor, A_PotteryExplode) } } S_Sound (mo, CHAN_BODY, "PotteryExplode", 1, ATTN_NORM); - if (self->args[0]>=0 && self->args[0]<=255 && SpawnableThings[self->args[0]]) - { // Spawn an item + // Spawn an item? + PClassActor *type = P_GetSpawnableType(self->args[0]); + if (type != NULL) + { if (!((level.flags2 & LEVEL2_NOMONSTERS) || (dmflags & DF_NO_MONSTERS)) - || !(GetDefaultByType (SpawnableThings[self->args[0]])->flags3 & MF3_ISMONSTER)) + || !(GetDefaultByType (type)->flags3 & MF3_ISMONSTER)) { // Only spawn monsters if not -nomonsters - Spawn (SpawnableThings[self->args[0]], - self->x, self->y, self->z, ALLOW_REPLACE); + Spawn (type, self->x, self->y, self->z, ALLOW_REPLACE); } } return 0; @@ -317,13 +318,14 @@ DEFINE_ACTION_FUNCTION(AActor, A_SoAExplode) mo->vely = pr_soaexplode.Random2()<<(FRACBITS-6); } } - if (self->args[0]>=0 && self->args[0]<=255 && SpawnableThings[self->args[0]]) - { // Spawn an item + // Spawn an item? + PClassActor *type = P_GetSpawnableType(self->args[0]); + if (type != NULL) + { if (!((level.flags2 & LEVEL2_NOMONSTERS) || (dmflags & DF_NO_MONSTERS)) - || !(GetDefaultByType (SpawnableThings[self->args[0]])->flags3 & MF3_ISMONSTER)) + || !(GetDefaultByType (type)->flags3 & MF3_ISMONSTER)) { // Only spawn monsters if not -nomonsters - Spawn (SpawnableThings[self->args[0]], - self->x, self->y, self->z, ALLOW_REPLACE); + Spawn (type, self->x, self->y, self->z, ALLOW_REPLACE); } } S_Sound (self, CHAN_BODY, self->DeathSound, 1, ATTN_NORM); diff --git a/src/g_hexen/a_korax.cpp b/src/g_hexen/a_korax.cpp index e859d88cc..01cc532ff 100644 --- a/src/g_hexen/a_korax.cpp +++ b/src/g_hexen/a_korax.cpp @@ -532,5 +532,5 @@ AActor *P_SpawnKoraxMissile (fixed_t x, fixed_t y, fixed_t z, dist = 1; } th->velz = (dest->z-z+(30*FRACUNIT))/dist; - return (P_CheckMissileSpawn(th) ? th : NULL); + return (P_CheckMissileSpawn(th, source->radius) ? th : NULL); } diff --git a/src/g_hexen/a_serpent.cpp b/src/g_hexen/a_serpent.cpp index 8cbf0e163..aa0719e62 100644 --- a/src/g_hexen/a_serpent.cpp +++ b/src/g_hexen/a_serpent.cpp @@ -197,8 +197,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_SerpentMeleeAttack) if (self->CheckMeleeRange ()) { int damage = pr_serpentmeattack.HitDice (5); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); S_Sound (self, CHAN_BODY, "SerpentMeleeHit", 1, ATTN_NORM); } if (pr_serpentmeattack() < 96) diff --git a/src/g_hexen/a_spike.cpp b/src/g_hexen/a_spike.cpp index 177b64b49..9ed02f22b 100644 --- a/src/g_hexen/a_spike.cpp +++ b/src/g_hexen/a_spike.cpp @@ -168,8 +168,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_ThrustImpale) if (thing == self) continue; // don't clip against self - P_DamageMobj (thing, self, self, 10001, NAME_Crush); - P_TraceBleed (10001, thing); + int newdam = P_DamageMobj (thing, self, self, 10001, NAME_Crush); + P_TraceBleed (newdam > 0 ? newdam : 10001, thing); self->args[1] = 1; // Mark thrust thing as bloody } return 0; diff --git a/src/g_level.cpp b/src/g_level.cpp index 32acf14cb..755a4fba6 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -77,6 +77,7 @@ #include "d_netinf.h" #include "v_palette.h" #include "menu/menu.h" +#include "a_sharedglobal.h" #include "a_strifeglobal.h" #include "r_data/colormaps.h" #include "farchive.h" @@ -114,7 +115,6 @@ extern bool timingdemo; int starttime; -extern bool netdemo; extern FString BackupSaveName; bool savegamerestore; @@ -228,9 +228,8 @@ void G_NewInit () int i; G_ClearSnapshots (); - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); netgame = false; - netdemo = false; multiplayer = false; if (demoplayback) { @@ -241,14 +240,15 @@ void G_NewInit () for (i = 0; i < MAXPLAYERS; ++i) { player_t *p = &players[i]; - userinfo_t saved_ui = players[i].userinfo; + userinfo_t saved_ui; + saved_ui.TransferFrom(players[i].userinfo); int chasecam = p->cheats & CF_CHASECAM; p->~player_t(); ::new(p) player_t; players[i].cheats |= chasecam; players[i].playerstate = PST_DEAD; playeringame[i] = 0; - players[i].userinfo = saved_ui; + players[i].userinfo.TransferFrom(saved_ui); } BackupSaveName = ""; consoleplayer = 0; @@ -288,7 +288,7 @@ static void InitPlayerClasses () { for (int i = 0; i < MAXPLAYERS; ++i) { - SinglePlayerClass[i] = players[i].userinfo.PlayerClass; + SinglePlayerClass[i] = players[i].userinfo.GetPlayerClassNum(); if (SinglePlayerClass[i] < 0 || !playeringame[i]) { SinglePlayerClass[i] = (pr_classchoice()) % PlayerClasses.Size (); @@ -426,7 +426,7 @@ void G_InitNew (const char *mapname, bool bTitleLevel) demoplayback = false; automapactive = false; viewactive = true; - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); //Added by MC: Initialize bots. if (!deathmatch) @@ -491,7 +491,7 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill } else if (strncmp(levelname, "enDSeQ", 6) != 0) { - nextinfo = FindLevelInfo (levelname); + nextinfo = FindLevelInfo (levelname, false); if (nextinfo != NULL) { level_info_t *nextredir = nextinfo->CheckLevelRedirect(); @@ -657,17 +657,14 @@ void G_DoCompleted (void) } else { - if (strncmp (nextlevel, "enDSeQ", 6) == 0) + level_info_t *nextinfo = FindLevelInfo (nextlevel, false); + if (nextinfo == NULL || strncmp (nextlevel, "enDSeQ", 6) == 0) { wminfo.next = nextlevel; wminfo.LName1 = NULL; } else { - - - - level_info_t *nextinfo = FindLevelInfo (nextlevel); wminfo.next = nextinfo->mapname; wminfo.LName1 = TexMan[TexMan.CheckForTexture(nextinfo->pname, FTexture::TEX_MiscPatch)]; } @@ -740,6 +737,9 @@ void G_DoCompleted (void) if (!(level.flags2 & LEVEL2_FORGETSTATE)) { G_SnapshotLevel (); + // Do not free any global strings this level might reference + // while it's not loaded. + FBehavior::StaticLockLevelVarStrings(); } else { // Make sure we don't have a snapshot lying around from before. @@ -882,6 +882,10 @@ void G_DoLoadLevel (int position, bool autosave) { level.flags2 &= ~LEVEL2_NOMONSTERS; } + if (changeflags & CHANGELEVEL_PRERAISEWEAPON) + { + level.flags2 |= LEVEL2_PRERAISEWEAPON; + } level.maptime = 0; P_SetupLevel (level.mapname, position); @@ -928,7 +932,7 @@ void G_DoLoadLevel (int position, bool autosave) G_UnSnapshotLevel (!savegamerestore); // [RH] Restore the state of the level. G_FinishTravel (); // For each player, if they are viewing through a player, make sure it is themselves. - for (int ii = 0; i < MAXPLAYERS; ++i) + for (int ii = 0; ii < MAXPLAYERS; ++ii) { if (playeringame[ii] && (players[ii].camera == NULL || players[ii].camera->player != NULL)) { @@ -1128,7 +1132,7 @@ void G_FinishTravel () // The player being spawned here is a short lived dummy and // must not start any ENTER script or big problems will happen. - pawndup = P_SpawnPlayer (&playerstarts[pawn->player - players], int(pawn->player - players), true); + pawndup = P_SpawnPlayer (&playerstarts[pawn->player - players], int(pawn->player - players), SPF_TEMPPLAYER); if (!(changeflags & CHANGELEVEL_KEEPFACING)) { pawn->angle = pawndup->angle; @@ -1273,6 +1277,7 @@ void G_InitLevelLocals () NormalLight.ChangeFade (level.fadeto); level.DefaultEnvironment = info->DefaultEnvironment; + level.DefaultSkybox = NULL; } //========================================================================== @@ -1436,6 +1441,11 @@ void G_SerializeLevel (FArchive &arc, bool hubLoad) P_SerializeSubsectors(arc); StatusBar->Serialize (arc); + if (SaveVersion >= 4222) + { // This must be done *after* thinkers are serialized. + arc << level.DefaultSkybox; + } + arc << level.total_monsters << level.total_items << level.total_secrets; // Does this level have custom translations? @@ -1568,6 +1578,11 @@ void G_UnSnapshotLevel (bool hubLoad) } // No reason to keep the snapshot around once the level's been entered. level.info->ClearSnapshot(); + if (hubLoad) + { + // Unlock ACS global strings that were locked when the snapshot was made. + FBehavior::StaticUnlockLevelVarStrings(); + } } //========================================================================== diff --git a/src/g_level.h b/src/g_level.h index 188207f32..2cf833202 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -179,7 +179,7 @@ enum ELevelFlags LEVEL2_KEEPFULLINVENTORY = 0x00000040, // doesn't reduce the amount of inventory items to 1 - /* = 0x00000080, */ + LEVEL2_PRERAISEWEAPON = 0x00000080, // players should spawn with their weapons fully raised (but not when respawning it multiplayer) LEVEL2_MONSTERFALLINGDAMAGE = 0x00000100, LEVEL2_CLIPMIDTEX = 0x00000200, LEVEL2_WRAPMIDTEX = 0x00000400, @@ -424,6 +424,8 @@ struct FLevelLocals int airsupply; int DefaultEnvironment; // Default sound environment. + TObjPtr DefaultSkybox; + FSectorScrollValues *Scrolls; // NULL if no DScrollers in this level SBYTE WallVertLight; // Light diffs for vert/horiz walls @@ -498,6 +500,7 @@ enum CHANGELEVEL_CHANGESKILL = 8, CHANGELEVEL_NOINTERMISSION = 16, CHANGELEVEL_RESETHEALTH = 32, + CHANGELEVEL_PRERAISEWEAPON = 64, }; void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill=-1); @@ -512,7 +515,7 @@ void G_InitLevelLocals (void); void G_AirControlChanged (); cluster_info_t *FindClusterInfo (int cluster); -level_info_t *FindLevelInfo (const char *mapname); +level_info_t *FindLevelInfo (const char *mapname, bool allowdefault=true); level_info_t *FindLevelByNum (int num); level_info_t *CheckLevelRedirect (level_info_t *info); diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index b3952ec1b..cf469cbe8 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -82,7 +82,7 @@ static int FindWadLevelInfo (const char *name) // //========================================================================== -level_info_t *FindLevelInfo (const char *mapname) +level_info_t *FindLevelInfo (const char *mapname, bool allowdefault) { int i; @@ -90,7 +90,7 @@ level_info_t *FindLevelInfo (const char *mapname) { return &wadlevelinfos[i]; } - else + else if (allowdefault) { if (TheDefaultLevelInfo.LevelName.IsEmpty()) { @@ -100,6 +100,7 @@ level_info_t *FindLevelInfo (const char *mapname) } return &TheDefaultLevelInfo; } + return NULL; } //========================================================================== @@ -197,6 +198,9 @@ void G_ClearSnapshots (void) { wadlevelinfos[i].ClearSnapshot(); } + // Since strings are only locked when snapshotting a level, unlock them + // all now, since we got rid of all the snapshots that cared about them. + GlobalACSStrings.UnlockAll(); } //========================================================================== @@ -305,6 +309,10 @@ FString level_info_t::LookupLevelName() { mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(mapname + 3)); } + else if (mapname[0] == 'L' && mapname[1] == 'E' && mapname[2] == 'V' && mapname[3] == 'E' && mapname[4] == 'L') + { + mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(mapname + 5)); + } thename = strstr (lookedup, checkstring); if (thename == NULL) { @@ -1267,6 +1275,7 @@ MapFlagHandlers[] = { "forgetstate", MITYPE_SETFLAG2, LEVEL2_FORGETSTATE, 0 }, { "rememberstate", MITYPE_CLRFLAG2, LEVEL2_FORGETSTATE, 0 }, { "unfreezesingleplayerconversations",MITYPE_SETFLAG2, LEVEL2_CONV_SINGLE_UNFREEZE, 0 }, + { "spawnwithweaponraised", MITYPE_SETFLAG2, LEVEL2_PRERAISEWEAPON, 0 }, { "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes { "compat_shorttex", MITYPE_COMPATFLAG, COMPATF_SHORTTEX, 0 }, { "compat_stairs", MITYPE_COMPATFLAG, COMPATF_STAIRINDEX, 0 }, diff --git a/src/g_raven/a_minotaur.cpp b/src/g_raven/a_minotaur.cpp index 075c681f8..b54daffeb 100644 --- a/src/g_raven/a_minotaur.cpp +++ b/src/g_raven/a_minotaur.cpp @@ -154,8 +154,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk1) if (self->CheckMeleeRange()) { int damage = pr_minotauratk1.HitDice (4); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); if ((player = self->target->player) != NULL && player->mo == self->target) { // Squish the player @@ -297,8 +297,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk2) { int damage; damage = pr_atk.HitDice (friendly ? 3 : 5); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } z = self->z + 40*FRACUNIT; @@ -346,8 +346,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurAtk3) int damage; damage = pr_minotauratk3.HitDice (friendly ? 3 : 5); - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); if ((player = self->target->player) != NULL && player->mo == self->target) { // Squish the player @@ -397,7 +397,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MntrFloorFire) mo = Spawn("MinotaurFX3", x, y, self->floorz, ALLOW_REPLACE); mo->target = self->target; mo->velx = 1; // Force block checking - P_CheckMissileSpawn (mo); + P_CheckMissileSpawn (mo, self->radius); return 0; } @@ -419,8 +419,8 @@ void P_MinotaurSlam (AActor *source, AActor *target) target->velx += FixedMul (thrust, finecosine[angle]); target->vely += FixedMul (thrust, finesine[angle]); damage = pr_minotaurslam.HitDice (static_cast(source) ? 4 : 6); - P_DamageMobj (target, NULL, NULL, damage, NAME_Melee); - P_TraceBleed (damage, target, angle, 0); + int newdam = P_DamageMobj (target, NULL, NULL, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, target, angle, 0); if (target->player) { target->reactiontime = 14+(pr_minotaurslam()&7); @@ -589,7 +589,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurChase) if (!self1->target || (self1->target->health <= 0) || !(self1->target->flags&MF_SHOOTABLE)) { // look for a new target - self1->SetState (self1->FindState ("Spawn")); + self1->SetIdle(); return 0; } diff --git a/src/g_shared/a_action.cpp b/src/g_shared/a_action.cpp index 1dad91587..2f47912b7 100644 --- a/src/g_shared/a_action.cpp +++ b/src/g_shared/a_action.cpp @@ -380,7 +380,6 @@ CUSTOM_CVAR(Int, sv_corpsequeuesize, 64, CVAR_ARCHIVE|CVAR_SERVERINFO) while (first != NULL && first->Count > (DWORD)self) { DCorpsePointer *next = iterator.Next (); - next->Count = first->Count; first->Destroy (); first = next; } @@ -403,9 +402,8 @@ DCorpsePointer::DCorpsePointer (AActor *ptr) if (first->Count >= (DWORD)sv_corpsequeuesize) { DCorpsePointer *next = iterator.Next (); - next->Count = first->Count; first->Destroy (); - return; + first = next; } } ++first->Count; diff --git a/src/g_shared/a_artifacts.cpp b/src/g_shared/a_artifacts.cpp index 59cf187ff..c71d3cc81 100644 --- a/src/g_shared/a_artifacts.cpp +++ b/src/g_shared/a_artifacts.cpp @@ -1107,7 +1107,6 @@ void APowerWeaponLevel2::EndEffect () Super::EndEffect(); if (player != NULL) { - if (player->ReadyWeapon != NULL && player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP) { @@ -1156,6 +1155,25 @@ void APlayerSpeedTrail::Tick () IMPLEMENT_CLASS (APowerSpeed) +//=========================================================================== +// +// APowerSpeed :: Serialize +// +//=========================================================================== + +void APowerSpeed::Serialize(FArchive &arc) +{ + Super::Serialize (arc); + if (SaveVersion < 4146) + { + SpeedFlags = 0; + } + else + { + arc << SpeedFlags; + } +} + //=========================================================================== // // APowerSpeed :: GetSpeedFactor @@ -1164,8 +1182,10 @@ IMPLEMENT_CLASS (APowerSpeed) fixed_t APowerSpeed ::GetSpeedFactor () { - if (Inventory != NULL) return FixedMul(Speed, Inventory->GetSpeedFactor()); - else return Speed; + if (Inventory != NULL) + return FixedMul(Speed, Inventory->GetSpeedFactor()); + else + return Speed; } //=========================================================================== @@ -1184,12 +1204,22 @@ void APowerSpeed::DoEffect () if (Owner->player->cheats & CF_PREDICTING) return; + if (SpeedFlags & PSF_NOTRAIL) + return; + if (level.time & 1) return; - // check if another speed item is present to avoid multiple drawing of the speed trail. - if (Inventory != NULL && Inventory->GetSpeedFactor() > FRACUNIT) - return; + // Check if another speed item is present to avoid multiple drawing of the speed trail. + // Only the last PowerSpeed without PSF_NOTRAIL set will actually draw the trail. + for (AInventory *item = Inventory; item != NULL; item = item->Inventory) + { + if (item->IsKindOf(RUNTIME_CLASS(APowerSpeed)) && + !(static_cast(item)->SpeedFlags & PSF_NOTRAIL)) + { + return; + } + } if (P_AproxDistance (Owner->velx, Owner->vely) <= 12*FRACUNIT) return; @@ -1411,7 +1441,8 @@ void APowerTimeFreezer::DoEffect() Super::DoEffect(); // [RH] Do not change LEVEL_FROZEN on odd tics, or the Revenant's tracer // will get thrown off. - if (level.time & 1) + // [ED850] Don't change it if the player is predicted either. + if (level.time & 1 || (Owner != NULL && Owner->player != NULL && Owner->player->cheats & CF_PREDICTING)) { return; } @@ -1503,7 +1534,7 @@ void APowerDamage::EndEffect( ) //=========================================================================== // -// APowerDamage :: AbsorbDamage +// APowerDamage :: ModifyDamage // //=========================================================================== diff --git a/src/g_shared/a_artifacts.h b/src/g_shared/a_artifacts.h index cc1796c7d..668c1da8d 100644 --- a/src/g_shared/a_artifacts.h +++ b/src/g_shared/a_artifacts.h @@ -30,6 +30,9 @@ protected: virtual void InitEffect (); virtual void DoEffect (); virtual void EndEffect (); + + friend void EndAllPowerupEffects(AInventory *item); + friend void InitAllPowerupEffects(AInventory *item); }; // An artifact is an item that gives the player a powerup when activated. @@ -143,9 +146,14 @@ class APowerSpeed : public APowerup DECLARE_CLASS (APowerSpeed, APowerup) protected: void DoEffect (); + void Serialize(FArchive &arc); fixed_t GetSpeedFactor(); +public: + int SpeedFlags; }; +#define PSF_NOTRAIL 1 + class APowerMinotaur : public APowerup { DECLARE_CLASS (APowerMinotaur, APowerup) diff --git a/src/g_shared/a_decals.cpp b/src/g_shared/a_decals.cpp index 22f53eefa..576ff6a5c 100644 --- a/src/g_shared/a_decals.cpp +++ b/src/g_shared/a_decals.cpp @@ -755,6 +755,48 @@ CCMD (spray) Net_WriteString (argv[1]); } +DBaseDecal *ShootDecal(const FDecalTemplate *tpl, AActor *basisactor, sector_t *sec, fixed_t x, fixed_t y, fixed_t z, angle_t angle, fixed_t tracedist, bool permanent) +{ + if (tpl == NULL || (tpl = tpl->GetDecal()) == NULL) + { + return NULL; + } + + FTraceResults trace; + DBaseDecal *decal; + side_t *wall; + + angle >>= ANGLETOFINESHIFT; + + Trace(x, y, z, sec, + finecosine[angle], finesine[angle], 0, + tracedist, 0, 0, NULL, trace, TRACE_NoSky); + + if (trace.HitType == TRACE_HitWall) + { + if (permanent) + { + decal = new DBaseDecal(trace.Z); + wall = trace.Line->sidedef[trace.Side]; + decal->StickToWall(wall, trace.X, trace.Y, trace.ffloor); + tpl->ApplyToDecal(decal, wall); + // Spread decal to nearby walls if it does not all fit on this one + if (cl_spreaddecals) + { + decal->Spread(tpl, wall, trace.X, trace.Y, trace.Z, trace.ffloor); + } + return decal; + } + else + { + return DImpactDecal::StaticCreate(tpl, + trace.X, trace.Y, trace.Z, + trace.Line->sidedef[trace.Side], NULL); + } + } + return NULL; +} + class ADecal : public AActor { DECLARE_CLASS (ADecal, AActor); @@ -767,9 +809,6 @@ IMPLEMENT_CLASS (ADecal) void ADecal::BeginPlay () { const FDecalTemplate *tpl; - FTraceResults trace; - DBaseDecal *decal; - side_t *wall; Super::BeginPlay (); @@ -781,31 +820,13 @@ void ADecal::BeginPlay () if (!tpl->PicNum.Exists()) { Printf("Decal actor at (%d,%d) does not have a valid texture\n", x>>FRACBITS, y>>FRACBITS); - } else { // Look for a wall within 64 units behind the actor. If none can be // found, then no decal is created, and this actor is destroyed // without effectively doing anything. - Trace (x, y, z, Sector, - finecosine[(angle+ANGLE_180)>>ANGLETOFINESHIFT], - finesine[(angle+ANGLE_180)>>ANGLETOFINESHIFT], 0, - 64*FRACUNIT, 0, 0, NULL, trace, TRACE_NoSky); - - if (trace.HitType == TRACE_HitWall) - { - decal = new DBaseDecal (this); - wall = trace.Line->sidedef[trace.Side]; - decal->StickToWall (wall, trace.X, trace.Y, trace.ffloor); - tpl->ApplyToDecal (decal, wall); - // Spread decal to nearby walls if it does not all fit on this one - if (cl_spreaddecals) - { - decal->Spread (tpl, wall, trace.X, trace.Y, z, trace.ffloor); - } - } - else + if (NULL == ShootDecal(tpl, this, Sector, x, y, z, angle + ANGLE_180, 64*FRACUNIT, true)) { DPrintf ("Could not find a wall to stick decal to at (%d,%d)\n", x>>FRACBITS, y>>FRACBITS); } diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index dafa1bafc..cf4d1b500 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -15,6 +15,9 @@ static FRandom pr_morphmonst ("MorphMonster"); +void EndAllPowerupEffects(AInventory *item); +void InitAllPowerupEffects(AInventory *item); + //--------------------------------------------------------------------------- // // FUNC P_MorphPlayer @@ -74,6 +77,7 @@ bool P_MorphPlayer (player_t *activator, player_t *p, PClassPlayerPawn *spawntyp } morphed = static_cast(Spawn (spawntype, actor->x, actor->y, actor->z, NO_REPLACE)); + EndAllPowerupEffects(actor->Inventory); DObject::StaticPointerSubstitution (actor, morphed); if ((actor->tid != 0) && (style & MORPH_NEWTIDBEHAVIOUR)) { @@ -144,6 +148,7 @@ bool P_MorphPlayer (player_t *activator, player_t *p, PClassPlayerPawn *spawntyp } item = next; } + InitAllPowerupEffects(morphed->Inventory); morphed->ActivateMorphWeapon (); if (p->camera == actor) { @@ -201,10 +206,8 @@ bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag, } pmo->player = NULL; - mo->ObtainInventory (pmo); - DObject::StaticPointerSubstitution (pmo, mo); // Remove the morph power if the morph is being undone prematurely. - for (AInventory *item = mo->Inventory, *next = NULL; item != NULL; item = next) + for (AInventory *item = pmo->Inventory, *next = NULL; item != NULL; item = next) { next = item->Inventory; if (item->IsKindOf(RUNTIME_CLASS(APowerMorph))) @@ -213,6 +216,9 @@ bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag, item->Destroy(); } } + EndAllPowerupEffects(pmo->Inventory); + mo->ObtainInventory (pmo); + DObject::StaticPointerSubstitution (pmo, mo); if ((pmo->tid != 0) && (player->MorphStyle & MORPH_NEWTIDBEHAVIOUR)) { mo->tid = pmo->tid; @@ -235,6 +241,7 @@ bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag, mo->flags2 = (mo->flags2 & ~MF2_FLY) | (pmo->flags2 & MF2_FLY); mo->flags3 = (mo->flags3 & ~MF3_GHOST) | (pmo->flags3 & MF3_GHOST); mo->Score = pmo->Score; + InitAllPowerupEffects(mo->Inventory); PClassActor *exit_flash = player->MorphExitFlash; bool correctweapon = !!(player->MorphStyle & MORPH_LOSEACTUALWEAPON); @@ -280,11 +287,11 @@ bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag, size_t skinindex = 0; // If a custom skin was in use, then reload it // or else the base skin for the player class. - if ((unsigned int)player->userinfo.skin >= PlayerClasses.Size () && - (size_t)player->userinfo.skin < numskins) + if ((unsigned int)player->userinfo.GetSkin() >= PlayerClasses.Size () && + (size_t)player->userinfo.GetSkin() < numskins) { - skinindex = player->userinfo.skin; + skinindex = player->userinfo.GetSkin(); } else if (PlayerClasses.Size () > 1) { @@ -366,7 +373,7 @@ bool P_MorphMonster (AActor *actor, PClassActor *spawntype, int duration, int st { AMorphedMonster *morphed; - if (actor->player || spawntype == NULL || + if (actor == NULL || actor->player || spawntype == NULL || actor->flags3 & MF3_DONTMORPH || !(actor->flags3 & MF3_ISMONSTER) || !spawntype->IsDescendantOf (RUNTIME_CLASS(AMorphedMonster))) @@ -387,8 +394,8 @@ bool P_MorphMonster (AActor *actor, PClassActor *spawntype, int duration, int st morphed->MorphStyle = style; morphed->MorphExitFlash = (exit_flash) ? exit_flash : RUNTIME_CLASS(ATeleportFog); morphed->FlagsSave = actor->flags & ~MF_JUSTHIT; - //morphed->special = actor->special; - //memcpy (morphed->args, actor->args, sizeof(actor->args)); + morphed->special = actor->special; + memcpy (morphed->args, actor->args, sizeof(actor->args)); morphed->CopyFriendliness (actor, true); morphed->flags |= actor->flags & MF_SHADOW; morphed->flags3 |= actor->flags3 & MF3_GHOST; @@ -398,6 +405,7 @@ bool P_MorphMonster (AActor *actor, PClassActor *spawntype, int duration, int st } morphed->AddToHash (); actor->RemoveFromHash (); + actor->special = 0; actor->tid = 0; actor->flags &= ~(MF_SOLID|MF_SHOOTABLE); actor->flags |= MF_UNMORPHED; @@ -420,7 +428,8 @@ bool P_UndoMonsterMorph (AMorphedMonster *beast, bool force) if (beast->UnmorphTime == 0 || beast->UnmorphedMe == NULL || - beast->flags3 & MF3_STAYMORPHED) + beast->flags3 & MF3_STAYMORPHED || + beast->UnmorphedMe->flags3 & MF3_STAYMORPHED) { return false; } @@ -428,10 +437,13 @@ bool P_UndoMonsterMorph (AMorphedMonster *beast, bool force) actor->SetOrigin (beast->x, beast->y, beast->z); actor->flags |= MF_SOLID; beast->flags &= ~MF_SOLID; + int beastflags6 = beast->flags6; + beast->flags6 &= ~MF6_TOUCHY; if (!force && !P_TestMobjLocation (actor)) { // Didn't fit actor->flags &= ~MF_SOLID; beast->flags |= MF_SOLID; + beast->flags6 = beastflags6; beast->UnmorphTime = level.time + 5*TICRATE; // Next try in 5 seconds return false; } @@ -538,6 +550,46 @@ bool P_MorphedDeath(AActor *actor, AActor **morphed, int *morphedstyle, int *mor return false; } +//=========================================================================== +// +// EndAllPowerupEffects +// +// Calls EndEffect() on every Powerup in the inventory list. +// +//=========================================================================== + +void EndAllPowerupEffects(AInventory *item) +{ + while (item != NULL) + { + if (item->IsKindOf(RUNTIME_CLASS(APowerup))) + { + static_cast(item)->EndEffect(); + } + item = item->Inventory; + } +} + +//=========================================================================== +// +// InitAllPowerupEffects +// +// Calls InitEffect() on every Powerup in the inventory list. +// +//=========================================================================== + +void InitAllPowerupEffects(AInventory *item) +{ + while (item != NULL) + { + if (item->IsKindOf(RUNTIME_CLASS(APowerup))) + { + static_cast(item)->InitEffect(); + } + item = item->Inventory; + } +} + // Base class for morphing projectiles -------------------------------------- IMPLEMENT_CLASS(AMorphProjectile) diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index f2f5b0857..98ecf9e61 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -561,6 +561,33 @@ void AInventory::BeginPlay () flags |= MF_DROPPED; // [RH] Items are dropped by default } +//=========================================================================== +// +// AInventory :: Grind +// +//=========================================================================== + +bool AInventory::Grind(bool items) +{ + // Does this grind request even care about items? + if (!items) + { + return false; + } + // Dropped items are normally destroyed by crushers. Set the DONTGIB flag, + // and they'll act like corpses with it set and be immune to crushers. + if (flags & MF_DROPPED) + { + if (!(flags3 & MF3_DONTGIB)) + { + Destroy(); + } + return false; + } + // Non-dropped items call the super method for compatibility. + return Super::Grind(items); +} + //=========================================================================== // // AInventory :: DoEffect @@ -984,6 +1011,8 @@ void AInventory::Touch (AActor *toucher) toucher = toucher->player->mo; } + bool localview = toucher->CheckLocalView(consoleplayer); + if (!CallTryPickup (toucher, &toucher)) return; // This is the only situation when a pickup flash should ever play. @@ -996,7 +1025,7 @@ void AInventory::Touch (AActor *toucher) { const char * message = PickupMessage (); - if (message != NULL && *message != 0 && toucher->CheckLocalView (consoleplayer) + if (message != NULL && *message != 0 && localview && (StaticLastMessageTic != gametic || StaticLastMessage != message)) { StaticLastMessageTic = gametic; @@ -1010,7 +1039,10 @@ void AInventory::Touch (AActor *toucher) if (toucher->player != NULL) { PlayPickupSound (toucher->player->mo); - toucher->player->bonuscount = BONUSADD; + if (!(ItemFlags & IF_NOSCREENFLASH)) + { + toucher->player->bonuscount = BONUSADD; + } } else { diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index 0f09a58dd..482bdab8a 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -124,6 +124,7 @@ enum IF_PERSISTENTPOWER = 1<<18, // Powerup is kept when travelling between levels IF_RESTRICTABSOLUTELY = 1<<19, // RestrictedTo and ForbiddenTo do not allow pickup in any form by other classes IF_NEVERRESPAWN = 1<<20, // Never, ever respawns + IF_NOSCREENFLASH = 1<<21, // No pickup flash on the player's screen }; @@ -160,6 +161,7 @@ public: virtual bool SpecialDropAction (AActor *dropper); virtual bool DrawPowerup (int x, int y); virtual void DoEffect (); + virtual bool Grind(bool items); virtual const char *PickupMessage (); virtual void PlayPickupSound (AActor *toucher); @@ -283,6 +285,7 @@ public: PClassActor *ProjectileType; // Projectile used by primary attack PClassActor *AltProjectileType; // Projectile used by alternate attack int SelectionOrder; // Lower-numbered weapons get picked first + int MinSelAmmo1, MinSelAmmo2; // Ignore in BestWeapon() if inadequate ammo fixed_t MoveCombatDist; // Used by bots, but do they *really* need it? int ReloadCounter; // For A_CheckForReload int BobStyle; // [XA] Bobbing style. Defines type of bobbing (e.g. Normal, Alpha) @@ -376,7 +379,17 @@ enum WIF_BOT_BFG = 1<<28, // this is a BFG }; -#define S_LIGHTDONE 0 +class AWeaponGiver : public AWeapon +{ + DECLARE_CLASS(AWeaponGiver, AWeapon) + +public: + bool TryPickup(AActor *&toucher); + void Serialize(FArchive &arc); + + fixed_t DropAmmoFactor; +}; + // Health is some item that gives the player health when picked up. class PClassHealth : public PClassInventory diff --git a/src/g_shared/a_randomspawner.cpp b/src/g_shared/a_randomspawner.cpp index a58d34f32..56262459d 100644 --- a/src/g_shared/a_randomspawner.cpp +++ b/src/g_shared/a_randomspawner.cpp @@ -31,6 +31,7 @@ class ARandomSpawner : public AActor DDropItem *di; // di will be our drop item list iterator DDropItem *drop; // while drop stays as the reference point. int n = 0; + bool nomonsters = (dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS); Super::BeginPlay(); drop = di = GetDropItems(); @@ -40,29 +41,46 @@ class ARandomSpawner : public AActor { if (di->Name != NAME_None) { - if (di->Amount < 0) di->Amount = 1; // default value is -1, we need a positive value. - n += di->Amount; // this is how we can weight the list. + if (!nomonsters || !(GetDefaultByType(PClass::FindClass(di->Name))->flags3 & MF3_ISMONSTER)) + { + if (di->Amount < 0) di->Amount = 1; // default value is -1, we need a positive value. + n += di->Amount; // this is how we can weight the list. + } di = di->Next; } } + if (n == 0) + { // Nothing left to spawn. They must have all been monsters, and monsters are disabled. + Destroy(); + return; + } // Then we reset the iterator to the start position... di = drop; // Take a random number... n = pr_randomspawn(n); // And iterate in the array up to the random number chosen. - while (n > -1) + while (n > -1 && di != NULL) { - if (di->Name != NAME_None) + if (di->Name != NAME_None && + (!nomonsters || !(GetDefaultByType(PClass::FindClass(di->Name))->flags3 & MF3_ISMONSTER))) { n -= di->Amount; - if ((di->Next != NULL) && (n > -1)) di = di->Next; else n = -1; + if ((di->Next != NULL) && (n > -1)) + di = di->Next; + else + n = -1; + } + else + { + di = di->Next; } } // So now we can spawn the dropped item. - if (bouncecount >= MAX_RANDOMSPAWNERS_RECURSION) // Prevents infinite recursions + if (di == NULL || bouncecount >= MAX_RANDOMSPAWNERS_RECURSION) // Prevents infinite recursions { Spawn("Unknown", x, y, z, NO_REPLACE); // Show that there's a problem. - Destroy(); return; + Destroy(); + return; } else if (pr_randomspawn() <= di->Probability) // prob 255 = always spawn, prob 0 = almost never spawn. { @@ -167,7 +185,7 @@ class ARandomSpawner : public AActor newmobj->z += SpawnPoint[2]; } if (newmobj->flags & MF_MISSILE) - P_CheckMissileSpawn(newmobj); + P_CheckMissileSpawn(newmobj, 0); // Bouncecount is used to count how many recursions we're in. if (newmobj->IsKindOf(PClass::FindClass("RandomSpawner"))) newmobj->bouncecount = ++bouncecount; @@ -179,8 +197,10 @@ class ARandomSpawner : public AActor if (rep && ((rep->flags4 & MF4_BOSSDEATH) || (rep->flags2 & MF2_BOSS))) boss = true; } - if (boss) this->tracer = newmobj; - else Destroy(); // "else" because a boss-replacing spawner must wait until it can call A_BossDeath. + if (boss) + this->tracer = newmobj; + else // "else" because a boss-replacing spawner must wait until it can call A_BossDeath. + Destroy(); } void Tick() // This function is needed for handling boss replacers diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h index af54efb12..1fe70c4b4 100644 --- a/src/g_shared/a_sharedglobal.h +++ b/src/g_shared/a_sharedglobal.h @@ -9,7 +9,8 @@ struct vertex_t; struct side_t; struct F3DFloor; -extern void P_SpawnDirt (AActor *actor, fixed_t radius); +void P_SpawnDirt (AActor *actor, fixed_t radius); +class DBaseDecal *ShootDecal(const FDecalTemplate *tpl, AActor *basisactor, sector_t *sec, fixed_t x, fixed_t y, fixed_t z, angle_t angle, fixed_t tracedist, bool permanent); class DBaseDecal : public DThinker { diff --git a/src/g_shared/a_skies.cpp b/src/g_shared/a_skies.cpp index 5e894002d..616b87c9e 100644 --- a/src/g_shared/a_skies.cpp +++ b/src/g_shared/a_skies.cpp @@ -49,21 +49,9 @@ void ASkyViewpoint::BeginPlay () { Super::BeginPlay (); - if (tid == 0) + if (tid == 0 && level.DefaultSkybox == NULL) { - int i; - - for (i = 0; i = 4203) + { + arc << MinSelAmmo1 << MinSelAmmo2; + } } //=========================================================================== @@ -328,7 +332,7 @@ void AWeapon::AttachToOwner (AActor *other) SisterWeapon = AddWeapon (SisterWeaponType); if (Owner->player != NULL) { - if (!Owner->player->userinfo.neverswitch && !(WeaponFlags & WIF_NO_AUTO_SWITCH)) + if (!Owner->player->userinfo.GetNeverSwitch() && !(WeaponFlags & WIF_NO_AUTO_SWITCH)) { Owner->player->PendingWeapon = this; } @@ -719,16 +723,17 @@ FState *AWeapon::GetZoomState () /* Weapon giver ***********************************************************/ -class AWeaponGiver : public AWeapon -{ - DECLARE_CLASS(AWeaponGiver, AWeapon) - -public: - bool TryPickup(AActor *&toucher); -}; - IMPLEMENT_CLASS(AWeaponGiver) +void AWeaponGiver::Serialize(FArchive &arc) +{ + Super::Serialize(arc); + if (SaveVersion >= 4246) + { + arc << DropAmmoFactor; + } +} + bool AWeaponGiver::TryPickup(AActor *&toucher) { DDropItem *di = GetDropItems(); @@ -746,8 +751,17 @@ bool AWeaponGiver::TryPickup(AActor *&toucher) { weap->ItemFlags &= ~IF_ALWAYSPICKUP; // use the flag of this item only. weap->flags = (weap->flags & ~MF_DROPPED) | (this->flags & MF_DROPPED); + + // If our ammo gives are non-negative, transfer them to the real weapon. if (AmmoGive1 >= 0) weap->AmmoGive1 = AmmoGive1; if (AmmoGive2 >= 0) weap->AmmoGive2 = AmmoGive2; + + // If DropAmmoFactor is non-negative, modify the given ammo amounts. + if (DropAmmoFactor > 0) + { + weap->AmmoGive1 = FixedMul(weap->AmmoGive1, DropAmmoFactor); + weap->AmmoGive2 = FixedMul(weap->AmmoGive2, DropAmmoFactor); + } weap->BecomeItem(); } else return false; diff --git a/src/g_shared/hudmessages.cpp b/src/g_shared/hudmessages.cpp index 76eaedcec..f60c8a84e 100644 --- a/src/g_shared/hudmessages.cpp +++ b/src/g_shared/hudmessages.cpp @@ -131,6 +131,9 @@ DHUDMessage::DHUDMessage (FFont *font, const char *text, float x, float y, int h Left = intpart - (float)(fracpart & 3) / 10.f; } } + NoWrap = false; + ClipX = ClipY = ClipWidth = ClipHeight = 0; + WrapWidth = 0; Top = y; Next = NULL; Lines = NULL; @@ -160,7 +163,7 @@ DHUDMessage::~DHUDMessage () Lines = NULL; if (screen != NULL) { - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } } if (SourceText != NULL) @@ -182,6 +185,17 @@ void DHUDMessage::Serialize (FArchive &arc) << Tics << State << TextColor << SBarID << SourceText << Font << Next << HUDWidth << HUDHeight; + if (SaveVersion >= 3960) + { + arc << NoWrap; + arc << ClipX << ClipY << ClipWidth << ClipHeight; + arc << WrapWidth; + } + else + { + NoWrap = false; + ClipX = ClipY = ClipWidth = ClipHeight = WrapWidth = 0; + } if (arc.IsLoading ()) { Lines = NULL; @@ -220,6 +234,37 @@ void DHUDMessage::ScreenSizeChanged () } } +//============================================================================ +// +// DHUDMessage :: CalcClipCoords +// +// Take the clip rectangle in HUD coordinates (set via SetHudSize in ACS) +// and convert them to screen coordinates. +// +//============================================================================ + +void DHUDMessage::CalcClipCoords(int hudheight) +{ + int x = ClipX, y = ClipY, w = ClipWidth, h = ClipHeight; + + if ((x | y | w | h) == 0) + { // No clipping rectangle set; use the full screen. + ClipLeft = 0; + ClipTop = 0; + ClipRight = screen->GetWidth(); + ClipBot = screen->GetHeight(); + } + else + { + screen->VirtualToRealCoordsInt(x, y, w, h, + HUDWidth, hudheight, false, true); + ClipLeft = x; + ClipTop = y; + ClipRight = x + w; + ClipBot = y + h; + } +} + //============================================================================ // // DHUDMessage :: ResetText @@ -232,11 +277,11 @@ void DHUDMessage::ResetText (const char *text) if (HUDWidth != 0) { - width = HUDWidth; + width = WrapWidth == 0 ? HUDWidth : WrapWidth; } else { - width = con_scaletext >=2 ? SCREENWIDTH/2 : (con_scaletext ? SCREENWIDTH / CleanXfac : SCREENWIDTH); + width = con_scaletext >= 2 ? SCREENWIDTH/2 : (con_scaletext ? SCREENWIDTH / CleanXfac : SCREENWIDTH); } if (Lines != NULL) @@ -244,7 +289,7 @@ void DHUDMessage::ResetText (const char *text) V_FreeBrokenLines (Lines); } - Lines = V_BreakLines (Font, width, (BYTE *)text); + Lines = V_BreakLines (Font, NoWrap ? INT_MAX : width, (BYTE *)text); NumLines = 0; Width = 0; @@ -377,15 +422,14 @@ void DHUDMessage::Draw (int bottom, int visibility) ystep = Font->GetHeight() * yscale; if (HUDHeight < 0) - { - // A negative height means the HUD size covers the status bar + { // A negative height means the HUD size covers the status bar hudheight = -HUDHeight; } else - { - // A positive height means the HUD size does not cover the status bar + { // A positive height means the HUD size does not cover the status bar hudheight = Scale (HUDHeight, screen_height, bottom); } + CalcClipCoords(hudheight); for (i = 0; i < NumLines; i++) { @@ -441,6 +485,10 @@ void DHUDMessage::DoDraw (int linenum, int x, int y, bool clean, int hudheight) screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_VirtualWidth, HUDWidth, DTA_VirtualHeight, hudheight, + DTA_ClipLeft, ClipLeft, + DTA_ClipRight, ClipRight, + DTA_ClipTop, ClipTop, + DTA_ClipBottom, ClipBot, DTA_Alpha, Alpha, DTA_RenderStyle, Style, TAG_DONE); @@ -541,11 +589,15 @@ void DHUDMessageFadeOut::DoDraw (int linenum, int x, int y, bool clean, int hudh screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_VirtualWidth, HUDWidth, DTA_VirtualHeight, hudheight, + DTA_ClipLeft, ClipLeft, + DTA_ClipRight, ClipRight, + DTA_ClipTop, ClipTop, + DTA_ClipBottom, ClipBot, DTA_Alpha, trans, DTA_RenderStyle, Style, TAG_DONE); } - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } } @@ -638,11 +690,15 @@ void DHUDMessageFadeInOut::DoDraw (int linenum, int x, int y, bool clean, int hu screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_VirtualWidth, HUDWidth, DTA_VirtualHeight, hudheight, + DTA_ClipLeft, ClipLeft, + DTA_ClipRight, ClipRight, + DTA_ClipTop, ClipTop, + DTA_ClipBottom, ClipBot, DTA_Alpha, trans, DTA_RenderStyle, Style, TAG_DONE); } - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } else { @@ -814,6 +870,10 @@ void DHUDMessageTypeOnFadeOut::DoDraw (int linenum, int x, int y, bool clean, in screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_VirtualWidth, HUDWidth, DTA_VirtualHeight, hudheight, + DTA_ClipLeft, ClipLeft, + DTA_ClipRight, ClipRight, + DTA_ClipTop, ClipTop, + DTA_ClipBottom, ClipBot, DTA_Alpha, Alpha, DTA_TextLen, LineVisible, DTA_RenderStyle, Style, diff --git a/src/g_shared/sbar.h b/src/g_shared/sbar.h index df3daf597..3d8c62001 100644 --- a/src/g_shared/sbar.h +++ b/src/g_shared/sbar.h @@ -48,11 +48,15 @@ enum EHudState { HUD_StatusBar, HUD_Fullscreen, - HUD_None + HUD_None, + + HUD_AltHud // Used for passing through popups to the alt hud }; class AWeapon; +void ST_SetNeedRefresh(); + // HUD Message base object -------------------------------------------------- class DHUDMessage : public DObject @@ -85,22 +89,42 @@ public: { Alpha = alpha; } + void SetNoWrap(bool nowrap) + { + NoWrap = nowrap; + ResetText(SourceText); + } + void SetClipRect(int x, int y, int width, int height) + { + ClipX = x; + ClipY = y; + ClipWidth = width; + ClipHeight = height; + } + void SetWrapWidth(int wrap) + { + WrapWidth = wrap; + ResetText(SourceText); + } protected: FBrokenLines *Lines; int Width, Height, NumLines; float Left, Top; - bool CenterX; + bool CenterX, NoWrap; int HoldTics; int Tics; int State; int VisibilityFlags; int HUDWidth, HUDHeight; + int ClipX, ClipY, ClipWidth, ClipHeight, WrapWidth; // in HUD coords + int ClipLeft, ClipTop, ClipRight, ClipBot; // in screen coords EColorRange TextColor; FFont *Font; FRenderStyle Style; fixed_t Alpha; + void CalcClipCoords(int hudheight); DHUDMessage () : SourceText(NULL) {} private: @@ -400,6 +424,7 @@ private: bool RepositionCoords (int &x, int &y, int xo, int yo, const int w, const int h) const; void DrawMessages (int layer, int bottom); void DrawConsistancy () const; + void DrawWaiting () const; TObjPtr Messages[NUM_HUDMSGLAYERS]; bool ShowLog; diff --git a/src/g_shared/sbar_mugshot.cpp b/src/g_shared/sbar_mugshot.cpp index 4587a9332..fcf5642de 100644 --- a/src/g_shared/sbar_mugshot.cpp +++ b/src/g_shared/sbar_mugshot.cpp @@ -98,7 +98,7 @@ FTexture *FMugShotFrame::GetTexture(const char *default_face, const char *skin_f } sprite.UnlockBuffer(); } - return TexMan[TexMan.CheckForTexture(sprite, 0, true)]; + return TexMan[TexMan.CheckForTexture(sprite, 0, FTextureManager::TEXMAN_TryAny|FTextureManager::TEXMAN_AllowSkins)]; } //=========================================================================== @@ -505,7 +505,7 @@ FTexture *FMugShot::GetFace(player_t *player, const char *default_face, int accu } if (CurrentState != NULL) { - const char *skin_face = player->morphTics ? player->MorphedPlayerClass->Face : skins[player->userinfo.skin].face; + const char *skin_face = player->morphTics ? player->MorphedPlayerClass->Face : skins[player->userinfo.GetSkin()].face; return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle); } return NULL; diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 7e0667062..09be47363 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -1040,37 +1040,41 @@ public: //prepare ammo counts GetCurrentAmmo(ammo1, ammo2, ammocount1, ammocount2); armor = CPlayer->mo->FindInventory(); - if(hud != lastHud) - { - script->huds[hud]->Tick(NULL, this, true); - // Restore scaling if need be. - if(scalingWasForced) + if(state != HUD_AltHud) + { + if(hud != lastHud) { - scalingWasForced = false; - SetScaled(false); - setsizeneeded = true; + script->huds[hud]->Tick(NULL, this, true); + + // Restore scaling if need be. + if(scalingWasForced) + { + scalingWasForced = false; + SetScaled(false); + setsizeneeded = true; + } } - } - if(currentPopup != POP_None && !script->huds[hud]->FullScreenOffsets()) - script->huds[hud]->Draw(NULL, this, script->popups[currentPopup-1].getXDisplacement(), script->popups[currentPopup-1].getYDisplacement(), FRACUNIT); - else - script->huds[hud]->Draw(NULL, this, 0, 0, FRACUNIT); - lastHud = hud; - - // Handle inventory bar drawing - if(CPlayer->inventorytics > 0 && !(level.flags & LEVEL_NOINVENTORYBAR) && (state == HUD_StatusBar || state == HUD_Fullscreen)) - { - SBarInfoMainBlock *inventoryBar = state == HUD_StatusBar ? script->huds[STBAR_INVENTORY] : script->huds[STBAR_INVENTORYFULLSCREEN]; - if(inventoryBar != lastInventoryBar) - inventoryBar->Tick(NULL, this, true); - - // No overlay? Lets cancel it. - if(inventoryBar->NumCommands() == 0) - CPlayer->inventorytics = 0; + if(currentPopup != POP_None && !script->huds[hud]->FullScreenOffsets()) + script->huds[hud]->Draw(NULL, this, script->popups[currentPopup-1].getXDisplacement(), script->popups[currentPopup-1].getYDisplacement(), FRACUNIT); else - inventoryBar->DrawAux(NULL, this, 0, 0, FRACUNIT); + script->huds[hud]->Draw(NULL, this, 0, 0, FRACUNIT); + lastHud = hud; + + // Handle inventory bar drawing + if(CPlayer->inventorytics > 0 && !(level.flags & LEVEL_NOINVENTORYBAR) && (state == HUD_StatusBar || state == HUD_Fullscreen)) + { + SBarInfoMainBlock *inventoryBar = state == HUD_StatusBar ? script->huds[STBAR_INVENTORY] : script->huds[STBAR_INVENTORYFULLSCREEN]; + if(inventoryBar != lastInventoryBar) + inventoryBar->Tick(NULL, this, true); + + // No overlay? Lets cancel it. + if(inventoryBar->NumCommands() == 0) + CPlayer->inventorytics = 0; + else + inventoryBar->DrawAux(NULL, this, 0, 0, FRACUNIT); + } } // Handle popups @@ -1199,8 +1203,8 @@ public: h = forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight; double dcx = cx == 0 ? 0 : dx + ((double) cx / FRACUNIT) - texture->GetScaledLeftOffsetDouble(); double dcy = cy == 0 ? 0 : dy + ((double) cy / FRACUNIT) - texture->GetScaledTopOffsetDouble(); - double dcr = cr == 0 ? INT_MAX : dx + w - ((double) cr / FRACUNIT); - double dcb = cb == 0 ? INT_MAX : dy + h - ((double) cb / FRACUNIT); + double dcr = cr == 0 ? INT_MAX : dx + w - ((double) cr / FRACUNIT) - texture->GetScaledLeftOffsetDouble(); + double dcb = cb == 0 ? INT_MAX : dy + h - ((double) cb / FRACUNIT) - texture->GetScaledTopOffsetDouble(); if(Scaled) { @@ -1288,10 +1292,10 @@ public: // Check for clipping if(cx != 0 || cy != 0 || cr != 0 || cb != 0) { - rcx = cx == 0 ? 0 : rx+(((double) cx/FRACUNIT)*xScale); - rcy = cy == 0 ? 0 : ry+(((double) cy/FRACUNIT)*yScale); - rcr = cr == 0 ? INT_MAX : rx+w-(((double) cr/FRACUNIT)*xScale); - rcb = cb == 0 ? INT_MAX : ry+h-(((double) cb/FRACUNIT)*yScale); + rcx = cx == 0 ? 0 : rx+((((double) cx/FRACUNIT) - texture->GetScaledLeftOffsetDouble())*xScale); + rcy = cy == 0 ? 0 : ry+((((double) cy/FRACUNIT) - texture->GetScaledTopOffsetDouble())*yScale); + rcr = cr == 0 ? INT_MAX : rx+w-((((double) cr/FRACUNIT) + texture->GetScaledLeftOffsetDouble())*xScale); + rcb = cb == 0 ? INT_MAX : ry+h-((((double) cb/FRACUNIT) + texture->GetScaledTopOffsetDouble())*yScale); } if(clearDontDraw) diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index 2efee7c15..ce69af37f 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -736,9 +736,9 @@ class CommandDrawString : public SBarInfoCommand } break; case PLAYERCLASS: - if(statusBar->CPlayer->userinfo.PlayerClass != cache) + if(statusBar->CPlayer->userinfo.GetPlayerClassNum() != cache) { - cache = statusBar->CPlayer->userinfo.PlayerClass; + cache = statusBar->CPlayer->userinfo.GetPlayerClassNum(); str = GetPrintableDisplayName(statusBar->CPlayer->cls); RealignString(); } @@ -758,7 +758,7 @@ class CommandDrawString : public SBarInfoCommand case PLAYERNAME: // Can't think of a good way to detect changes to this, so // I guess copying it every tick will have to do. - str = statusBar->CPlayer->userinfo.netname; + str = statusBar->CPlayer->userinfo.GetName(); RealignString(); break; case GLOBALVAR: @@ -1960,11 +1960,7 @@ class CommandDrawInventoryBar : public SBarInfoCommand void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) { - int spacing = 0; - if(!vertical) - spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1; - else - spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1; + int spacing = GetCounterSpacing(statusBar); int bgalpha = block->Alpha(); if(translucent) @@ -2107,16 +2103,36 @@ class CommandDrawInventoryBar : public SBarInfoCommand } sc.MustGetToken(';'); } + int GetCounterSpacing(const DSBarInfo *statusBar) const + { + FTexture *box = (style != STYLE_Strife) + ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX] + : statusBar->Images[statusBar->invBarOffset + imgCURSOR]; + if (box == NULL) + { // Don't crash without a graphic. + return 32; + } + else + { + int spacing; + if (!vertical) + { + spacing = box->GetScaledWidth(); + } + else + { + spacing = box->GetScaledHeight(); + } + return spacing + ((style != STYLE_Strife) ? 1 : -1); + } + } void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) { // Make the counters if need be. if(counters == NULL) { - int spacing = 0; - if(!vertical) - spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1; - else - spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1; + int spacing = GetCounterSpacing(statusBar); + counters = new CommandDrawNumber*[size]; for(unsigned int i = 0;i < size;i++) @@ -2314,7 +2330,8 @@ class CommandDrawBar : public SBarInfoCommand public: CommandDrawBar(SBarInfo *script) : SBarInfoCommand(script), border(0), horizontal(false), reverse(false), foreground(-1), - background(-1), type(HEALTH), interpolationSpeed(0), drawValue(0) + background(-1), type(HEALTH), interpolationSpeed(0), drawValue(0), + pixel(-1) { } @@ -2633,7 +2650,14 @@ class CommandDrawBar : public SBarInfoCommand value = 0; if(interpolationSpeed != 0 && (!hudChanged || level.time == 1)) { - if(value < drawValue) + // [BL] Since we used a percentage (in order to get the most fluid animation) + // we need to establish a cut off point so the last pixel won't hang as the animation slows + if(pixel == -1 && statusBar->Images[foreground]) + pixel = MAX(1, FRACUNIT/statusBar->Images[foreground]->GetWidth()); + + if(abs(drawValue - value) < pixel) + drawValue = value; + else if(value < drawValue) drawValue -= clamp((drawValue - value) >> 2, 1, FixedDiv(interpolationSpeed<((value - drawValue) >> 2, 1, FixedDiv(interpolationSpeed<armor->Amount : statusBar->CPlayer->mo->health; + goalValue = armor ? (statusBar->armor ? statusBar->armor->Amount : 0) : statusBar->CPlayer->mo->health; int max = armor ? 100 : statusBar->CPlayer->mo->GetMaxHealth() + statusBar->CPlayer->mo->stamina; if(max != 0 && goalValue > 0) { @@ -3349,7 +3374,7 @@ SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) case SBARINFO_DRAWSTRING: return new CommandDrawString(script); case SBARINFO_DRAWNUMBER: return new CommandDrawNumber(script); case SBARINFO_DRAWMUGSHOT: return new CommandDrawMugShot(script); - case SBARINFO_DRAWSELECTEDINVENTORY: return reinterpret_cast (new CommandDrawSelectedInventory(script)); + case SBARINFO_DRAWSELECTEDINVENTORY: return static_cast (new CommandDrawSelectedInventory(script)); case SBARINFO_DRAWSHADER: return new CommandDrawShader(script); case SBARINFO_DRAWINVENTORYBAR: return new CommandDrawInventoryBar(script); case SBARINFO_DRAWKEYBAR: return new CommandDrawKeyBar(script); diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index cc5cbec65..f6e3b10d9 100644 --- a/src/g_shared/shared_hud.cpp +++ b/src/g_shared/shared_hud.cpp @@ -49,6 +49,8 @@ #include "doomstat.h" #include "g_level.h" +#include + #define HUMETA_AltIcon 0x10f000 @@ -68,6 +70,8 @@ CVAR (Bool, hud_showmonsters, true,CVAR_ARCHIVE); // Show monster stats on HUD CVAR (Bool, hud_showitems, false,CVAR_ARCHIVE); // Show item stats on HUD CVAR (Bool, hud_showstats, false, CVAR_ARCHIVE); // for stamina and accuracy. CVAR (Bool, hud_showscore, false, CVAR_ARCHIVE); // for user maintained score +CVAR (Int , hud_showtime, 0, CVAR_ARCHIVE); // Show time on HUD +CVAR (Int , hud_timecolor, CR_GOLD,CVAR_ARCHIVE); // Color of in-game time on HUD CVAR (Int, hud_ammo_red, 25, CVAR_ARCHIVE) // ammo percent less than which status is red CVAR (Int, hud_ammo_yellow, 50, CVAR_ARCHIVE) // ammo percent less is yellow more green @@ -78,13 +82,15 @@ CVAR (Int, hud_armor_red, 25, CVAR_ARCHIVE) // armor amount less than which CVAR (Int, hud_armor_yellow, 50, CVAR_ARCHIVE) // armor amount less than which status is yellow CVAR (Int, hud_armor_green, 100, CVAR_ARCHIVE) // armor amount above is blue, below is green +CVAR (Bool, hud_berserk_health, true, CVAR_ARCHIVE); // when found berserk pack instead of health box + CVAR (Int, hudcolor_titl, CR_YELLOW, CVAR_ARCHIVE) // color of automap title CVAR (Int, hudcolor_time, CR_RED, CVAR_ARCHIVE) // color of level/hub time CVAR (Int, hudcolor_ltim, CR_ORANGE, CVAR_ARCHIVE) // color of single level time CVAR (Int, hudcolor_ttim, CR_GOLD, CVAR_ARCHIVE) // color of total time CVAR (Int, hudcolor_xyco, CR_GREEN, CVAR_ARCHIVE) // color of coordinates -CVAR (Int, hudcolor_statnames, CR_RED, CVAR_ARCHIVE) // For the letters befóre the stats +CVAR (Int, hudcolor_statnames, CR_RED, CVAR_ARCHIVE) // For the letters before the stats CVAR (Int, hudcolor_stats, CR_GREEN, CVAR_ARCHIVE) // For the stats values themselves @@ -95,6 +101,7 @@ static FFont * IndexFont; // The font for the inventory indices // Icons static FTexture * healthpic; // Health icon +static FTexture * berserkpic; // Berserk icon (Doom only) static FTexture * fragpic; // Frags icon static FTexture * invgems[4]; // Inventory arrows @@ -265,16 +272,22 @@ static void DrawStatus(player_t * CPlayer, int x, int y) // //=========================================================================== -static void DrawHealth(int health, int x, int y) +static void DrawHealth(player_t *CPlayer, int x, int y) { + int health = CPlayer->health; + // decide on the color first int fontcolor = health < hud_health_red ? CR_RED : health < hud_health_yellow ? CR_GOLD : health <= hud_health_green ? CR_GREEN : CR_BLUE; - - DrawImageToBox(healthpic, x, y, 31, 17); + + const bool haveBerserk = hud_berserk_health + && NULL != berserkpic + && NULL != CPlayer->mo->FindInventory< APowerStrength >(); + + DrawImageToBox(haveBerserk ? berserkpic : healthpic, x, y, 31, 17); DrawHudNumber(HudFont, fontcolor, health, x + 33, y + 17); } @@ -806,6 +819,84 @@ static void DrawCoordinates(player_t * CPlayer) DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE); } +//--------------------------------------------------------------------------- +// +// Draw in-game time +// +// Check AltHUDTime option value in wadsrc/static/menudef.txt +// for meaning of all display modes +// +//--------------------------------------------------------------------------- + +static void DrawTime() +{ + if (hud_showtime <= 0 || hud_showtime > 9) + { + return; + } + + int hours = 0; + int minutes = 0; + int seconds = 0; + + if (hud_showtime < 8) + { + const int timeTicks = + hud_showtime < 4 + ? level.maptime + : (hud_showtime < 6 + ? level.time + : level.totaltime); + const int timeSeconds = timeTicks / TICRATE; + + hours = timeSeconds / 3600; + minutes = (timeSeconds % 3600) / 60; + seconds = timeSeconds % 60; + } + else + { + time_t now; + time(&now); + + struct tm* timeinfo = localtime(&now); + + if (NULL != timeinfo) + { + hours = timeinfo->tm_hour; + minutes = timeinfo->tm_min; + seconds = timeinfo->tm_sec; + } + } + + const bool showMillis = 1 == hud_showtime; + const bool showSeconds = showMillis || (0 == hud_showtime % 2); + + char timeString[sizeof "HH:MM:SS.MMM"]; + + if (showMillis) + { + const int millis = (level.time % TICRATE) * (1000 / TICRATE); + + mysnprintf(timeString, sizeof(timeString), "%02i:%02i:%02i.%03i", hours, minutes, seconds, millis); + } + else if (showSeconds) + { + mysnprintf(timeString, sizeof(timeString), "%02i:%02i:%02i", hours, minutes, seconds); + } + else + { + mysnprintf(timeString, sizeof(timeString), "%02i:%02i", hours, minutes); + } + + const int characterCount = static_cast( sizeof "HH:MM" - 1 + + (showSeconds ? sizeof ":SS" - 1 : 0) + + (showMillis ? sizeof ".MMM" - 1 : 0) ); + const int width = SmallFont->GetCharWidth('0') * characterCount + 2; // small offset from screen's border + const int height = SmallFont->GetHeight(); + + DrawHudText(SmallFont, hud_timecolor, timeString, hudwidth - width, height, FRACUNIT); +} + //--------------------------------------------------------------------------- // @@ -857,7 +948,7 @@ void DrawHUD() DrawStatus(CPlayer, 5, hudheight-75); DrawFrags(CPlayer, 5, hudheight-70); } - DrawHealth(CPlayer->health, 5, hudheight-45); + DrawHealth(CPlayer, 5, hudheight-45); DrawArmor(CPlayer->mo->FindInventory(), CPlayer->mo->FindInventory(), 5, hudheight-20); i=DrawKeys(CPlayer, hudwidth-4, hudheight-10); @@ -869,6 +960,8 @@ void DrawHUD() StatusBar->DrawCrosshair(); } if (idmypos) DrawCoordinates(CPlayer); + + DrawTime(); } else { @@ -889,18 +982,18 @@ void DrawHUD() if (am_showtime) { - seconds = level.time /TICRATE; - mysnprintf(printstr, countof(printstr), "%02i:%02i:%02i", seconds/3600, (seconds%3600)/60, seconds%60); - DrawHudText(SmallFont, hudcolor_time, printstr, hudwidth-length, bottom, FRACUNIT); - bottom -= fonth; - - // Single level time for hubs if (level.clusterflags&CLUSTER_HUB) { - seconds= level.maptime /TICRATE; + seconds = level.time /TICRATE; mysnprintf(printstr, countof(printstr), "%02i:%02i:%02i", seconds/3600, (seconds%3600)/60, seconds%60); - DrawHudText(SmallFont, hudcolor_ltim, printstr, hudwidth-length, bottom, FRACUNIT); + DrawHudText(SmallFont, hudcolor_time, printstr, hudwidth-length, bottom, FRACUNIT); + bottom -= fonth; } + + // Single level time for hubs + seconds= level.maptime /TICRATE; + mysnprintf(printstr, countof(printstr), "%02i:%02i:%02i", seconds/3600, (seconds%3600)/60, seconds%60); + DrawHudText(SmallFont, hudcolor_ltim, printstr, hudwidth-length, bottom, FRACUNIT); } ST_FormatMapName(mapname); @@ -935,6 +1028,7 @@ void HUD_InitHud() default: healthpic = TexMan.FindTexture("MEDIA0"); + berserkpic = TexMan.FindTexture("PSTRA0"); HudFont=FFont::FindFont("HUDFONT_DOOM"); break; } @@ -972,6 +1066,12 @@ void HUD_InitHud() FTextureID tex = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); if (tex.isValid()) healthpic = TexMan[tex]; } + else if (sc.Compare("Berserk")) + { + sc.MustGetString(); + FTextureID tex = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + if (tex.isValid()) berserkpic = TexMan[tex]; + } else { PClass *ti = PClass::FindClass(sc.String); diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index 13e802bef..cc5d65e93 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -209,6 +209,17 @@ void ST_Clear() CrosshairNum = 0; } +//--------------------------------------------------------------------------- +// +// ST_SetNeedRefresh +// +//--------------------------------------------------------------------------- + +void ST_SetNeedRefresh() +{ + SB_state = (StatusBar == NULL || screen == NULL) ? 0 : screen->GetPageCount(); +} + //--------------------------------------------------------------------------- // // Constructor @@ -297,7 +308,7 @@ void DBaseStatusBar::SetScaled (bool scale, bool force) Displacement = 0; } ::ST_X = ST_X; - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); } //--------------------------------------------------------------------------- @@ -309,7 +320,7 @@ void DBaseStatusBar::SetScaled (bool scale, bool force) void DBaseStatusBar::AttachToPlayer (player_t *player) { CPlayer = player; - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); } //--------------------------------------------------------------------------- @@ -331,7 +342,7 @@ int DBaseStatusBar::GetPlayer () void DBaseStatusBar::MultiplayerChanged () { - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); } //--------------------------------------------------------------------------- @@ -441,7 +452,7 @@ DHUDMessage *DBaseStatusBar::DetachMessage (DHUDMessage *msg) // Redraw the status bar in case it was covered if (screen != NULL) { - SB_state = screen->GetPageCount(); + ST_SetNeedRefresh(); } return probe; } @@ -468,7 +479,7 @@ DHUDMessage *DBaseStatusBar::DetachMessage (DWORD id) // Redraw the status bar in case it was covered if (screen != NULL) { - SB_state = screen->GetPageCount(); + ST_SetNeedRefresh(); } return probe; } @@ -509,7 +520,7 @@ void DBaseStatusBar::ShowPlayerName () EColorRange color; color = (CPlayer == &players[consoleplayer]) ? CR_GOLD : CR_GREEN; - AttachMessage (new DHUDMessageFadeOut (SmallFont, CPlayer->userinfo.netname, + AttachMessage (new DHUDMessageFadeOut (SmallFont, CPlayer->userinfo.GetName(), 1.5f, 0.92f, 0, 0, color, 2.f, 0.35f), MAKE_ID('P','N','A','M')); } @@ -1227,6 +1238,10 @@ void DBaseStatusBar::DrawMessages (int layer, int bottom) void DBaseStatusBar::Draw (EHudState state) { + // HUD_AltHud state is for popups only + if (state == HUD_AltHud) + return; + char line[64+10]; if ((SB_state != 0 || BorderNeedRefresh) && state == HUD_StatusBar) @@ -1277,7 +1292,7 @@ void DBaseStatusBar::Draw (EHudState state) DTA_KeepRatio, true, DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE); - BorderNeedRefresh = screen->GetPageCount(); + V_SetBorderNeedRefresh(); } } @@ -1496,6 +1511,7 @@ void DBaseStatusBar::DrawTopStuff (EHudState state) } DrawMessages (HUDMSGLayer_OverHUD, (state == HUD_StatusBar) ? ::ST_Y : SCREENHEIGHT); DrawConsistancy (); + DrawWaiting (); if (ShowLog && MustDrawLog(state)) DrawLog (); if (noisedebug) @@ -1597,6 +1613,39 @@ void DBaseStatusBar::DrawConsistancy () const } } +void DBaseStatusBar::DrawWaiting () const +{ + int i; + char conbuff[64], *buff_p; + + if (!netgame) + return; + + buff_p = NULL; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].waiting) + { + if (buff_p == NULL) + { + strcpy (conbuff, "Waiting for:"); + buff_p = conbuff + 12; + } + *buff_p++ = ' '; + *buff_p++ = '1' + i; + *buff_p = 0; + } + } + + if (buff_p != NULL) + { + screen->DrawText (SmallFont, CR_ORANGE, + (screen->GetWidth() - SmallFont->StringWidth (conbuff)*CleanXfac) / 2, + SmallFont->GetHeight()*CleanYfac, conbuff, DTA_CleanNoMove, true, TAG_DONE); + BorderTopRefresh = screen->GetPageCount (); + } +} + void DBaseStatusBar::FlashItem (const PClass *itemtype) { } @@ -1637,7 +1686,7 @@ void DBaseStatusBar::Serialize (FArchive &arc) void DBaseStatusBar::ScreenSizeChanged () { st_scale.Callback (); - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); for (size_t i = 0; i < countof(Messages); ++i) { diff --git a/src/g_strife/a_entityboss.cpp b/src/g_strife/a_entityboss.cpp index 552911f3d..c1094af2b 100644 --- a/src/g_strife/a_entityboss.cpp +++ b/src/g_strife/a_entityboss.cpp @@ -32,7 +32,7 @@ void A_SpectralMissile (AActor *self, const char *missilename) if (missile != NULL) { missile->tracer = self->target; - P_CheckMissileSpawn(missile); + P_CheckMissileSpawn(missile, self->radius); } } } diff --git a/src/g_strife/a_oracle.cpp b/src/g_strife/a_oracle.cpp index 1582657f9..521720cd6 100644 --- a/src/g_strife/a_oracle.cpp +++ b/src/g_strife/a_oracle.cpp @@ -15,7 +15,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_WakeOracleSpectre) TThinkerIterator it(NAME_AlienSpectre3); AActor *spectre = it.Next(); - if (spectre != NULL) + if (spectre != NULL && spectre->health > 0 && self->target != spectre) { spectre->Sector->SoundTarget = spectre->LastHeard = self->LastHeard; spectre->target = self->target; diff --git a/src/g_strife/a_programmer.cpp b/src/g_strife/a_programmer.cpp index 53fbff200..1f45d8b5e 100644 --- a/src/g_strife/a_programmer.cpp +++ b/src/g_strife/a_programmer.cpp @@ -89,8 +89,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_ProgrammerMelee) S_Sound (self, CHAN_WEAPON, "programmer/clank", 1, ATTN_NORM); damage = ((pr_prog() % 10) + 1) * 6; - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); return 0; } diff --git a/src/g_strife/a_sentinel.cpp b/src/g_strife/a_sentinel.cpp index c5cfa7d26..aea274f12 100644 --- a/src/g_strife/a_sentinel.cpp +++ b/src/g_strife/a_sentinel.cpp @@ -69,7 +69,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SentinelAttack) trail->velx = missile->velx; trail->vely = missile->vely; trail->velz = missile->velz; - P_CheckMissileSpawn (trail); + P_CheckMissileSpawn (trail, self->radius); } } missile->z += missile->velz >> 2; diff --git a/src/g_strife/a_stalker.cpp b/src/g_strife/a_stalker.cpp index 2f0d5c4d3..1388699fa 100644 --- a/src/g_strife/a_stalker.cpp +++ b/src/g_strife/a_stalker.cpp @@ -70,8 +70,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_StalkerAttack) { int damage = (pr_stalker() & 7) * 2 + 2; - P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } } return 0; diff --git a/src/g_strife/a_strifeweapons.cpp b/src/g_strife/a_strifeweapons.cpp index b87503468..9b014ffc8 100644 --- a/src/g_strife/a_strifeweapons.cpp +++ b/src/g_strife/a_strifeweapons.cpp @@ -53,7 +53,7 @@ void P_DaggerAlert (AActor *target, AActor *emitter) emitter->flags4 |= MF4_INCOMBAT; emitter->target = target; - FState * painstate = emitter->FindState(NAME_Pain); + FState *painstate = emitter->FindState(NAME_Pain, NAME_Dagger); if (painstate != NULL) { emitter->SetState (painstate); @@ -574,7 +574,7 @@ AActor *P_SpawnSubMissile (AActor *source, PClassActor *type, AActor *target) } } - if (P_CheckMissileSpawn (other)) + if (P_CheckMissileSpawn (other, source->radius)) { angle_t pitch = P_AimLineAttack (source, source->angle, 1024*FRACUNIT); other->velz = FixedMul (-finesine[pitch>>ANGLETOFINESHIFT], other->Speed); diff --git a/src/g_strife/strife_sbar.cpp b/src/g_strife/strife_sbar.cpp index f541f34ff..950cba4ba 100644 --- a/src/g_strife/strife_sbar.cpp +++ b/src/g_strife/strife_sbar.cpp @@ -222,12 +222,7 @@ public: { DBaseStatusBar::Draw (state); - if (state == HUD_Fullscreen) - { - SB_state = screen->GetPageCount (); - DrawFullScreenStuff (); - } - else if (state == HUD_StatusBar) + if (state == HUD_StatusBar) { if (SB_state != 0) { @@ -235,6 +230,20 @@ public: } DrawMainBar (); } + else + { + if (state == HUD_Fullscreen) + { + ST_SetNeedRefresh(); + DrawFullScreenStuff (); + } + + // Draw pop screen (log, keys, and status) + if (CurrentPop != POP_None && PopHeight < 0) + { + DrawPopScreen (screen->GetHeight()); + } + } } void ShowPop (int popnum) @@ -279,7 +288,7 @@ public: bool MustDrawLog(EHudState state) { // Tell the base class to draw the log if the pop screen won't be displayed. - return (state == HUD_None); + return false; } private: @@ -302,7 +311,7 @@ private: CursorImage = Images[imgINVCURS] != NULL ? imgINVCURS : imgCURSOR01; - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); CurrentPop = POP_None; PendingPop = POP_NoChange; @@ -436,6 +445,7 @@ private: } // Inventory + CPlayer->inventorytics = 0; CPlayer->mo->InvFirst = ValidateInvFirst (6); for (item = CPlayer->mo->InvFirst, i = 0; item != NULL && i < 6; item = item->NextInv(), ++i) { @@ -554,12 +564,6 @@ private: } } } - - // Draw pop screen (log, keys, and status) - if (CurrentPop != POP_None && PopHeight < 0) - { - DrawPopScreen (screen->GetHeight()); - } } void DrawPopScreen (int bottom) diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 156973443..2b97bf4bd 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -86,6 +86,7 @@ FGameConfigFile::FGameConfigFile () OkayToWrite = false; // Do not allow saving of the config before DoGameSetup() bMigrating = false; + bModSetup = false; pathname = GetConfigPath (true); ChangePathName (pathname); LoadConfigFile (MigrateStub, NULL); @@ -376,8 +377,8 @@ void FGameConfigFile::DoGameSetup (const char *gamename) SetRavenDefaults (gameinfo.gametype == GAME_Hexen); } - // The NetServerInfo section will be read when it's determined that - // a netgame is being played. + // The NetServerInfo section will be read and override anything loaded + // here when it's determined that a netgame is being played. strncpy (subsection, "LocalServerInfo", sublen); if (SetSection (section)) { @@ -445,6 +446,24 @@ void FGameConfigFile::DoGameSetup (const char *gamename) OkayToWrite = true; } +// Like DoGameSetup(), but for mod-specific cvars. +// Called after CVARINFO has been parsed. +void FGameConfigFile::DoModSetup(const char *gamename) +{ + mysnprintf(section, countof(section), "%s.Player.Mod", gamename); + if (SetSection(section)) + { + ReadCVars(CVAR_MOD|CVAR_USERINFO|CVAR_IGNORE); + } + mysnprintf(section, countof(section), "%s.LocalServerInfo.Mod", gamename); + if (SetSection (section)) + { + ReadCVars (CVAR_MOD|CVAR_SERVERINFO|CVAR_IGNORE); + } + // Signal that these sections should be rewritten when saving the config. + bModSetup = true; +} + void FGameConfigFile::ReadNetVars () { strncpy (subsection, "NetServerInfo", sublen); @@ -452,21 +471,31 @@ void FGameConfigFile::ReadNetVars () { ReadCVars (0); } + if (bModSetup) + { + mysnprintf(subsection, sublen, "NetServerInfo.Mod"); + if (SetSection(section)) + { + ReadCVars(CVAR_MOD|CVAR_SERVERINFO|CVAR_IGNORE); + } + } } +// Read cvars from a cvar section of the ini. Flags are the flags to give +// to newly-created cvars that were not already defined. void FGameConfigFile::ReadCVars (DWORD flags) { const char *key, *value; FBaseCVar *cvar; UCVarValue val; + flags |= CVAR_ARCHIVE|CVAR_UNSETTABLE|CVAR_AUTO; while (NextInSection (key, value)) { cvar = FindCVar (key, NULL); if (cvar == NULL) { - cvar = new FStringCVar (key, NULL, - CVAR_AUTO|CVAR_UNSETTABLE|CVAR_ARCHIVE|flags); + cvar = new FStringCVar (key, NULL, flags); } val.String = const_cast(value); cvar->SetGenericRep (val, CVAR_String); @@ -483,26 +512,43 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) strncpy (subsection, "Player", sublen); SetSection (section, true); ClearCurrentSection (); - C_ArchiveCVars (this, 4); + C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_USERINFO); + + if (bModSetup) + { + strncpy (subsection + 6, ".Mod", sublen - 6); + SetSection (section, true); + ClearCurrentSection (); + C_ArchiveCVars (this, CVAR_MOD|CVAR_ARCHIVE|CVAR_AUTO|CVAR_USERINFO); + } strncpy (subsection, "ConsoleVariables", sublen); SetSection (section, true); ClearCurrentSection (); - C_ArchiveCVars (this, 0); + C_ArchiveCVars (this, CVAR_ARCHIVE); - strncpy (subsection, netgame ? "NetServerInfo" : "LocalServerInfo", sublen); + // Do not overwrite the serverinfo section if playing a netgame, and + // this machine was not the initial host. if (!netgame || consoleplayer == 0) - { // Do not overwrite this section if playing a netgame, and - // this machine was not the initial host. + { + strncpy (subsection, netgame ? "NetServerInfo" : "LocalServerInfo", sublen); SetSection (section, true); ClearCurrentSection (); - C_ArchiveCVars (this, 5); + C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_SERVERINFO); + + if (bModSetup) + { + strncpy (subsection, netgame ? "NetServerInfo.Mod" : "LocalServerInfo.Mod", sublen); + SetSection (section, true); + ClearCurrentSection (); + C_ArchiveCVars (this, CVAR_MOD|CVAR_ARCHIVE|CVAR_AUTO|CVAR_SERVERINFO); + } } strncpy (subsection, "UnknownConsoleVariables", sublen); SetSection (section, true); ClearCurrentSection (); - C_ArchiveCVars (this, 2); + C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_AUTO); strncpy (subsection, "ConsoleAliases", sublen); SetSection (section, true); @@ -532,11 +578,11 @@ void FGameConfigFile::ArchiveGlobalData () SetSection ("GlobalSettings", true); ClearCurrentSection (); - C_ArchiveCVars (this, 1); + C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); SetSection ("GlobalSettings.Unknown", true); ClearCurrentSection (); - C_ArchiveCVars (this, 3); + C_ArchiveCVars (this, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_AUTO); } FString FGameConfigFile::GetConfigPath (bool tryProg) diff --git a/src/gameconfigfile.h b/src/gameconfigfile.h index 440d900da..74376b583 100644 --- a/src/gameconfigfile.h +++ b/src/gameconfigfile.h @@ -47,6 +47,7 @@ public: void DoGlobalSetup (); void DoGameSetup (const char *gamename); + void DoModSetup (const char *gamename); void ArchiveGlobalData (); void ArchiveGameData (const char *gamename); void AddAutoexec (DArgs *list, const char *gamename); @@ -65,6 +66,7 @@ private: void ReadCVars (DWORD flags); bool bMigrating; + bool bModSetup; char section[64]; char *subsection; diff --git a/src/gi.cpp b/src/gi.cpp index 0e010724a..2df1fdde0 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -338,6 +338,7 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_FONT(mStatscreenEnteringFont, "statscreen_enteringfont") GAMEINFOKEY_PATCH(mStatscreenFinishedFont, "statscreen_finishedpatch") GAMEINFOKEY_PATCH(mStatscreenEnteringFont, "statscreen_enteringpatch") + GAMEINFOKEY_BOOL(norandomplayerclass, "norandomplayerclass") else { diff --git a/src/gi.h b/src/gi.h index 647db1a9b..ff678df07 100644 --- a/src/gi.h +++ b/src/gi.h @@ -139,6 +139,7 @@ struct gameinfo_t FGIFont mStatscreenMapNameFont; FGIFont mStatscreenFinishedFont; FGIFont mStatscreenEnteringFont; + bool norandomplayerclass; const char *GetFinalePage(unsigned int num) const; }; diff --git a/src/hu_scores.cpp b/src/hu_scores.cpp index 55d657b7c..afbb7a35d 100644 --- a/src/hu_scores.cpp +++ b/src/hu_scores.cpp @@ -94,7 +94,7 @@ static int STACK_ARGS comparepoints (const void *arg1, const void *arg2) diff = deathmatch ? p2->fragcount - p1->fragcount : p2->killcount - p1->killcount; if (diff == 0) { - diff = stricmp (p1->userinfo.netname, p2->userinfo.netname); + diff = stricmp(p1->userinfo.GetName(), p2->userinfo.GetName()); } return diff; } @@ -106,13 +106,13 @@ static int STACK_ARGS compareteams (const void *arg1, const void *arg2) player_t *p2 = *(player_t **)arg2; int diff; - diff = p1->userinfo.team - p2->userinfo.team; + diff = p1->userinfo.GetTeam() - p2->userinfo.GetTeam(); if (diff == 0) { diff = p2->fragcount - p1->fragcount; if (diff == 0) { - diff = stricmp (p1->userinfo.netname, p2->userinfo.netname); + diff = stricmp (p1->userinfo.GetName(), p2->userinfo.GetName()); } } return diff; @@ -168,7 +168,7 @@ void HU_DrawScores (player_t *player) HU_DoDrawScores (player, sortedplayers); - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } //========================================================================== @@ -189,7 +189,7 @@ void HU_GetPlayerWidths(int &maxnamewidth, int &maxscorewidth, int &maxiconheigh { if (playeringame[i]) { - int width = SmallFont->StringWidth(players[i].userinfo.netname); + int width = SmallFont->StringWidth(players[i].userinfo.GetName()); if (width > maxnamewidth) { maxnamewidth = width; @@ -268,14 +268,14 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER for (i = 0; i < MAXPLAYERS; ++i) { - if (playeringame[sortedplayers[i]-players] && TeamLibrary.IsValidTeam (sortedplayers[i]->userinfo.team)) + if (playeringame[sortedplayers[i]-players] && TeamLibrary.IsValidTeam (sortedplayers[i]->userinfo.GetTeam())) { - if (Teams[sortedplayers[i]->userinfo.team].m_iPlayerCount++ == 0) + if (Teams[sortedplayers[i]->userinfo.GetTeam()].m_iPlayerCount++ == 0) { numTeams++; } - Teams[sortedplayers[i]->userinfo.team].m_iScore += sortedplayers[i]->fragcount; + Teams[sortedplayers[i]->userinfo.GetTeam()].m_iScore += sortedplayers[i]->fragcount; } } @@ -413,12 +413,12 @@ static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, TAG_DONE); } - screen->DrawText (SmallFont, color, col4, y + ypadding, player->userinfo.netname, + screen->DrawText (SmallFont, color, col4, y + ypadding, player->userinfo.GetName(), DTA_CleanNoMove, true, TAG_DONE); - if (teamplay && Teams[player->userinfo.team].GetLogo ().IsNotEmpty ()) + if (teamplay && Teams[player->userinfo.GetTeam()].GetLogo().IsNotEmpty ()) { - FTexture *pic = TexMan[Teams[player->userinfo.team].GetLogo ().GetChars ()]; + FTexture *pic = TexMan[Teams[player->userinfo.GetTeam()].GetLogo().GetChars ()]; screen->DrawTexture (pic, col1 - (pic->GetScaledWidth() + 2) * CleanXfac, y, DTA_CleanNoMove, true, TAG_DONE); } @@ -453,8 +453,8 @@ int HU_GetRowColor(player_t *player, bool highlight) { if (teamplay && deathmatch) { - if (TeamLibrary.IsValidTeam (player->userinfo.team)) - return Teams[player->userinfo.team].GetTextColor(); + if (TeamLibrary.IsValidTeam (player->userinfo.GetTeam())) + return Teams[player->userinfo.GetTeam()].GetTextColor(); else return CR_GREY; } diff --git a/src/i_net.cpp b/src/i_net.cpp index 57543b29d..2c01f407d 100644 --- a/src/i_net.cpp +++ b/src/i_net.cpp @@ -272,12 +272,12 @@ void PacketGet (void) if (StartScreen != NULL) { StartScreen->NetMessage ("The connection from %s was dropped.\n", - players[sendplayer[node]].userinfo.netname); + players[sendplayer[node]].userinfo.GetName()); } else { Printf("The connection from %s was dropped.\n", - players[sendplayer[node]].userinfo.netname); + players[sendplayer[node]].userinfo.GetName()); } doomcom.data[0] = 0x80; // NCMD_EXIT diff --git a/src/info.cpp b/src/info.cpp index 244c9c68b..33461bb77 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -57,7 +57,7 @@ extern void InitBotStuff(); extern void ClearStrifeTypes(); TArray PClassActor::AllActorClasses; -FRandom FState::pr_statetics; +FRandom FState::pr_statetics("StateTics"); cycle_t ActionCycles; @@ -168,7 +168,7 @@ void PClassActor::StaticInit() void PClassActor::StaticSetActorNums() { - memset(SpawnableThings, 0, sizeof(SpawnableThings)); + SpawnableThings.Clear(); DoomEdMap.Empty(); for (unsigned int i = 0; i < PClassActor::AllActorClasses.Size(); ++i) @@ -360,7 +360,7 @@ void PClassActor::RegisterIDs() if (GameFilter == GAME_Any || (GameFilter & gameinfo.gametype)) { - if (SpawnID != 0) + if (SpawnID > 0) { SpawnableThings[SpawnID] = cls; if (cls != this) diff --git a/src/info.h b/src/info.h index 897cac27a..afed14f1f 100644 --- a/src/info.h +++ b/src/info.h @@ -77,6 +77,7 @@ struct FState BYTE Fullbright:1; // State is fullbright BYTE SameFrame:1; // Ignore Frame (except when spawning actor) BYTE Fast:1; + BYTE NoDelay:1; // Spawn states executes its action normally inline int GetFrame() const { @@ -110,6 +111,10 @@ struct FState { return NextState; } + inline bool GetNoDelay() const + { + return NoDelay; + } inline void SetFrame(BYTE frame) { Frame = frame - 'A'; diff --git a/src/intermission/intermission.cpp b/src/intermission/intermission.cpp index 0f64124dc..9587711ab 100644 --- a/src/intermission/intermission.cpp +++ b/src/intermission/intermission.cpp @@ -328,15 +328,42 @@ void DIntermissionScreenText::Drawer () size_t count; int c; const FRemapTable *range; - - // draw some of the text onto the screen - int rowheight = SmallFont->GetHeight () + (gameinfo.gametype & (GAME_DoomStrifeChex) ? 3 : -1); - bool scale = (CleanXfac != 1 || CleanYfac != 1); - - int cx = mTextX; - int cy = mTextY; const char *ch = mText; + // Count number of rows in this text. Since it does not word-wrap, we just count + // line feed characters. + int numrows; + + for (numrows = 1, c = 0; ch[c] != '\0'; ++c) + { + numrows += (ch[c] == '\n'); + } + + int rowheight = SmallFont->GetHeight() * CleanYfac; + int rowpadding = (gameinfo.gametype & (GAME_DoomStrifeChex) ? 3 : -1) * CleanYfac; + + int cx = (mTextX - 160)*CleanXfac + screen->GetWidth() / 2; + int cy = (mTextY - 100)*CleanYfac + screen->GetHeight() / 2; + int startx = cx; + + // Does this text fall off the end of the screen? If so, try to eliminate some margins first. + while (rowpadding > 0 && cy + numrows * (rowheight + rowpadding) - rowpadding > screen->GetHeight()) + { + rowpadding--; + } + // If it's still off the bottom, try to center it vertically. + if (cy + numrows * (rowheight + rowpadding) - rowpadding > screen->GetHeight()) + { + cy = (screen->GetHeight() - (numrows * (rowheight + rowpadding) - rowpadding)) / 2; + // If it's off the top now, you're screwed. It's too tall to fit. + if (cy < 0) + { + cy = 0; + } + } + rowheight += rowpadding; + + // draw some of the text onto the screen count = (mTicker - mTextDelay) / mTextSpeed; range = SmallFont->GetColorTranslation (mTextColor); @@ -347,33 +374,23 @@ void DIntermissionScreenText::Drawer () break; if (c == '\n') { - cx = mTextX; + cx = startx; cy += rowheight; continue; } pic = SmallFont->GetChar (c, &w); - if (cx+w > SCREENWIDTH) + w *= CleanXfac; + if (cx + w > SCREENWIDTH) continue; if (pic != NULL) { - if (scale) - { - screen->DrawTexture (pic, - cx,// + 320 / 2, - cy,// + 200 / 2, - DTA_Translation, range, - DTA_Clean, true, - TAG_DONE); - } - else - { - screen->DrawTexture (pic, - cx,// + 320 / 2, - cy,// + 200 / 2, - DTA_Translation, range, - TAG_DONE); - } + screen->DrawTexture (pic, + cx, + cy, + DTA_Translation, range, + DTA_CleanNoMove, true, + TAG_DONE); } cx += w; } @@ -572,18 +589,22 @@ void DIntermissionScreenCast::Drawer () // draw the current frame in the middle of the screen if (caststate != NULL) { - int castsprite; + int castsprite = caststate->sprite; if (!(mDefaults->flags4 & MF4_NOSKIN) && mDefaults->SpawnState != NULL && caststate->sprite == mDefaults->SpawnState->sprite && mClass->IsDescendantOf(RUNTIME_CLASS(APlayerPawn)) && skins != NULL) { - castsprite = skins[players[consoleplayer].userinfo.skin].sprite; - } - else - { - castsprite = caststate->sprite; + // Only use the skin sprite if this class has not been removed from the + // PlayerClasses list. + for (unsigned i = 0; i < PlayerClasses.Size(); ++i) + { + if (PlayerClasses[i].Type == mClass) + { + castsprite = skins[players[consoleplayer].userinfo.GetSkin()].sprite; + } + } } sprframe = &SpriteFrames[sprites[castsprite].spriteframes + caststate->GetFrame()]; diff --git a/src/intermission/intermission_parse.cpp b/src/intermission/intermission_parse.cpp index d621d4c6c..6166e608f 100644 --- a/src/intermission/intermission_parse.cpp +++ b/src/intermission/intermission_parse.cpp @@ -660,7 +660,7 @@ FName FMapInfoParser::ParseEndGame() } } FIntermissionDescriptor *desc = new FIntermissionDescriptor; - FIntermissionAction *action; + FIntermissionAction *action = NULL; switch (newSeq.EndType) { @@ -699,15 +699,23 @@ FName FMapInfoParser::ParseEndGame() break; } - action->mBackground = newSeq.PicName; - action->mMusic = newSeq.Music; - action->mMusicLooping = newSeq.MusicLooping; - desc->mActions.Push(action); + if (action == NULL) + { + sc.ScriptError("Endgame type was not defined"); + return NAME_None; // We won't really get here. + } + else + { + action->mBackground = newSeq.PicName; + action->mMusic = newSeq.Music; + action->mMusicLooping = newSeq.MusicLooping; + desc->mActions.Push(action); - FString seq; - seq.Format("@EndSequence_%d_", generated++); - ReplaceIntermission(seq, desc); - return FName(seq); + FString seq; + seq.Format("@EndSequence_%d_", generated++); + ReplaceIntermission(seq, desc); + return FName(seq); + } } //========================================================================== diff --git a/src/m_bbox.h b/src/m_bbox.h index febf9d2a2..256393d60 100644 --- a/src/m_bbox.h +++ b/src/m_bbox.h @@ -57,6 +57,15 @@ public: m_Box[BOXBOTTOM] = m_Box[BOXLEFT] = FIXED_MAX; } + // Returns a bounding box that encloses both bounding boxes + FBoundingBox operator | (const FBoundingBox &box2) const + { + return FBoundingBox(m_Box[BOXLEFT] < box2.m_Box[BOXLEFT] ? m_Box[BOXLEFT] : box2.m_Box[BOXLEFT], + m_Box[BOXBOTTOM] < box2.m_Box[BOXBOTTOM] ? m_Box[BOXBOTTOM] : box2.m_Box[BOXBOTTOM], + m_Box[BOXRIGHT] > box2.m_Box[BOXRIGHT] ? m_Box[BOXRIGHT] : box2.m_Box[BOXRIGHT], + m_Box[BOXTOP] > box2.m_Box[BOXTOP] ? m_Box[BOXTOP] : box2.m_Box[BOXTOP]); + } + void AddToBox (fixed_t x, fixed_t y); inline fixed_t Top () const { return m_Box[BOXTOP]; } diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 18be0ad6a..6a020895c 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -88,7 +88,7 @@ void cht_DoCheat (player_t *player, int cheat) msg = GStrings("STSTR_DQDON"); else msg = GStrings("STSTR_DQDOFF"); - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); break; case CHT_BUDDHA: @@ -310,8 +310,10 @@ void cht_DoCheat (player_t *player, int cheat) player->mo->flags3 = player->mo->GetDefault()->flags3; player->mo->flags4 = player->mo->GetDefault()->flags4; player->mo->flags5 = player->mo->GetDefault()->flags5; + player->mo->flags6 = player->mo->GetDefault()->flags6; player->mo->renderflags &= ~RF_INVISIBLE; player->mo->height = player->mo->GetDefault()->height; + player->mo->radius = player->mo->GetDefault()->radius; player->mo->special1 = 0; // required for the Hexen fighter's fist attack. // This gets set by AActor::Die as flag for the wimpy death and must be reset here. player->mo->SetState (player->mo->SpawnState); @@ -520,7 +522,7 @@ void cht_DoCheat (player_t *player, int cheat) if (player == &players[consoleplayer]) Printf ("%s\n", msg); else if (cheat != CHT_CHASECAM) - Printf ("%s cheats: %s\n", player->userinfo.netname, msg); + Printf ("%s cheats: %s\n", player->userinfo.GetName(), msg); } const char *cht_Morph (player_t *player, PClassPlayerPawn *morphclass, bool quickundo) @@ -601,7 +603,7 @@ void cht_Give (player_t *player, const char *name, int amount) PClassActor *type; if (player != &players[consoleplayer]) - Printf ("%s is a cheater: give %s\n", player->userinfo.netname, name); + Printf ("%s is a cheater: give %s\n", player->userinfo.GetName(), name); if (player->mo == NULL || player->health <= 0) { diff --git a/src/m_png.cpp b/src/m_png.cpp index dd2e46970..2bec48103 100644 --- a/src/m_png.cpp +++ b/src/m_png.cpp @@ -103,7 +103,7 @@ static inline void MakeChunk (void *where, DWORD type, size_t len); static inline void StuffPalette (const PalEntry *from, BYTE *to); static bool WriteIDAT (FILE *file, const BYTE *data, int len); static void UnfilterRow (int width, BYTE *dest, BYTE *stream, BYTE *prev, int bpp); -static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout); +static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout, bool grayscale); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -604,7 +604,7 @@ bool M_ReadIDAT (FileReader *file, BYTE *buffer, int width, int height, int pitc in = prev; if (bitdepth < 8) { - UnpackPixels (passwidth, bytesPerRowIn, bitdepth, in, adam7buff[2]); + UnpackPixels (passwidth, bytesPerRowIn, bitdepth, in, adam7buff[2], colortype == 0); in = adam7buff[2]; } // Distribute pixels into the output buffer @@ -688,7 +688,7 @@ bool M_ReadIDAT (FileReader *file, BYTE *buffer, int width, int height, int pitc passpitch = pitch << interlace; for (curr = buffer + pitch * interlace; curr <= prev; curr += passpitch) { - UnpackPixels (width, bytesPerRowIn, bitdepth, curr, curr); + UnpackPixels (width, bytesPerRowIn, bitdepth, curr, curr, colortype == 0); } } return true; @@ -1155,13 +1155,15 @@ void UnfilterRow (int width, BYTE *dest, BYTE *row, BYTE *prev, int bpp) // //========================================================================== -static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout) +static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout, bool grayscale) { const BYTE *in; BYTE *out; BYTE pack; int lastbyte; + assert(bitdepth == 1 || bitdepth == 2 || bitdepth == 4); + out = rowout + width; in = rowin + bytesPerRow; @@ -1242,4 +1244,43 @@ static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE * } break; } + + // Expand grayscale to 8bpp + if (grayscale) + { + // Put the 2-bit lookup table on the stack, since it's probably already + // in a cache line. + union + { + uint32 bits2l; + BYTE bits2[4]; + }; + + out = rowout + width; + switch (bitdepth) + { + case 1: + while (--out >= rowout) + { + // 1 becomes -1 (0xFF), and 0 remains untouched. + *out = 0 - *out; + } + break; + + case 2: + bits2l = MAKE_ID(0x00,0x55,0xAA,0xFF); + while (--out >= rowout) + { + *out = bits2[*out]; + } + break; + + case 4: + while (--out >= rowout) + { + *out |= (*out << 4); + } + break; + } + } } diff --git a/src/m_random.cpp b/src/m_random.cpp index 436b803bc..763410111 100644 --- a/src/m_random.cpp +++ b/src/m_random.cpp @@ -86,9 +86,8 @@ extern FRandom pr_spawnmobj; extern FRandom pr_acs; extern FRandom pr_chase; -extern FRandom pr_lost; -extern FRandom pr_slam; extern FRandom pr_exrandom; +extern FRandom pr_damagemobj; // PUBLIC DATA DEFINITIONS ------------------------------------------------- @@ -245,8 +244,7 @@ DWORD FRandom::StaticSumSeeds () pr_spawnmobj.sfmt.u[0] + pr_spawnmobj.idx + pr_acs.sfmt.u[0] + pr_acs.idx + pr_chase.sfmt.u[0] + pr_chase.idx + - pr_lost.sfmt.u[0] + pr_lost.idx + - pr_slam.sfmt.u[0] + pr_slam.idx; + pr_damagemobj.sfmt.u[0] + pr_damagemobj.idx; } //========================================================================== diff --git a/src/menu/joystickmenu.cpp b/src/menu/joystickmenu.cpp index 19f87e158..6be728865 100644 --- a/src/menu/joystickmenu.cpp +++ b/src/menu/joystickmenu.cpp @@ -280,15 +280,16 @@ FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy) delete opt->mItems[i]; opt->mItems.Clear(); } - opt->mTitle.Format("Configure %s", joy->GetName().GetChars()); - if (joy == NULL) { + opt->mTitle = "Configure Controller"; it = new FOptionMenuItemStaticText("Invalid controller specified for menu", false); opt->mItems.Push(it); } else { + opt->mTitle.Format("Configure %s", joy->GetName().GetChars()); + SELECTED_JOYSTICK = joy; it = new FOptionMenuSliderJoySensitivity("Overall sensitivity", 0, 2, 0.1, 3); diff --git a/src/menu/listmenu.cpp b/src/menu/listmenu.cpp index 5e9fc13d1..53b4fee22 100644 --- a/src/menu/listmenu.cpp +++ b/src/menu/listmenu.cpp @@ -495,12 +495,13 @@ bool FListMenuItemSelectable::MouseEvent(int type, int x, int y) // //============================================================================= -FListMenuItemText::FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, FName child, int param) +FListMenuItemText::FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, EColorRange color2, FName child, int param) : FListMenuItemSelectable(x, y, height, child, param) { mText = ncopystring(text); mFont = font; mColor = color; + mColorSelected = color2; mHotkey = hotkey; } @@ -518,7 +519,7 @@ void FListMenuItemText::Drawer(bool selected) if (text != NULL) { if (*text == '$') text = GStrings(text+1); - screen->DrawText(mFont, mColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + screen->DrawText(mFont, selected ? mColorSelected : mColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); } } diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp index 78bdadf59..99ac59938 100644 --- a/src/menu/loadsavemenu.cpp +++ b/src/menu/loadsavemenu.cpp @@ -961,7 +961,7 @@ void DSaveMenu::DoSave (FSaveGameNode *node) G_SaveGame (filename, savegamestring); } M_ClearMenus(); - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } //============================================================================= @@ -1100,7 +1100,7 @@ bool DLoadMenu::MenuEvent (int mkey, bool fromcontroller) quickSaveSlot = SaveGames[Selected]; } M_ClearMenus(); - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); LastAccessed = Selected; return true; } diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 327632e55..0e36ef073 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -409,7 +409,7 @@ void M_SetMenu(FName menu, int param) FMenuDescriptor **desc = MenuDescriptors.CheckKey(menu); if (desc != NULL) { - if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame) + if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame && !demoplayback) { M_StartMessage((*desc)->mNetgameMessage, 1); return; @@ -734,7 +734,7 @@ void M_ClearMenus () DMenu::CurrentMenu->Destroy(); DMenu::CurrentMenu = NULL; } - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); menuactive = MENU_Off; } @@ -957,5 +957,6 @@ CCMD(reset2saved) { GameConfig->DoGlobalSetup (); GameConfig->DoGameSetup (gameinfo.ConfigName); + GameConfig->DoModSetup (gameinfo.ConfigName); R_SetViewSize (screenblocks); } diff --git a/src/menu/menu.h b/src/menu/menu.h index 4ab3d2e10..1388a33bb 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -393,8 +393,9 @@ class FListMenuItemText : public FListMenuItemSelectable const char *mText; FFont *mFont; EColorRange mColor; + EColorRange mColorSelected; public: - FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, FName child, int param = 0); + FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, EColorRange color2, FName child, int param = 0); ~FListMenuItemText(); void Drawer(bool selected); int GetWidth(); diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index 98b1dc34b..00af179a5 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -324,7 +324,7 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) param = sc.Number; } - FListMenuItem *it = new FListMenuItemText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mFont, desc->mFontColor, action, param); + FListMenuItem *it = new FListMenuItemText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mFont, desc->mFontColor, desc->mFontColor2, action, param); desc->mItems.Push(it); desc->mYpos += desc->mLinespacing; if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; @@ -976,7 +976,7 @@ static void BuildEpisodeMenu() else { it = new FListMenuItemText(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, - AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, NAME_Skillmenu, i); + AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, ld->mFontColor2, NAME_Skillmenu, i); } ld->mItems.Push(it); posy += ld->mLinespacing; @@ -1065,7 +1065,7 @@ static void BuildPlayerclassMenu() { // create a dummy item that auto-chooses the default class. FListMenuItemText *it = new FListMenuItemText(0, 0, 0, 'p', "player", - ld->mFont,ld->mFontColor, NAME_Episodemenu, -1000); + ld->mFont,ld->mFontColor, ld->mFontColor2, NAME_Episodemenu, -1000); ld->mAutoselect = ld->mItems.Push(it); success = true; } @@ -1091,17 +1091,17 @@ static void BuildPlayerclassMenu() if (pname != NULL) { FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, - pname, ld->mFont,ld->mFontColor, NAME_Episodemenu, i); + pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, i); ld->mItems.Push(it); ld->mYpos += ld->mLinespacing; n++; } } } - if (n > 1) + if (n > 1 && !gameinfo.norandomplayerclass) { FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, 'r', - "$MNU_RANDOM", ld->mFont,ld->mFontColor, NAME_Episodemenu, -1); + "$MNU_RANDOM", ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, -1); ld->mItems.Push(it); } if (n == 0) @@ -1110,7 +1110,7 @@ static void BuildPlayerclassMenu() if (pname != NULL) { FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, - pname, ld->mFont,ld->mFontColor, NAME_Episodemenu, 0); + pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, 0); ld->mItems.Push(it); } } @@ -1135,7 +1135,7 @@ static void BuildPlayerclassMenu() od->mScrollTop = 0; od->mIndent = 160; od->mDontDim = false; - od->mNetgameMessage = "$NETGAME"; + od->mNetgameMessage = "$NEWGAME"; for (unsigned i = 0; i < PlayerClasses.Size (); i++) { @@ -1369,7 +1369,7 @@ void M_StartupSkillMenu(FGameStartup *gs) EColorRange color = (EColorRange)skill.GetTextColor(); if (color == CR_UNTRANSLATED) color = ld->mFontColor; li = new FListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut, - pItemText? *pItemText : skill.MenuName, ld->mFont, color, action, i); + pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, i); } ld->mItems.Push(li); y += ld->mLinespacing; diff --git a/src/menu/messagebox.cpp b/src/menu/messagebox.cpp index e7b6abcef..b94daae0a 100644 --- a/src/menu/messagebox.cpp +++ b/src/menu/messagebox.cpp @@ -179,8 +179,8 @@ void DMessageBoxMenu::Drawer () PalEntry fade = 0; int fontheight = SmallFont->GetHeight(); - //BorderNeedRefresh = screen->GetPageCount (); - //SB_state = screen->GetPageCount (); + //V_SetBorderNeedRefresh(); + //ST_SetNeedRefresh(); y = 100; diff --git a/src/menu/playerdisplay.cpp b/src/menu/playerdisplay.cpp index 7ac4bdf31..30e320de9 100644 --- a/src/menu/playerdisplay.cpp +++ b/src/menu/playerdisplay.cpp @@ -406,9 +406,9 @@ void FListMenuItemPlayerDisplay::UpdateRandomClass() void FListMenuItemPlayerDisplay::UpdateTranslation() { - int PlayerColor = players[consoleplayer].userinfo.color; - int PlayerSkin = players[consoleplayer].userinfo.skin; - int PlayerColorset = players[consoleplayer].userinfo.colorset; + int PlayerColor = players[consoleplayer].userinfo.GetColor(); + int PlayerSkin = players[consoleplayer].userinfo.GetSkin(); + int PlayerColorset = players[consoleplayer].userinfo.GetColorSet(); if (mPlayerClass != NULL) { @@ -562,7 +562,7 @@ void FListMenuItemPlayerDisplay::Drawer(bool selected) V_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); - spriteframe_t *sprframe; + spriteframe_t *sprframe = NULL; fixed_t scaleX, scaleY; if (mPlayerState != NULL) diff --git a/src/menu/playermenu.cpp b/src/menu/playermenu.cpp index daadda910..84748785c 100644 --- a/src/menu/playermenu.cpp +++ b/src/menu/playermenu.cpp @@ -545,11 +545,11 @@ void DPlayerMenu::Init(DMenu *parent, FListMenuDescriptor *desc) li->SetValue(FListMenuItemPlayerDisplay::PDF_ROTATION, 0); li->SetValue(FListMenuItemPlayerDisplay::PDF_MODE, 1); li->SetValue(FListMenuItemPlayerDisplay::PDF_TRANSLATE, 1); - li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.PlayerClass); + li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.GetPlayerClassNum()); if (PlayerClass != NULL && !(GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN) && - players[consoleplayer].userinfo.PlayerClass != -1) + players[consoleplayer].userinfo.GetPlayerClassNum() != -1) { - li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, players[consoleplayer].userinfo.skin); + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, players[consoleplayer].userinfo.GetSkin()); } } @@ -570,8 +570,8 @@ void DPlayerMenu::Init(DMenu *parent, FListMenuDescriptor *desc) li->SetValue(0, team == TEAM_NONE? 0 : team + 1); } - int mycolorset = players[consoleplayer].userinfo.colorset; - int color = players[consoleplayer].userinfo.color; + int mycolorset = players[consoleplayer].userinfo.GetColorSet(); + int color = players[consoleplayer].userinfo.GetColor(); UpdateColorsets(); @@ -606,13 +606,16 @@ void DPlayerMenu::Init(DMenu *parent, FListMenuDescriptor *desc) } else { - li->SetString(0, "Random"); + // [XA] Remove the "Random" option if the relevant gameinfo flag is set. + if(!gameinfo.norandomplayerclass) + li->SetString(0, "Random"); for(unsigned i=0; i< PlayerClasses.Size(); i++) { const char *cls = GetPrintableDisplayName(PlayerClasses[i].Type); - li->SetString(i+1, cls); + li->SetString(gameinfo.norandomplayerclass ? i : i+1, cls); } - li->SetValue(0, players[consoleplayer].userinfo.PlayerClass + 1); + int pclass = players[consoleplayer].userinfo.GetPlayerClassNum(); + li->SetValue(0, gameinfo.norandomplayerclass && pclass >= 0 ? pclass : pclass + 1); } } @@ -621,7 +624,7 @@ void DPlayerMenu::Init(DMenu *parent, FListMenuDescriptor *desc) li = GetItem(NAME_Gender); if (li != NULL) { - li->SetValue(0, players[consoleplayer].userinfo.gender); + li->SetValue(0, players[consoleplayer].userinfo.GetGender()); } li = GetItem(NAME_Autoaim); @@ -683,9 +686,9 @@ bool DPlayerMenu::Responder (event_t *ev) void DPlayerMenu::UpdateTranslation() { - int PlayerColor = players[consoleplayer].userinfo.color; - int PlayerSkin = players[consoleplayer].userinfo.skin; - int PlayerColorset = players[consoleplayer].userinfo.colorset; + int PlayerColor = players[consoleplayer].userinfo.GetColor(); + int PlayerSkin = players[consoleplayer].userinfo.GetSkin(); + int PlayerColorset = players[consoleplayer].userinfo.GetColorSet(); if (PlayerClass != NULL) { @@ -718,7 +721,7 @@ void DPlayerMenu::PickPlayerClass() // [GRB] Pick a class from player class list if (PlayerClasses.Size () > 1) { - pclass = players[consoleplayer].userinfo.PlayerClass; + pclass = players[consoleplayer].userinfo.GetPlayerClassNum(); if (pclass < 0) { @@ -741,7 +744,7 @@ void DPlayerMenu::SendNewColor (int red, int green, int blue) { char command[24]; - players[consoleplayer].userinfo.color = MAKERGB(red, green, blue); + players[consoleplayer].userinfo.ColorChanged(MAKERGB(red,green,blue)); mysnprintf (command, countof(command), "color \"%02x %02x %02x\"", red, green, blue); C_DoCommand (command); UpdateTranslation(); @@ -766,7 +769,7 @@ void DPlayerMenu::UpdateColorsets() FPlayerColorSet *colorset = PlayerClass->Type->GetColorSet(PlayerColorSets[i]); li->SetString(i+1, colorset->Name); } - int mycolorset = players[consoleplayer].userinfo.colorset; + int mycolorset = players[consoleplayer].userinfo.GetColorSet(); if (mycolorset != -1) { for(unsigned i=0;iType)->flags4 & MF4_NOSKIN || - players[consoleplayer].userinfo.PlayerClass == -1) + players[consoleplayer].userinfo.GetPlayerClassNum() == -1) { li->SetString(0, "Base"); li->SetValue(0, 0); @@ -810,7 +813,7 @@ void DPlayerMenu::UpdateSkins() { int j = PlayerSkins.Push(i); li->SetString(j, skins[i].name); - if (players[consoleplayer].userinfo.skin == i) + if (players[consoleplayer].userinfo.GetSkin() == i) { sel = j; } @@ -882,7 +885,7 @@ void DPlayerMenu::ColorSetChanged (FListMenuItem *li) if (blue != NULL) blue->Enable(mycolorset == -1); char command[24]; - players[consoleplayer].userinfo.colorset = mycolorset; + players[consoleplayer].userinfo.ColorSetChanged(mycolorset); mysnprintf(command, countof(command), "colorset %d", mycolorset); C_DoCommand(command); UpdateTranslation(); @@ -906,10 +909,10 @@ void DPlayerMenu::ClassChanged (FListMenuItem *li) if (li->GetValue(0, &sel)) { - players[consoleplayer].userinfo.PlayerClass = sel-1; + players[consoleplayer].userinfo.PlayerClassNumChanged(gameinfo.norandomplayerclass ? sel : sel-1); PickPlayerClass(); - cvar_set ("playerclass", sel == 0 ? "Random" : PlayerClass->Type->DisplayName.GetChars()); + cvar_set ("playerclass", sel == 0 && !gameinfo.norandomplayerclass ? "Random" : PlayerClass->Type->DisplayName.GetChars()); UpdateSkins(); UpdateColorsets(); @@ -918,7 +921,7 @@ void DPlayerMenu::ClassChanged (FListMenuItem *li) li = GetItem(NAME_Playerdisplay); if (li != NULL) { - li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.PlayerClass); + li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.GetPlayerClassNum()); } } } @@ -932,7 +935,7 @@ void DPlayerMenu::ClassChanged (FListMenuItem *li) void DPlayerMenu::SkinChanged (FListMenuItem *li) { if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || - players[consoleplayer].userinfo.PlayerClass == -1) + players[consoleplayer].userinfo.GetPlayerClassNum() == -1) { return; } @@ -942,7 +945,7 @@ void DPlayerMenu::SkinChanged (FListMenuItem *li) if (li->GetValue(0, &sel)) { sel = PlayerSkins[sel]; - players[consoleplayer].userinfo.skin = sel; + players[consoleplayer].userinfo.SkinNumChanged(sel); UpdateTranslation(); cvar_set ("skin", skins[sel].name); @@ -1009,7 +1012,7 @@ bool DPlayerMenu::MenuEvent (int mkey, bool fromcontroller) case NAME_Red: if (li->GetValue(0, &v)) { - int color = players[consoleplayer].userinfo.color; + uint32 color = players[consoleplayer].userinfo.GetColor(); SendNewColor (v, GPART(color), BPART(color)); } break; @@ -1017,7 +1020,7 @@ bool DPlayerMenu::MenuEvent (int mkey, bool fromcontroller) case NAME_Green: if (li->GetValue(0, &v)) { - int color = players[consoleplayer].userinfo.color; + uint32 color = players[consoleplayer].userinfo.GetColor(); SendNewColor (RPART(color), v, BPART(color)); } break; @@ -1025,7 +1028,7 @@ bool DPlayerMenu::MenuEvent (int mkey, bool fromcontroller) case NAME_Blue: if (li->GetValue(0, &v)) { - int color = players[consoleplayer].userinfo.color; + uint32 color = players[consoleplayer].userinfo.GetColor(); SendNewColor (RPART(color), GPART(color), v); } break; @@ -1088,7 +1091,7 @@ bool DPlayerMenu::MouseEvent(int type, int x, int y) case NAME_Red: if (li->GetValue(0, &v)) { - int color = players[consoleplayer].userinfo.color; + uint32 color = players[consoleplayer].userinfo.GetColor(); SendNewColor (v, GPART(color), BPART(color)); } break; @@ -1096,7 +1099,7 @@ bool DPlayerMenu::MouseEvent(int type, int x, int y) case NAME_Green: if (li->GetValue(0, &v)) { - int color = players[consoleplayer].userinfo.color; + uint32 color = players[consoleplayer].userinfo.GetColor(); SendNewColor (RPART(color), v, BPART(color)); } break; @@ -1104,7 +1107,7 @@ bool DPlayerMenu::MouseEvent(int type, int x, int y) case NAME_Blue: if (li->GetValue(0, &v)) { - int color = players[consoleplayer].userinfo.color; + uint32 color = players[consoleplayer].userinfo.GetColor(); SendNewColor (RPART(color), GPART(color), v); } break; diff --git a/src/name.cpp b/src/name.cpp index c0d14b51e..36cce2595 100644 --- a/src/name.cpp +++ b/src/name.cpp @@ -65,6 +65,7 @@ struct FName::NameManager::NameBlock // PRIVATE DATA DEFINITIONS ------------------------------------------------ FName::NameManager FName::NameData; +bool FName::NameManager::Inited; // Define the predefined names. static const char *PredefinedNames[] = diff --git a/src/name.h b/src/name.h index fa161ab2e..3e2eaf4bb 100644 --- a/src/name.h +++ b/src/name.h @@ -116,7 +116,7 @@ protected: int AddName (const char *text, unsigned int hash, unsigned int bucket); NameBlock *AddBlock (size_t len); void InitBuckets (); - bool Inited; + static bool Inited; }; static NameManager NameData; diff --git a/src/namedef.h b/src/namedef.h index 48d7a6a90..96bba6a3c 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -183,6 +183,13 @@ xx(Idle) xx(GenericFreezeDeath) xx(GenericCrush) +// Bounce state names +xx(Bounce) +xx(Wall) +xx(Floor) +xx(Ceiling) +xx(Creature) + // Compatible death names for the decorate parser. xx(XDeath) xx(Burn) @@ -223,6 +230,7 @@ xx(Rocket) xx(Plasma) xx(BFG) //xx(Railgun) +xx(Dagger) // Damage types //xx(Fire) already defined above @@ -301,6 +309,7 @@ xx(Arg2) xx(Arg3) xx(Arg4) xx(Arg0Str) +xx(Arg1Str) xx(Id) xx(V1) xx(V2) @@ -450,6 +459,7 @@ xx(scalex_bottom) xx(scaley_bottom) xx(light) xx(lightabsolute) +xx(lightfog) xx(nofakecontrast) xx(smoothlighting) xx(blockprojectiles) @@ -551,6 +561,13 @@ xx(Inter_Strife_MAP03) xx(Inter_Strife_MAP10) xx(Multiplayer) +// more stuff +xx(ColorSet) +xx(NeverSwitchOnPickup) +xx(MoveBob) +xx(StillBob) +xx(PlayerClass) + // Decorate compatibility functions xx(DecoRandom) xx(DecoFRandom) diff --git a/src/nodebuild.cpp b/src/nodebuild.cpp index 5ac860bef..c13527d87 100644 --- a/src/nodebuild.cpp +++ b/src/nodebuild.cpp @@ -243,12 +243,16 @@ void FNodeBuilder::CreateSubsectorsForReal () D(Printf (PRINT_LOG, "Output subsector %d:\n", Subsectors.Size())); for (unsigned int i = firstline; i < SegList.Size(); ++i) { - D(Printf (PRINT_LOG, " Seg %5d%c(%5d,%5d)-(%5d,%5d)\n", SegList[i].SegPtr - &Segs[0], + D(Printf (PRINT_LOG, " Seg %5d%c%d(%5d,%5d)-%d(%5d,%5d) [%08x,%08x]-[%08x,%08x]\n", SegList[i].SegPtr - &Segs[0], SegList[i].SegPtr->linedef == -1 ? '+' : ' ', + SegList[i].SegPtr->v1, Vertices[SegList[i].SegPtr->v1].x>>16, Vertices[SegList[i].SegPtr->v1].y>>16, + SegList[i].SegPtr->v2, Vertices[SegList[i].SegPtr->v2].x>>16, - Vertices[SegList[i].SegPtr->v2].y>>16)); + Vertices[SegList[i].SegPtr->v2].y>>16, + Vertices[SegList[i].SegPtr->v1].x, Vertices[SegList[i].SegPtr->v1].y, + Vertices[SegList[i].SegPtr->v2].x, Vertices[SegList[i].SegPtr->v2].y)); SegList[i].SegNum = DWORD(SegList[i].SegPtr - &Segs[0]); } Subsectors.Push (sub); @@ -330,7 +334,8 @@ bool FNodeBuilder::CheckSubsector (DWORD set, node_t &node, DWORD &splitseg) do { - D(Printf (PRINT_LOG, " - seg %d(%d,%d)-(%d,%d) line %d front %d back %d\n", seg, + D(Printf (PRINT_LOG, " - seg %d%c(%d,%d)-(%d,%d) line %d front %d back %d\n", seg, + Segs[seg].linedef == -1 ? '+' : ' ', Vertices[Segs[seg].v1].x>>16, Vertices[Segs[seg].v1].y>>16, Vertices[Segs[seg].v2].x>>16, Vertices[Segs[seg].v2].y>>16, Segs[seg].linedef, @@ -1111,12 +1116,14 @@ int ClassifyLineBackpatchC (node_t &node, const FSimpleVert *v1, const FSimpleVe #endif // printf ("Patching for SSE %d @ %p %d\n", SSELevel, calleroffset, *calleroffset); +#ifndef DISABLE_SSE if (CPU.bSSE2) { func = ClassifyLineSSE2; diff = int((char *)ClassifyLineSSE2 - (char *)calleroffset); } else +#endif { func = ClassifyLine2; diff = int((char *)ClassifyLine2 - (char *)calleroffset); diff --git a/src/nodebuild.h b/src/nodebuild.h index a6f0459a0..81c7b1345 100644 --- a/src/nodebuild.h +++ b/src/nodebuild.h @@ -34,6 +34,8 @@ public: FEvent *FindEvent (double distance) const; void DeleteAll (); + void PrintTree () const { PrintTree (Root); } + private: FEvent Nil; FEvent *Root; @@ -42,6 +44,8 @@ private: void DeletionTraverser (FEvent *event); FEvent *Successor (FEvent *event) const; FEvent *Predecessor (FEvent *event) const; + + void PrintTree (const FEvent *event) const; }; struct FSimpleVert diff --git a/src/nodebuild_events.cpp b/src/nodebuild_events.cpp index d7660f588..2b71e01c7 100644 --- a/src/nodebuild_events.cpp +++ b/src/nodebuild_events.cpp @@ -210,3 +210,17 @@ FEvent *FEventTree::GetMinimum () } return node; } + +void FEventTree::PrintTree (const FEvent *event) const +{ + // Use the CRT's sprintf so that it shares the same formatting as ZDBSP's output. + char buff[100]; + if (event != &Nil) + { + PrintTree(event->Left); + sprintf(buff, " Distance %g, vertex %d, seg %u\n", + sqrt(event->Distance/4294967296.0), event->Info.Vertex, (unsigned)event->Info.FrontSeg); + Printf(PRINT_LOG, "%s", buff); + PrintTree(event->Right); + } +} diff --git a/src/nodebuild_extract.cpp b/src/nodebuild_extract.cpp index 6260ac2f0..c0f769873 100644 --- a/src/nodebuild_extract.cpp +++ b/src/nodebuild_extract.cpp @@ -78,7 +78,8 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, memcpy (outNodes, &Nodes[0], nodeCount*sizeof(node_t)); for (i = 0; i < nodeCount; ++i) { - D(Printf(PRINT_LOG, "Node %d:\n", i)); + D(Printf(PRINT_LOG, "Node %d: Splitter[%08x,%08x] [%08x,%08x]\n", i, + outNodes[i].x, outNodes[i].y, outNodes[i].dx, outNodes[i].dy)); // Go backwards because on 64-bit systems, both of the intchildren are // inside the first in-game child. for (int j = 1; j >= 0; --j) @@ -296,12 +297,14 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t { seg = &Segs[SegList[j].SegNum]; angle_t ang = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy); - Printf(PRINT_LOG, "%d%c %5d(%5d,%5d)->%5d(%5d,%5d) - %3.3f %d,%d\n", j, + Printf(PRINT_LOG, "%d%c %5d(%5d,%5d)->%5d(%5d,%5d) - %3.5f %d,%d [%08x,%08x]-[%08x,%08x]\n", j, seg->linedef == -1 ? '+' : ':', seg->v1, Vertices[seg->v1].x>>16, Vertices[seg->v1].y>>16, seg->v2, Vertices[seg->v2].x>>16, Vertices[seg->v2].y>>16, double(ang/2)*180/(1<<30), - seg->planenum, seg->planefront); + seg->planenum, seg->planefront, + Vertices[seg->v1].x, Vertices[seg->v1].y, + Vertices[seg->v2].x, Vertices[seg->v2].y); } #endif @@ -395,12 +398,16 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t Printf(PRINT_LOG, "Output GL subsector %d:\n", subsector); for (i = segs.Size() - count; i < (int)segs.Size(); ++i) { - Printf(PRINT_LOG, " Seg %5d%c(%5d,%5d)-(%5d,%5d)\n", i, + Printf(PRINT_LOG, " Seg %5d%c(%5d,%5d)-(%5d,%5d) [%08x,%08x]-[%08x,%08x]\n", i, segs[i].linedef == NULL ? '+' : ' ', segs[i].v1->x>>16, segs[i].v1->y>>16, segs[i].v2->x>>16, - segs[i].v2->y>>16); + segs[i].v2->y>>16, + segs[i].v1->x, + segs[i].v1->y, + segs[i].v2->x, + segs[i].v2->y); } #endif diff --git a/src/nodebuild_gl.cpp b/src/nodebuild_gl.cpp index add4f7123..d029e7f69 100644 --- a/src/nodebuild_gl.cpp +++ b/src/nodebuild_gl.cpp @@ -83,6 +83,8 @@ double FNodeBuilder::AddIntersection (const node_t &node, int vertex) // seg information will be messed up in the generated tree. void FNodeBuilder::FixSplitSharers (const node_t &node) { + D(Printf(PRINT_LOG, "events:\n")); + D(Events.PrintTree()); for (unsigned int i = 0; i < SplitSharers.Size(); ++i) { DWORD seg = SplitSharers[i].Seg; @@ -95,6 +97,18 @@ void FNodeBuilder::FixSplitSharers (const node_t &node) continue; } + // Use the CRT's printf so the formatting matches ZDBSP's + D(char buff[200]); + D(sprintf(buff, "Considering events on seg %d(%d[%d,%d]->%d[%d,%d]) [%g:%g]\n", seg, + Segs[seg].v1, + Vertices[Segs[seg].v1].x>>16, + Vertices[Segs[seg].v1].y>>16, + Segs[seg].v2, + Vertices[Segs[seg].v2].x>>16, + Vertices[Segs[seg].v2].y>>16, + SplitSharers[i].Distance, event->Distance)); + D(Printf(PRINT_LOG, "%s", buff)); + if (SplitSharers[i].Forward) { event = Events.GetSuccessor (event); diff --git a/src/nodebuild_utility.cpp b/src/nodebuild_utility.cpp index b37533921..e7d5865d5 100644 --- a/src/nodebuild_utility.cpp +++ b/src/nodebuild_utility.cpp @@ -185,6 +185,9 @@ int FNodeBuilder::CreateSeg (int linenum, int sidenum) segnum = (int)Segs.Push (seg); Vertices[seg.v1].segs = segnum; Vertices[seg.v2].segs2 = segnum; + D(Printf(PRINT_LOG, "Seg %4d: From line %d, side %s (%5d,%5d)-(%5d,%5d) [%08x,%08x]-[%08x,%08x]\n", segnum, linenum, sidenum ? "back " : "front", + Vertices[seg.v1].x>>16, Vertices[seg.v1].y>>16, Vertices[seg.v2].x>>16, Vertices[seg.v2].y>>16, + Vertices[seg.v1].x, Vertices[seg.v1].y, Vertices[seg.v2].x, Vertices[seg.v2].y)); return segnum; } diff --git a/src/oplsynth/OPL3.cpp b/src/oplsynth/OPL3.cpp new file mode 100644 index 000000000..c4806a694 --- /dev/null +++ b/src/oplsynth/OPL3.cpp @@ -0,0 +1,1868 @@ +/* + * File: OPL3.java + * Software implementation of the Yamaha YMF262 sound generator. + * Copyright (C) 2008 Robson Cozendey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * One of the objectives of this emulator is to stimulate further research in the + * OPL3 chip emulation. There was an explicit effort in making no optimizations, + * and making the code as legible as possible, so that a new programmer + * interested in modify and improve upon it could do so more easily. + * This emulator's main body of information was taken from reverse engineering of + * the OPL3 chip, from the YMF262 Datasheet and from the OPL3 section in the + * YMF278b Application's Manual, + * together with the vibrato table information, eighth waveform parameter + * information and feedback averaging information provided in MAME's YMF262 and + * YM3812 emulators, by Jarek Burczynski and Tatsuyuki Satoh. + * This emulator has a high degree of accuracy, and most of music files sound + * almost identical, exception made in some games which uses specific parts of + * the rhythm section. In this respect, some parts of the rhythm mode are still + * only an approximation of the real chip. + * The other thing to note is that this emulator was done through recordings of + * the SB16 DAC, so it has not bitwise precision. Additional equipment should be + * used to verify the samples directly from the chip, and allow this exact + * per-sample correspondence. As a good side-effect, since this emulator uses + * floating point and has a more fine-grained envelope generator, it can produce + * sometimes a crystal-clear, denser kind of OPL3 sound that, because of that, + * may be useful for creating new music. + * + * Version 1.0.6 + * + */ + +#include +#include + +#include "doomtype.h" +#include "opl.h" +#include "m_random.h" +#include "xs_Float.h" + +static FRandom pr_opl3; + +#define VOLUME_MUL 0.3333 + +class Operator; + +static inline double StripIntPart(double num) +{ +#if 0 + double dontcare; + return modf(num, &dontcare); +#else + return num - xs_RoundToInt(num); +#endif +} + +// +// Channels +// + + +class Channel +{ +protected: + double feedback[2]; + + int fnuml, fnumh, kon, block, fb, cha, chb, cnt; + + // Factor to convert between normalized amplitude to normalized + // radians. The amplitude maximum is equivalent to 8*Pi radians. +#define toPhase (4.f) + +public: + int channelBaseAddress; + + double leftPan, rightPan; + + Channel (int baseAddress, double startvol); + virtual ~Channel() {} + void update_2_KON1_BLOCK3_FNUMH2(class OPL3 *OPL3); + void update_FNUML8(class OPL3 *OPL3); + void update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(class OPL3 *OPL3); + void updateChannel(class OPL3 *OPL3); + void updatePan(class OPL3 *OPL3); + virtual double getChannelOutput(class OPL3 *OPL3) = 0; + + virtual void keyOn() = 0; + virtual void keyOff() = 0; + virtual void updateOperators(class OPL3 *OPL3) = 0; +}; + + +class Channel2op : public Channel +{ +public: + Operator *op1, *op2; + + Channel2op (int baseAddress, double startvol, Operator *o1, Operator *o2); + double getChannelOutput(class OPL3 *OPL3); + + void keyOn(); + void keyOff(); + void updateOperators(class OPL3 *OPL3); +}; + + +class Channel4op : public Channel +{ +public: + Operator *op1, *op2, *op3, *op4; + + Channel4op (int baseAddress, double startvol, Operator *o1, Operator *o2, Operator *o3, Operator *o4); + double getChannelOutput(class OPL3 *OPL3); + + void keyOn(); + void keyOff(); + void updateOperators(class OPL3 *OPL3); +}; + +// There's just one instance of this class, that fills the eventual gaps in the Channel array; +class DisabledChannel : public Channel +{ +public: + DisabledChannel() : Channel(0, 0) { } + double getChannelOutput(class OPL3 *OPL3) { return 0; } + void keyOn() { } + void keyOff() { } + void updateOperators(class OPL3 *OPL3) { } +}; + + + +// +// Envelope Generator +// + + +class EnvelopeGenerator +{ +public: + enum Stage {ATTACK,DECAY,SUSTAIN,RELEASE,OFF}; + Stage stage; + int actualAttackRate, actualDecayRate, actualReleaseRate; + double xAttackIncrement, xMinimumInAttack; + double dBdecayIncrement; + double dBreleaseIncrement; + double attenuation, totalLevel, sustainLevel; + double x, envelope; + +public: + EnvelopeGenerator(); + void setActualSustainLevel(int sl); + void setTotalLevel(int tl); + void setAtennuation(int f_number, int block, int ksl); + void setActualAttackRate(int attackRate, int ksr, int keyScaleNumber); + void setActualDecayRate(int decayRate, int ksr, int keyScaleNumber); + void setActualReleaseRate(int releaseRate, int ksr, int keyScaleNumber); +private: + int calculateActualRate(int rate, int ksr, int keyScaleNumber); +public: + double getEnvelope(OPL3 *OPL3, int egt, int am); + void keyOn(); + void keyOff(); + +private: + static double dBtoX(double dB); + static double percentageToDB(double percentage); + static double percentageToX(double percentage); +}; + + +// +// Phase Generator +// + + +class PhaseGenerator { + double phase, phaseIncrement; + +public: + PhaseGenerator(); + void setFrequency(int f_number, int block, int mult); + double getPhase(class OPL3 *OPL3, int vib); + void keyOn(); +}; + + +// +// Operators +// + + +class Operator +{ +public: + PhaseGenerator phaseGenerator; + EnvelopeGenerator envelopeGenerator; + + double envelope, phase; + + int operatorBaseAddress; + int am, vib, ksr, egt, mult, ksl, tl, ar, dr, sl, rr, ws; + int keyScaleNumber, f_number, block; + + static const double noModulator; + +public: + Operator(int baseAddress); + void update_AM1_VIB1_EGT1_KSR1_MULT4(class OPL3 *OPL3); + void update_KSL2_TL6(class OPL3 *OPL3); + void update_AR4_DR4(class OPL3 *OPL3); + void update_SL4_RR4(class OPL3 *OPL3); + void update_5_WS3(class OPL3 *OPL3); + double getOperatorOutput(class OPL3 *OPL3, double modulator); + + void keyOn(); + void keyOff(); + void updateOperator(class OPL3 *OPL3, int ksn, int f_num, int blk); +protected: + double getOutput(double modulator, double outputPhase, double *waveform); +}; + + +// +// Rhythm +// + +// The getOperatorOutput() method in TopCymbalOperator, HighHatOperator and SnareDrumOperator +// were made through purely empyrical reverse engineering of the OPL3 output. + +class RhythmChannel : public Channel2op +{ +public: + RhythmChannel(int baseAddress, double startvol, Operator *o1, Operator *o2) + : Channel2op(baseAddress, startvol, o1, o2) + { } + double getChannelOutput(class OPL3 *OPL3); + + // Rhythm channels are always running, + // only the envelope is activated by the user. + void keyOn() { } + void keyOff() { } +}; + +class HighHatSnareDrumChannel : public RhythmChannel { + static const int highHatSnareDrumChannelBaseAddress = 7; +public: + HighHatSnareDrumChannel(double startvol, Operator *o1, Operator *o2) + : RhythmChannel(highHatSnareDrumChannelBaseAddress, startvol, o1, o2) + { } +}; + +class TomTomTopCymbalChannel : public RhythmChannel { + static const int tomTomTopCymbalChannelBaseAddress = 8; +public: + TomTomTopCymbalChannel(double startvol, Operator *o1, Operator *o2) + : RhythmChannel(tomTomTopCymbalChannelBaseAddress, startvol, o1, o2) + { } +}; + +class TopCymbalOperator : public Operator { + static const int topCymbalOperatorBaseAddress = 0x15; +public: + TopCymbalOperator(int baseAddress); + TopCymbalOperator(); + double getOperatorOutput(class OPL3 *OPL3, double modulator); + double getOperatorOutput(class OPL3 *OPL3, double modulator, double externalPhase); +}; + +class HighHatOperator : public TopCymbalOperator { + static const int highHatOperatorBaseAddress = 0x11; +public: + HighHatOperator(); + double getOperatorOutput(class OPL3 *OPL3, double modulator); +}; + +class SnareDrumOperator : public Operator { + static const int snareDrumOperatorBaseAddress = 0x14; +public: + SnareDrumOperator(); + double getOperatorOutput(class OPL3 *OPL3, double modulator); +}; + +class TomTomOperator : public Operator { + static const int tomTomOperatorBaseAddress = 0x12; +public: + TomTomOperator() : Operator(tomTomOperatorBaseAddress) { } +}; + +class BassDrumChannel : public Channel2op { + static const int bassDrumChannelBaseAddress = 6; + static const int op1BaseAddress = 0x10; + static const int op2BaseAddress = 0x13; + + Operator my_op1, my_op2; + +public: + BassDrumChannel(double startvol); + double getChannelOutput(class OPL3 *OPL3); + + // Key ON and OFF are unused in rhythm channels. + void keyOn() { } + void keyOff() { } +}; + + +// +// OPl3 Data +// + + +struct OPL3DataStruct +{ +public: + // OPL3-wide registers offsets: + static const int + _1_NTS1_6_Offset = 0x08, + DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1_Offset = 0xBD, + _7_NEW1_Offset = 0x105, + _2_CONNECTIONSEL6_Offset = 0x104; + + // The OPL3 tremolo repetition rate is 3.7 Hz. + #define tremoloFrequency (3.7) + + static const int tremoloTableLength = (int)(OPL_SAMPLE_RATE/tremoloFrequency); + static const int vibratoTableLength = 8192; + + OPL3DataStruct() + { + loadVibratoTable(); + loadTremoloTable(); + } + + // The first array is used when DVB=0 and the second array is used when DVB=1. + double vibratoTable[2][vibratoTableLength]; + + // First array used when AM = 0 and second array used when AM = 1. + double tremoloTable[2][tremoloTableLength]; + + static double calculateIncrement(double begin, double end, double period) { + return (end-begin)/OPL_SAMPLE_RATE * (1/period); + } + +private: + void loadVibratoTable(); + void loadTremoloTable(); +}; + + +// +// Channel Data +// + + +struct ChannelData +{ + static const int + _2_KON1_BLOCK3_FNUMH2_Offset = 0xB0, + FNUML8_Offset = 0xA0, + CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset = 0xC0; + + // Feedback rate in fractions of 2*Pi, normalized to (0,1): + // 0, Pi/16, Pi/8, Pi/4, Pi/2, Pi, 2*Pi, 4*Pi turns to be: + static const float feedback[8]; +}; +const float ChannelData::feedback[8] = {0,1/32.f,1/16.f,1/8.f,1/4.f,1/2.f,1,2}; + + +// +// Operator Data +// + + +struct OperatorDataStruct +{ + static const int + AM1_VIB1_EGT1_KSR1_MULT4_Offset = 0x20, + KSL2_TL6_Offset = 0x40, + AR4_DR4_Offset = 0x60, + SL4_RR4_Offset = 0x80, + _5_WS3_Offset = 0xE0; + + enum type {NO_MODULATION, CARRIER, FEEDBACK}; + + static const int waveLength = 1024; + + static const float multTable[16]; + static const float ksl3dBtable[16][8]; + + //OPL3 has eight waveforms: + double waveforms[8][waveLength]; + +#define MIN_DB (-120.0) +#define DB_TABLE_RES (4.0) +#define DB_TABLE_SIZE (int)(-MIN_DB * DB_TABLE_RES) + + double dbpow[DB_TABLE_SIZE]; + +#define ATTACK_MIN (-5.0) +#define ATTACK_MAX (8.0) +#define ATTACK_RES (0.03125) +#define ATTACK_TABLE_SIZE (int)((ATTACK_MAX - ATTACK_MIN) / ATTACK_RES) + + double attackTable[ATTACK_TABLE_SIZE]; + + OperatorDataStruct() + { + loadWaveforms(); + loaddBPowTable(); + loadAttackTable(); + } + + static double log2(double x) { + return log(x)/log(2.0); + } +private: + void loadWaveforms(); + void loaddBPowTable(); + void loadAttackTable(); +}; +const float OperatorDataStruct::multTable[16] = {0.5,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15}; + +const float OperatorDataStruct::ksl3dBtable[16][8] = { + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,-3,-6,-9}, + {0,0,0,0,-3,-6,-9,-12}, + {0,0,0, -1.875, -4.875, -7.875, -10.875, -13.875}, + + {0,0,0,-3,-6,-9,-12,-15}, + {0,0, -1.125, -4.125, -7.125, -10.125, -13.125, -16.125}, + {0,0, -1.875, -4.875, -7.875, -10.875, -13.875, -16.875}, + {0,0, -2.625, -5.625, -8.625, -11.625, -14.625, -17.625}, + + {0,0,-3,-6,-9,-12,-15,-18}, + {0, -0.750, -3.750, -6.750, -9.750, -12.750, -15.750, -18.750}, + {0, -1.125, -4.125, -7.125, -10.125, -13.125, -16.125, -19.125}, + {0, -1.500, -4.500, -7.500, -10.500, -13.500, -16.500, -19.500}, + + {0, -1.875, -4.875, -7.875, -10.875, -13.875, -16.875, -19.875}, + {0, -2.250, -5.250, -8.250, -11.250, -14.250, -17.250, -20.250}, + {0, -2.625, -5.625, -8.625, -11.625, -14.625, -17.625, -20.625}, + {0,-3,-6,-9,-12,-15,-18,-21} +}; + +// +// Envelope Generator Data +// + + +namespace EnvelopeGeneratorData +{ + static const double MUGEN = std::numeric_limits::infinity(); + // This table is indexed by the value of Operator.ksr + // and the value of ChannelRegister.keyScaleNumber. + static const int rateOffset[2][16] = { + {0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3}, + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} + }; + // These attack periods in miliseconds were taken from the YMF278B manual. + // The attack actual rates range from 0 to 63, with different data for + // 0%-100% and for 10%-90%: + static const double attackTimeValuesTable[64][2] = { + {MUGEN,MUGEN}, {MUGEN,MUGEN}, {MUGEN,MUGEN}, {MUGEN,MUGEN}, + {2826.24,1482.75}, {2252.80,1155.07}, {1884.16,991.23}, {1597.44,868.35}, + {1413.12,741.38}, {1126.40,577.54}, {942.08,495.62}, {798.72,434.18}, + {706.56,370.69}, {563.20,288.77}, {471.04,247.81}, {399.36,217.09}, + + {353.28,185.34}, {281.60,144.38}, {235.52,123.90}, {199.68,108.54}, + {176.76,92.67}, {140.80,72.19}, {117.76,61.95}, {99.84,54.27}, + {88.32,46.34}, {70.40,36.10}, {58.88,30.98}, {49.92,27.14}, + {44.16,23.17}, {35.20,18.05}, {29.44,15.49}, {24.96,13.57}, + + {22.08,11.58}, {17.60,9.02}, {14.72,7.74}, {12.48,6.78}, + {11.04,5.79}, {8.80,4.51}, {7.36,3.87}, {6.24,3.39}, + {5.52,2.90}, {4.40,2.26}, {3.68,1.94}, {3.12,1.70}, + {2.76,1.45}, {2.20,1.13}, {1.84,0.97}, {1.56,0.85}, + + {1.40,0.73}, {1.12,0.61}, {0.92,0.49}, {0.80,0.43}, + {0.70,0.37}, {0.56,0.31}, {0.46,0.26}, {0.42,0.22}, + {0.38,0.19}, {0.30,0.14}, {0.24,0.11}, {0.20,0.11}, + {0.00,0.00}, {0.00,0.00}, {0.00,0.00}, {0.00,0.00} + }; + + // These decay and release periods in milliseconds were taken from the YMF278B manual. + // The rate index range from 0 to 63, with different data for + // 0%-100% and for 10%-90%: + static const double decayAndReleaseTimeValuesTable[64][2] = { + {MUGEN,MUGEN}, {MUGEN,MUGEN}, {MUGEN,MUGEN}, {MUGEN,MUGEN}, + {39280.64,8212.48}, {31416.32,6574.08}, {26173.44,5509.12}, {22446.08,4730.88}, + {19640.32,4106.24}, {15708.16,3287.04}, {13086.72,2754.56}, {11223.04,2365.44}, + {9820.16,2053.12}, {7854.08,1643.52}, {6543.36,1377.28}, {5611.52,1182.72}, + + {4910.08,1026.56}, {3927.04,821.76}, {3271.68,688.64}, {2805.76,591.36}, + {2455.04,513.28}, {1936.52,410.88}, {1635.84,344.34}, {1402.88,295.68}, + {1227.52,256.64}, {981.76,205.44}, {817.92,172.16}, {701.44,147.84}, + {613.76,128.32}, {490.88,102.72}, {488.96,86.08}, {350.72,73.92}, + + {306.88,64.16}, {245.44,51.36}, {204.48,43.04}, {175.36,36.96}, + {153.44,32.08}, {122.72,25.68}, {102.24,21.52}, {87.68,18.48}, + {76.72,16.04}, {61.36,12.84}, {51.12,10.76}, {43.84,9.24}, + {38.36,8.02}, {30.68,6.42}, {25.56,5.38}, {21.92,4.62}, + + {19.20,4.02}, {15.36,3.22}, {12.80,2.68}, {10.96,2.32}, + {9.60,2.02}, {7.68,1.62}, {6.40,1.35}, {5.48,1.15}, + {4.80,1.01}, {3.84,0.81}, {3.20,0.69}, {2.74,0.58}, + {2.40,0.51}, {2.40,0.51}, {2.40,0.51}, {2.40,0.51} + }; +}; + +class OPL3 : public OPLEmul +{ +public: + BYTE registers[0x200]; + + Operator *operators[2][0x20]; + Channel2op *channels2op[2][9]; + Channel4op *channels4op[2][3]; + Channel *channels[2][9]; + + // Unique instance to fill future gaps in the Channel array, + // when there will be switches between 2op and 4op mode. + DisabledChannel disabledChannel; + + // Specific operators to switch when in rhythm mode: + HighHatOperator highHatOperator; + SnareDrumOperator snareDrumOperator; + TomTomOperator tomTomOperator; + TomTomTopCymbalChannel tomTomTopCymbalChannel; + + // Rhythm channels + BassDrumChannel bassDrumChannel; + HighHatSnareDrumChannel highHatSnareDrumChannel; + TopCymbalOperator topCymbalOperator; + + Operator *highHatOperatorInNonRhythmMode; + Operator *snareDrumOperatorInNonRhythmMode; + Operator *tomTomOperatorInNonRhythmMode; + Operator *topCymbalOperatorInNonRhythmMode; + + int nts, dam, dvb, ryt, bd, sd, tom, tc, hh, _new, connectionsel; + int vibratoIndex, tremoloIndex; + + bool FullPan; + + static OperatorDataStruct *OperatorData; + static OPL3DataStruct *OPL3Data; + + // The methods read() and write() are the only + // ones needed by the user to interface with the emulator. + // read() returns one frame at a time, to be played at 49700 Hz, + // with each frame being four 16-bit samples, + // corresponding to the OPL3 four output channels CHA...CHD. +public: + //void read(float output[2]); + void write(int array, int address, int data); + + OPL3(bool fullpan); + ~OPL3(); + +private: + void initOperators(); + void initChannels2op(); + void initChannels4op(); + void initRhythmChannels(); + void initChannels(); + void update_1_NTS1_6(); + void update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1(); + void update_7_NEW1(); + void setEnabledChannels(); + void updateChannelPans(); + void update_2_CONNECTIONSEL6(); + void set4opConnections(); + void setRhythmMode(); + + static int InstanceCount; + + // OPLEmul interface +public: + void Reset(); + void WriteReg(int reg, int v); + void Update(float *buffer, int length); + void SetPanning(int c, float left, float right); +}; + +OperatorDataStruct *OPL3::OperatorData; +OPL3DataStruct *OPL3::OPL3Data; +int OPL3::InstanceCount; + +void OPL3::Update(float *output, int numsamples) { + while (numsamples--) { + // If _new = 0, use OPL2 mode with 9 channels. If _new = 1, use OPL3 18 channels; + for(int array=0; array < (_new + 1); array++) + for(int channelNumber=0; channelNumber < 9; channelNumber++) { + // Reads output from each OPL3 channel, and accumulates it in the output buffer: + Channel *channel = channels[array][channelNumber]; + if (channel != &disabledChannel) + { + double channelOutput = channel->getChannelOutput(this); + output[0] += float(channelOutput * channel->leftPan); + output[1] += float(channelOutput * channel->rightPan); + } + } + + // Advances the OPL3-wide vibrato index, which is used by + // PhaseGenerator.getPhase() in each Operator. + vibratoIndex = (vibratoIndex + 1) & (OPL3DataStruct::vibratoTableLength - 1); + // Advances the OPL3-wide tremolo index, which is used by + // EnvelopeGenerator.getEnvelope() in each Operator. + tremoloIndex++; + if(tremoloIndex >= OPL3DataStruct::tremoloTableLength) tremoloIndex = 0; + output += 2; + } +} + +void OPL3::write(int array, int address, int data) { + // The OPL3 has two registers arrays, each with adresses ranging + // from 0x00 to 0xF5. + // This emulator uses one array, with the two original register arrays + // starting at 0x00 and at 0x100. + int registerAddress = (array<<8) | address; + // If the address is out of the OPL3 memory map, returns. + if(registerAddress<0 || registerAddress>=0x200) return; + + registers[registerAddress] = data; + switch(address&0xE0) { + // The first 3 bits masking gives the type of the register by using its base address: + // 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0 + // When it is needed, we further separate the register type inside each base address, + // which is the case of 0x00 and 0xA0. + + // Through out this emulator we will use the same name convention to + // reference a byte with several bit registers. + // The name of each bit register will be followed by the number of bits + // it occupies inside the byte. + // Numbers without accompanying names are unused bits. + case 0x00: + // Unique registers for the entire OPL3: + if(array==1) { + if(address==0x04) + update_2_CONNECTIONSEL6(); + else if(address==0x05) + update_7_NEW1(); + } + else if(address==0x08) update_1_NTS1_6(); + break; + + case 0xA0: + // 0xBD is a control register for the entire OPL3: + if(address==0xBD) { + if(array==0) + update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1(); + break; + } + // Registers for each channel are in A0-A8, B0-B8, C0-C8, in both register arrays. + // 0xB0...0xB8 keeps kon,block,fnum(h) for each channel. + if( (address&0xF0) == 0xB0 && address <= 0xB8) { + // If the address is in the second register array, adds 9 to the channel number. + // The channel number is given by the last four bits, like in A0,...,A8. + channels[array][address&0x0F]->update_2_KON1_BLOCK3_FNUMH2(this); + break; + } + // 0xA0...0xA8 keeps fnum(l) for each channel. + if( (address&0xF0) == 0xA0 && address <= 0xA8) + channels[array][address&0x0F]->update_FNUML8(this); + break; + // 0xC0...0xC8 keeps cha,chb,chc,chd,fb,cnt for each channel: + case 0xC0: + if(address <= 0xC8) + channels[array][address&0x0F]->update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(this); + break; + + // Registers for each of the 36 Operators: + default: + int operatorOffset = address&0x1F; + if(operators[array][operatorOffset] == NULL) break; + switch(address&0xE0) { + // 0x20...0x35 keeps am,vib,egt,ksr,mult for each operator: + case 0x20: + operators[array][operatorOffset]->update_AM1_VIB1_EGT1_KSR1_MULT4(this); + break; + // 0x40...0x55 keeps ksl,tl for each operator: + case 0x40: + operators[array][operatorOffset]->update_KSL2_TL6(this); + break; + // 0x60...0x75 keeps ar,dr for each operator: + case 0x60: + operators[array][operatorOffset]->update_AR4_DR4(this); + break; + // 0x80...0x95 keeps sl,rr for each operator: + case 0x80: + operators[array][operatorOffset]->update_SL4_RR4(this); + break; + // 0xE0...0xF5 keeps ws for each operator: + case 0xE0: + operators[array][operatorOffset]->update_5_WS3(this); + } + } +} + +OPL3::OPL3(bool fullpan) +: tomTomTopCymbalChannel(fullpan ? CENTER_PANNING_POWER : 1, &tomTomOperator, &topCymbalOperator), + bassDrumChannel(fullpan ? CENTER_PANNING_POWER : 1), + highHatSnareDrumChannel(fullpan ? CENTER_PANNING_POWER : 1, &highHatOperator, &snareDrumOperator) +{ + FullPan = fullpan; + nts = dam = dvb = ryt = bd = sd = tom = tc = hh = _new = connectionsel = 0; + vibratoIndex = tremoloIndex = 0; + + if (InstanceCount++ == 0) + { + OPL3Data = new struct OPL3DataStruct; + OperatorData = new struct OperatorDataStruct; + } + + initOperators(); + initChannels2op(); + initChannels4op(); + initRhythmChannels(); + initChannels(); +} + +OPL3::~OPL3() +{ + ryt = 0; + setRhythmMode(); // Make sure all operators point to the dynamically allocated ones. + for (int array = 0; array < 2; array++) + { + for (int operatorNumber = 0; operatorNumber < 0x20; operatorNumber++) + { + if (operators[array][operatorNumber] != NULL) + { + delete operators[array][operatorNumber]; + } + } + for (int channelNumber = 0; channelNumber < 9; channelNumber++) + { + delete channels2op[array][channelNumber]; + } + for (int channelNumber = 0; channelNumber < 3; channelNumber++) + { + delete channels4op[array][channelNumber]; + } + } + if (--InstanceCount == 0) + { + delete OPL3Data; + OPL3Data = NULL; + delete OperatorData; + OperatorData = NULL; + } +} + + +void OPL3::initOperators() { + int baseAddress; + // The YMF262 has 36 operators: + memset(operators, 0, sizeof(operators)); + for(int array=0; array<2; array++) + for(int group = 0; group<=0x10; group+=8) + for(int offset=0; offset<6; offset++) { + baseAddress = (array<<8) | (group+offset); + operators[array][group+offset] = new Operator(baseAddress); + } + + // Save operators when they are in non-rhythm mode: + // Channel 7: + highHatOperatorInNonRhythmMode = operators[0][0x11]; + snareDrumOperatorInNonRhythmMode = operators[0][0x14]; + // Channel 8: + tomTomOperatorInNonRhythmMode = operators[0][0x12]; + topCymbalOperatorInNonRhythmMode = operators[0][0x15]; + +} + +void OPL3::initChannels2op() { + // The YMF262 has 18 2-op channels. + // Each 2-op channel can be at a serial or parallel operator configuration: + memset(channels2op, 0, sizeof(channels2op)); + double startvol = FullPan ? CENTER_PANNING_POWER : 1; + for(int array=0; array<2; array++) + for(int channelNumber=0; channelNumber<3; channelNumber++) { + int baseAddress = (array<<8) | channelNumber; + // Channels 1, 2, 3 -> Operator offsets 0x0,0x3; 0x1,0x4; 0x2,0x5 + channels2op[array][channelNumber] = new Channel2op(baseAddress, startvol, operators[array][channelNumber], operators[array][channelNumber+0x3]); + // Channels 4, 5, 6 -> Operator offsets 0x8,0xB; 0x9,0xC; 0xA,0xD + channels2op[array][channelNumber+3] = new Channel2op(baseAddress+3, startvol, operators[array][channelNumber+0x8], operators[array][channelNumber+0xB]); + // Channels 7, 8, 9 -> Operators 0x10,0x13; 0x11,0x14; 0x12,0x15 + channels2op[array][channelNumber+6] = new Channel2op(baseAddress+6, startvol, operators[array][channelNumber+0x10], operators[array][channelNumber+0x13]); + } +} + +void OPL3::initChannels4op() { + // The YMF262 has 3 4-op channels in each array: + memset(channels4op, 0, sizeof(channels4op)); + double startvol = FullPan ? CENTER_PANNING_POWER : 1; + for(int array=0; array<2; array++) + for(int channelNumber=0; channelNumber<3; channelNumber++) { + int baseAddress = (array<<8) | channelNumber; + // Channels 1, 2, 3 -> Operators 0x0,0x3,0x8,0xB; 0x1,0x4,0x9,0xC; 0x2,0x5,0xA,0xD; + channels4op[array][channelNumber] = new Channel4op(baseAddress, startvol, operators[array][channelNumber], operators[array][channelNumber+0x3], operators[array][channelNumber+0x8], operators[array][channelNumber+0xB]); + } +} + +void OPL3::initRhythmChannels() { +} + +void OPL3::initChannels() { + // Channel is an abstract class that can be a 2-op, 4-op, rhythm or disabled channel, + // depending on the OPL3 configuration at the time. + // channels[] inits as a 2-op serial channel array: + for(int array=0; array<2; array++) + for(int i=0; i<9; i++) channels[array][i] = channels2op[array][i]; +} + +void OPL3::update_1_NTS1_6() { + int _1_nts1_6 = registers[OPL3DataStruct::_1_NTS1_6_Offset]; + // Note Selection. This register is used in Channel.updateOperators() implementations, + // to calculate the channel´s Key Scale Number. + // The value of the actual envelope rate follows the value of + // OPL3.nts,Operator.keyScaleNumber and Operator.ksr + nts = (_1_nts1_6 & 0x40) >> 6; +} + +void OPL3::update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1() { + int dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 = registers[OPL3DataStruct::DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1_Offset]; + // Depth of amplitude. This register is used in EnvelopeGenerator.getEnvelope(); + dam = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x80) >> 7; + + // Depth of vibrato. This register is used in PhaseGenerator.getPhase(); + dvb = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x40) >> 6; + + int new_ryt = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x20) >> 5; + if(new_ryt != ryt) { + ryt = new_ryt; + setRhythmMode(); + } + + int new_bd = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x10) >> 4; + if(new_bd != bd) { + bd = new_bd; + if(bd==1) { + bassDrumChannel.op1->keyOn(); + bassDrumChannel.op2->keyOn(); + } + } + + int new_sd = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x08) >> 3; + if(new_sd != sd) { + sd = new_sd; + if(sd==1) snareDrumOperator.keyOn(); + } + + int new_tom = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x04) >> 2; + if(new_tom != tom) { + tom = new_tom; + if(tom==1) tomTomOperator.keyOn(); + } + + int new_tc = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x02) >> 1; + if(new_tc != tc) { + tc = new_tc; + if(tc==1) topCymbalOperator.keyOn(); + } + + int new_hh = dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x01; + if(new_hh != hh) { + hh = new_hh; + if(hh==1) highHatOperator.keyOn(); + } + +} + +void OPL3::update_7_NEW1() { + int _7_new1 = registers[OPL3DataStruct::_7_NEW1_Offset]; + // OPL2/OPL3 mode selection. This register is used in + // OPL3.read(), OPL3.write() and Operator.getOperatorOutput(); + _new = (_7_new1 & 0x01); + if(_new==1) setEnabledChannels(); + set4opConnections(); + updateChannelPans(); +} + +void OPL3::setEnabledChannels() { + for(int array=0; array<2; array++) + for(int i=0; i<9; i++) { + int baseAddress = channels[array][i]->channelBaseAddress; + registers[baseAddress+ChannelData::CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] |= 0xF0; + channels[array][i]->update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(this); + } +} + +void OPL3::updateChannelPans() { + for(int array=0; array<2; array++) + for(int i=0; i<9; i++) { + int baseAddress = channels[array][i]->channelBaseAddress; + registers[baseAddress+ChannelData::CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] |= 0xF0; + channels[array][i]->updatePan(this); + } + +} + +void OPL3::update_2_CONNECTIONSEL6() { + // This method is called only if _new is set. + int _2_connectionsel6 = registers[OPL3DataStruct::_2_CONNECTIONSEL6_Offset]; + // 2-op/4-op channel selection. This register is used here to configure the OPL3.channels[] array. + connectionsel = (_2_connectionsel6 & 0x3F); + set4opConnections(); +} + +void OPL3::set4opConnections() { + // bits 0, 1, 2 sets respectively 2-op channels (1,4), (2,5), (3,6) to 4-op operation. + // bits 3, 4, 5 sets respectively 2-op channels (10,13), (11,14), (12,15) to 4-op operation. + for(int array=0; array<2; array++) + for(int i=0; i<3; i++) { + if(_new == 1) { + int shift = array*3 + i; + int connectionBit = (connectionsel >> shift) & 0x01; + if(connectionBit == 1) { + channels[array][i] = channels4op[array][i]; + channels[array][i+3] = &disabledChannel; + channels[array][i]->updateChannel(this); + continue; + } + } + channels[array][i] = channels2op[array][i]; + channels[array][i+3] = channels2op[array][i+3]; + channels[array][i]->updateChannel(this); + channels[array][i+3]->updateChannel(this); + } +} + +void OPL3::setRhythmMode() { + if(ryt==1) { + channels[0][6] = &bassDrumChannel; + channels[0][7] = &highHatSnareDrumChannel; + channels[0][8] = &tomTomTopCymbalChannel; + operators[0][0x11] = &highHatOperator; + operators[0][0x14] = &snareDrumOperator; + operators[0][0x12] = &tomTomOperator; + operators[0][0x15] = &topCymbalOperator; + } + else { + for(int i=6; i<=8; i++) channels[0][i] = channels2op[0][i]; + operators[0][0x11] = highHatOperatorInNonRhythmMode; + operators[0][0x14] = snareDrumOperatorInNonRhythmMode; + operators[0][0x12] = tomTomOperatorInNonRhythmMode; + operators[0][0x15] = topCymbalOperatorInNonRhythmMode; + } + for(int i=6; i<=8; i++) channels[0][i]->updateChannel(this); +} + +static double EnvelopeFromDB(double db) +{ +#if 0 + return pow(10.0, db/10); +#else + if (db < MIN_DB) + return 0; + return OPL3::OperatorData->dbpow[xs_FloorToInt(-db * DB_TABLE_RES)]; +#endif +} + +Channel::Channel (int baseAddress, double startvol) { + channelBaseAddress = baseAddress; + fnuml = fnumh = kon = block = fb = cnt = 0; + feedback[0] = feedback[1] = 0; + leftPan = rightPan = startvol; +} + +void Channel::update_2_KON1_BLOCK3_FNUMH2(OPL3 *OPL3) { + + int _2_kon1_block3_fnumh2 = OPL3->registers[channelBaseAddress+ChannelData::_2_KON1_BLOCK3_FNUMH2_Offset]; + + // Frequency Number (hi-register) and Block. These two registers, together with fnuml, + // sets the Channel´s base frequency; + block = (_2_kon1_block3_fnumh2 & 0x1C) >> 2; + fnumh = _2_kon1_block3_fnumh2 & 0x03; + updateOperators(OPL3); + + // Key On. If changed, calls Channel.keyOn() / keyOff(). + int newKon = (_2_kon1_block3_fnumh2 & 0x20) >> 5; + if(newKon != kon) { + if(newKon == 1) keyOn(); + else keyOff(); + kon = newKon; + } +} + +void Channel::update_FNUML8(OPL3 *OPL3) { + int fnuml8 = OPL3->registers[channelBaseAddress+ChannelData::FNUML8_Offset]; + // Frequency Number, low register. + fnuml = fnuml8&0xFF; + updateOperators(OPL3); +} + +void Channel::update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(OPL3 *OPL3) { + int chd1_chc1_chb1_cha1_fb3_cnt1 = OPL3->registers[channelBaseAddress+ChannelData::CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset]; +// chd = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x80) >> 7; +// chc = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x40) >> 6; + chb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x20) >> 5; + cha = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x10) >> 4; + fb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x0E) >> 1; + cnt = chd1_chc1_chb1_cha1_fb3_cnt1 & 0x01; + updatePan(OPL3); + updateOperators(OPL3); +} + +void Channel::updatePan(OPL3 *OPL3) { + if (!OPL3->FullPan) + { + if (OPL3->_new == 0) + { + leftPan = VOLUME_MUL; + rightPan = VOLUME_MUL; + } + else + { + leftPan = cha * VOLUME_MUL; + rightPan = chb * VOLUME_MUL; + } + } +} + +void Channel::updateChannel(OPL3 *OPL3) { + update_2_KON1_BLOCK3_FNUMH2(OPL3); + update_FNUML8(OPL3); + update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(OPL3); +} + +Channel2op::Channel2op (int baseAddress, double startvol, Operator *o1, Operator *o2) +: Channel(baseAddress, startvol) +{ + op1 = o1; + op2 = o2; +} + +double Channel2op::getChannelOutput(OPL3 *OPL3) { + double channelOutput = 0, op1Output = 0, op2Output = 0; + // The feedback uses the last two outputs from + // the first operator, instead of just the last one. + double feedbackOutput = (feedback[0] + feedback[1]) / 2; + + switch(cnt) { + // CNT = 0, the operators are in series, with the first in feedback. + case 0: + if(op2->envelopeGenerator.stage==EnvelopeGenerator::OFF) + return 0; + op1Output = op1->getOperatorOutput(OPL3, feedbackOutput); + channelOutput = op2->getOperatorOutput(OPL3, op1Output*toPhase); + break; + // CNT = 1, the operators are in parallel, with the first in feedback. + case 1: + if(op1->envelopeGenerator.stage==EnvelopeGenerator::OFF && + op2->envelopeGenerator.stage==EnvelopeGenerator::OFF) + return 0; + op1Output = op1->getOperatorOutput(OPL3, feedbackOutput); + op2Output = op2->getOperatorOutput(OPL3, Operator::noModulator); + channelOutput = (op1Output + op2Output) / 2; + } + + feedback[0] = feedback[1]; + feedback[1] = StripIntPart(op1Output * ChannelData::feedback[fb]); + return channelOutput; +} + +void Channel2op::keyOn() { + op1->keyOn(); + op2->keyOn(); + feedback[0] = feedback[1] = 0; +} + +void Channel2op::keyOff() { + op1->keyOff(); + op2->keyOff(); +} + +void Channel2op::updateOperators(OPL3 *OPL3) { + // Key Scale Number, used in EnvelopeGenerator.setActualRates(). + int keyScaleNumber = block*2 + ((fnumh>>OPL3->nts)&0x01); + int f_number = (fnumh<<8) | fnuml; + op1->updateOperator(OPL3, keyScaleNumber, f_number, block); + op2->updateOperator(OPL3, keyScaleNumber, f_number, block); +} + +Channel4op::Channel4op (int baseAddress, double startvol, Operator *o1, Operator *o2, Operator *o3, Operator *o4) +: Channel(baseAddress, startvol) +{ + op1 = o1; + op2 = o2; + op3 = o3; + op4 = o4; +} + +double Channel4op::getChannelOutput(OPL3 *OPL3) { + double channelOutput = 0, + op1Output = 0, op2Output = 0, op3Output = 0, op4Output = 0; + + int secondChannelBaseAddress = channelBaseAddress+3; + int secondCnt = OPL3->registers[secondChannelBaseAddress+ChannelData::CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] & 0x1; + int cnt4op = (cnt << 1) | secondCnt; + + double feedbackOutput = (feedback[0] + feedback[1]) / 2; + + switch(cnt4op) { + case 0: + if(op4->envelopeGenerator.stage==EnvelopeGenerator::OFF) + return 0; + + op1Output = op1->getOperatorOutput(OPL3, feedbackOutput); + op2Output = op2->getOperatorOutput(OPL3, op1Output*toPhase); + op3Output = op3->getOperatorOutput(OPL3, op2Output*toPhase); + channelOutput = op4->getOperatorOutput(OPL3, op3Output*toPhase); + + break; + case 1: + if(op2->envelopeGenerator.stage==EnvelopeGenerator::OFF && + op4->envelopeGenerator.stage==EnvelopeGenerator::OFF) + return 0; + + op1Output = op1->getOperatorOutput(OPL3, feedbackOutput); + op2Output = op2->getOperatorOutput(OPL3, op1Output*toPhase); + + op3Output = op3->getOperatorOutput(OPL3, Operator::noModulator); + op4Output = op4->getOperatorOutput(OPL3, op3Output*toPhase); + + channelOutput = (op2Output + op4Output) / 2; + break; + case 2: + if(op1->envelopeGenerator.stage==EnvelopeGenerator::OFF && + op4->envelopeGenerator.stage==EnvelopeGenerator::OFF) + return 0; + + op1Output = op1->getOperatorOutput(OPL3, feedbackOutput); + + op2Output = op2->getOperatorOutput(OPL3, Operator::noModulator); + op3Output = op3->getOperatorOutput(OPL3, op2Output*toPhase); + op4Output = op4->getOperatorOutput(OPL3, op3Output*toPhase); + + channelOutput = (op1Output + op4Output) / 2; + break; + case 3: + if(op1->envelopeGenerator.stage==EnvelopeGenerator::OFF && + op3->envelopeGenerator.stage==EnvelopeGenerator::OFF && + op4->envelopeGenerator.stage==EnvelopeGenerator::OFF) + return 0; + + op1Output = op1->getOperatorOutput(OPL3, feedbackOutput); + + op2Output = op2->getOperatorOutput(OPL3, Operator::noModulator); + op3Output = op3->getOperatorOutput(OPL3, op2Output*toPhase); + + op4Output = op4->getOperatorOutput(OPL3, Operator::noModulator); + + channelOutput = (op1Output + op3Output + op4Output) / 3; + } + + feedback[0] = feedback[1]; + feedback[1] = StripIntPart(op1Output * ChannelData::feedback[fb]); + + return channelOutput; +} + +void Channel4op::keyOn() { + op1->keyOn(); + op2->keyOn(); + op3->keyOn(); + op4->keyOn(); + feedback[0] = feedback[1] = 0; +} + +void Channel4op::keyOff() { + op1->keyOff(); + op2->keyOff(); + op3->keyOff(); + op4->keyOff(); +} + +void Channel4op::updateOperators(OPL3 *OPL3) { + // Key Scale Number, used in EnvelopeGenerator.setActualRates(). + int keyScaleNumber = block*2 + ((fnumh>>OPL3->nts)&0x01); + int f_number = (fnumh<<8) | fnuml; + op1->updateOperator(OPL3, keyScaleNumber, f_number, block); + op2->updateOperator(OPL3, keyScaleNumber, f_number, block); + op3->updateOperator(OPL3, keyScaleNumber, f_number, block); + op4->updateOperator(OPL3, keyScaleNumber, f_number, block); +} + +const double Operator::noModulator = 0; + +Operator::Operator(int baseAddress) { + operatorBaseAddress = baseAddress; + + envelope = 0; + am = vib = ksr = egt = mult = ksl = tl = ar = dr = sl = rr = ws = 0; + keyScaleNumber = f_number = block = 0; +} + +void Operator::update_AM1_VIB1_EGT1_KSR1_MULT4(OPL3 *OPL3) { + + int am1_vib1_egt1_ksr1_mult4 = OPL3->registers[operatorBaseAddress+OperatorDataStruct::AM1_VIB1_EGT1_KSR1_MULT4_Offset]; + + // Amplitude Modulation. This register is used int EnvelopeGenerator.getEnvelope(); + am = (am1_vib1_egt1_ksr1_mult4 & 0x80) >> 7; + // Vibrato. This register is used in PhaseGenerator.getPhase(); + vib = (am1_vib1_egt1_ksr1_mult4 & 0x40) >> 6; + // Envelope Generator Type. This register is used in EnvelopeGenerator.getEnvelope(); + egt = (am1_vib1_egt1_ksr1_mult4 & 0x20) >> 5; + // Key Scale Rate. Sets the actual envelope rate together with rate and keyScaleNumber. + // This register os used in EnvelopeGenerator.setActualAttackRate(). + ksr = (am1_vib1_egt1_ksr1_mult4 & 0x10) >> 4; + // Multiple. Multiplies the Channel.baseFrequency to get the Operator.operatorFrequency. + // This register is used in PhaseGenerator.setFrequency(). + mult = am1_vib1_egt1_ksr1_mult4 & 0x0F; + + phaseGenerator.setFrequency(f_number, block, mult); + envelopeGenerator.setActualAttackRate(ar, ksr, keyScaleNumber); + envelopeGenerator.setActualDecayRate(dr, ksr, keyScaleNumber); + envelopeGenerator.setActualReleaseRate(rr, ksr, keyScaleNumber); +} + +void Operator::update_KSL2_TL6(OPL3 *OPL3) { + + int ksl2_tl6 = OPL3->registers[operatorBaseAddress+OperatorDataStruct::KSL2_TL6_Offset]; + + // Key Scale Level. Sets the attenuation in accordance with the octave. + ksl = (ksl2_tl6 & 0xC0) >> 6; + // Total Level. Sets the overall damping for the envelope. + tl = ksl2_tl6 & 0x3F; + + envelopeGenerator.setAtennuation(f_number, block, ksl); + envelopeGenerator.setTotalLevel(tl); +} + +void Operator::update_AR4_DR4(OPL3 *OPL3) { + + int ar4_dr4 = OPL3->registers[operatorBaseAddress+OperatorDataStruct::AR4_DR4_Offset]; + + // Attack Rate. + ar = (ar4_dr4 & 0xF0) >> 4; + // Decay Rate. + dr = ar4_dr4 & 0x0F; + + envelopeGenerator.setActualAttackRate(ar, ksr, keyScaleNumber); + envelopeGenerator.setActualDecayRate(dr, ksr, keyScaleNumber); +} + +void Operator::update_SL4_RR4(OPL3 *OPL3) { + + int sl4_rr4 = OPL3->registers[operatorBaseAddress+OperatorDataStruct::SL4_RR4_Offset]; + + // Sustain Level. + sl = (sl4_rr4 & 0xF0) >> 4; + // Release Rate. + rr = sl4_rr4 & 0x0F; + + envelopeGenerator.setActualSustainLevel(sl); + envelopeGenerator.setActualReleaseRate(rr, ksr, keyScaleNumber); +} + +void Operator::update_5_WS3(OPL3 *OPL3) { + int _5_ws3 = OPL3->registers[operatorBaseAddress+OperatorDataStruct::_5_WS3_Offset]; + ws = _5_ws3 & 0x07; +} + +double Operator::getOperatorOutput(OPL3 *OPL3, double modulator) { + if(envelopeGenerator.stage == EnvelopeGenerator::OFF) return 0; + + double envelopeInDB = envelopeGenerator.getEnvelope(OPL3, egt, am); + envelope = EnvelopeFromDB(envelopeInDB); + + // If it is in OPL2 mode, use first four waveforms only: + ws &= ((OPL3->_new<<2) + 3); + double *waveform = OPL3::OperatorData->waveforms[ws]; + + phase = phaseGenerator.getPhase(OPL3, vib); + + double operatorOutput = getOutput(modulator, phase, waveform); + return operatorOutput; +} + +double Operator::getOutput(double modulator, double outputPhase, double *waveform) { + int sampleIndex = xs_FloorToInt((outputPhase + modulator) * OperatorDataStruct::waveLength) & (OperatorDataStruct::waveLength - 1); + return waveform[sampleIndex] * envelope; +} + +void Operator::keyOn() { + if(ar > 0) { + envelopeGenerator.keyOn(); + phaseGenerator.keyOn(); + } + else envelopeGenerator.stage = EnvelopeGenerator::OFF; +} + +void Operator::keyOff() { + envelopeGenerator.keyOff(); +} + +void Operator::updateOperator(OPL3 *OPL3, int ksn, int f_num, int blk) { + keyScaleNumber = ksn; + f_number = f_num; + block = blk; + update_AM1_VIB1_EGT1_KSR1_MULT4(OPL3); + update_KSL2_TL6(OPL3); + update_AR4_DR4(OPL3); + update_SL4_RR4(OPL3); + update_5_WS3(OPL3); +} + +EnvelopeGenerator::EnvelopeGenerator() { + stage = OFF; + actualAttackRate = actualDecayRate = actualReleaseRate = 0; + xAttackIncrement = xMinimumInAttack = 0; + dBdecayIncrement = 0; + dBreleaseIncrement = 0; + attenuation = totalLevel = sustainLevel = 0; + x = dBtoX(-96); + envelope = -96; +} + +void EnvelopeGenerator::setActualSustainLevel(int sl) { + // If all SL bits are 1, sustain level is set to -93 dB: + if(sl == 0x0F) { + sustainLevel = -93; + return; + } + // The datasheet states that the SL formula is + // sustainLevel = -24*d7 -12*d6 -6*d5 -3*d4, + // translated as: + sustainLevel = -3*sl; +} + +void EnvelopeGenerator::setTotalLevel(int tl) { + // The datasheet states that the TL formula is + // TL = -(24*d5 + 12*d4 + 6*d3 + 3*d2 + 1.5*d1 + 0.75*d0), + // translated as: + totalLevel = tl*-0.75; +} + +void EnvelopeGenerator::setAtennuation(int f_number, int block, int ksl) { + int hi4bits = (f_number>>6)&0x0F; + switch(ksl) { + case 0: + attenuation = 0; + break; + case 1: + // ~3 dB/Octave + attenuation = OperatorDataStruct::ksl3dBtable[hi4bits][block]; + break; + case 2: + // ~1.5 dB/Octave + attenuation = OperatorDataStruct::ksl3dBtable[hi4bits][block]/2; + break; + case 3: + // ~6 dB/Octave + attenuation = OperatorDataStruct::ksl3dBtable[hi4bits][block]*2; + } +} + +void EnvelopeGenerator::setActualAttackRate(int attackRate, int ksr, int keyScaleNumber) { + // According to the YMF278B manual's OPL3 section, the attack curve is exponential, + // with a dynamic range from -96 dB to 0 dB and a resolution of 0.1875 dB + // per level. + // + // This method sets an attack increment and attack minimum value + // that creates a exponential dB curve with 'period0to100' seconds in length + // and 'period10to90' seconds between 10% and 90% of the curve total level. + actualAttackRate = calculateActualRate(attackRate, ksr, keyScaleNumber); + double period0to100inSeconds = EnvelopeGeneratorData::attackTimeValuesTable[actualAttackRate][0]/1000.0; + int period0to100inSamples = (int)(period0to100inSeconds*OPL_SAMPLE_RATE); + double period10to90inSeconds = EnvelopeGeneratorData::attackTimeValuesTable[actualAttackRate][1]/1000.0; + int period10to90inSamples = (int)(period10to90inSeconds*OPL_SAMPLE_RATE); + // The x increment is dictated by the period between 10% and 90%: + xAttackIncrement = OPL3DataStruct::calculateIncrement(percentageToX(0.1), percentageToX(0.9), period10to90inSeconds); + // Discover how many samples are still from the top. + // It cannot reach 0 dB, since x is a logarithmic parameter and would be + // negative infinity. So we will use -0.1875 dB as the resolution + // maximum. + // + // percentageToX(0.9) + samplesToTheTop*xAttackIncrement = dBToX(-0.1875); -> + // samplesToTheTop = (dBtoX(-0.1875) - percentageToX(0.9)) / xAttackIncrement); -> + // period10to100InSamples = period10to90InSamples + samplesToTheTop; -> + int period10to100inSamples = (int) (period10to90inSamples + (dBtoX(-0.1875) - percentageToX(0.9)) / xAttackIncrement); + // Discover the minimum x that, through the attackIncrement value, keeps + // the 10%-90% period, and reaches 0 dB at the total period: + xMinimumInAttack = percentageToX(0.1) - (period0to100inSamples-period10to100inSamples)*xAttackIncrement; +} + + +void EnvelopeGenerator::setActualDecayRate(int decayRate, int ksr, int keyScaleNumber) { + actualDecayRate = calculateActualRate(decayRate, ksr, keyScaleNumber); + double period10to90inSeconds = EnvelopeGeneratorData::decayAndReleaseTimeValuesTable[actualDecayRate][1]/1000.0; + // Differently from the attack curve, the decay/release curve is linear. + // The dB increment is dictated by the period between 10% and 90%: + dBdecayIncrement = OPL3DataStruct::calculateIncrement(percentageToDB(0.1), percentageToDB(0.9), period10to90inSeconds); +} + +void EnvelopeGenerator::setActualReleaseRate(int releaseRate, int ksr, int keyScaleNumber) { + actualReleaseRate = calculateActualRate(releaseRate, ksr, keyScaleNumber); + double period10to90inSeconds = EnvelopeGeneratorData::decayAndReleaseTimeValuesTable[actualReleaseRate][1]/1000.0; + dBreleaseIncrement = OPL3DataStruct::calculateIncrement(percentageToDB(0.1), percentageToDB(0.9), period10to90inSeconds); +} + +int EnvelopeGenerator::calculateActualRate(int rate, int ksr, int keyScaleNumber) { + int rof = EnvelopeGeneratorData::rateOffset[ksr][keyScaleNumber]; + int actualRate = rate*4 + rof; + // If, as an example at the maximum, rate is 15 and the rate offset is 15, + // the value would + // be 75, but the maximum allowed is 63: + if(actualRate > 63) actualRate = 63; + return actualRate; +} + +double EnvelopeGenerator::getEnvelope(OPL3 *OPL3, int egt, int am) { + // The datasheets attenuation values + // must be halved to match the real OPL3 output. + double envelopeSustainLevel = sustainLevel / 2; + double envelopeTremolo = + OPL3::OPL3Data->tremoloTable[OPL3->dam][OPL3->tremoloIndex] / 2; + double envelopeAttenuation = attenuation / 2; + double envelopeTotalLevel = totalLevel / 2; + + double envelopeMinimum = -96; + double envelopeResolution = 0.1875; + + double outputEnvelope; + // + // Envelope Generation + // + switch(stage) { + case ATTACK: + // Since the attack is exponential, it will never reach 0 dB, so + // we´ll work with the next to maximum in the envelope resolution. + if(envelope<-envelopeResolution && xAttackIncrement != -EnvelopeGeneratorData::MUGEN) { + // The attack is exponential. +#if 0 + envelope = -pow(2.0,x); +#else + int index = xs_FloorToInt((x - ATTACK_MIN) / ATTACK_RES); + if (index < 0) + envelope = OPL3::OperatorData->attackTable[0]; + else if (index >= ATTACK_TABLE_SIZE) + envelope = OPL3::OperatorData->attackTable[ATTACK_TABLE_SIZE-1]; + else + envelope = OPL3::OperatorData->attackTable[index]; +#endif + x += xAttackIncrement; + break; + } + else { + // It is needed here to explicitly set envelope = 0, since + // only the attack can have a period of + // 0 seconds and produce an infinity envelope increment. + envelope = 0; + stage = DECAY; + } + case DECAY: + // The decay and release are linear. + if(envelope>envelopeSustainLevel) { + envelope -= dBdecayIncrement; + break; + } + else + stage = SUSTAIN; + case SUSTAIN: + // The Sustain stage is mantained all the time of the Key ON, + // even if we are in non-sustaining mode. + // This is necessary because, if the key is still pressed, we can + // change back and forth the state of EGT, and it will release and + // hold again accordingly. + if(egt==1) break; + else { + if(envelope > envelopeMinimum) + envelope -= dBreleaseIncrement; + else stage = OFF; + } + break; + case RELEASE: + // If we have Key OFF, only here we are in the Release stage. + // Now, we can turn EGT back and forth and it will have no effect,i.e., + // it will release inexorably to the Off stage. + if(envelope > envelopeMinimum) + envelope -= dBreleaseIncrement; + else stage = OFF; + case OFF: + break; + } + + // Ongoing original envelope + outputEnvelope = envelope; + + //Tremolo + if(am == 1) outputEnvelope += envelopeTremolo; + + //Attenuation + outputEnvelope += envelopeAttenuation; + + //Total Level + outputEnvelope += envelopeTotalLevel; + + return outputEnvelope; +} + +void EnvelopeGenerator::keyOn() { + // If we are taking it in the middle of a previous envelope, + // start to rise from the current level: + // envelope = - (2 ^ x); -> + // 2 ^ x = -envelope -> + // x = log2(-envelope); -> + double xCurrent = OperatorDataStruct::log2(-envelope); + x = xCurrent < xMinimumInAttack ? xCurrent : xMinimumInAttack; + stage = ATTACK; +} + +void EnvelopeGenerator::keyOff() { + if(stage != OFF) stage = RELEASE; +} + +double EnvelopeGenerator::dBtoX(double dB) { + return OperatorDataStruct::log2(-dB); +} + +double EnvelopeGenerator::percentageToDB(double percentage) { + return log10(percentage) * 10.0; +} + +double EnvelopeGenerator::percentageToX(double percentage) { + return dBtoX(percentageToDB(percentage)); +} + +PhaseGenerator::PhaseGenerator() { + phase = phaseIncrement = 0; +} + +void PhaseGenerator::setFrequency(int f_number, int block, int mult) { + // This frequency formula is derived from the following equation: + // f_number = baseFrequency * pow(2,19) / OPL_SAMPLE_RATE / pow(2,block-1); + double baseFrequency = + f_number * pow(2.0, block-1) * OPL_SAMPLE_RATE / pow(2.0,19); + double operatorFrequency = baseFrequency*OperatorDataStruct::multTable[mult]; + + // phase goes from 0 to 1 at + // period = (1/frequency) seconds -> + // Samples in each period is (1/frequency)*OPL_SAMPLE_RATE = + // = OPL_SAMPLE_RATE/frequency -> + // So the increment in each sample, to go from 0 to 1, is: + // increment = (1-0) / samples in the period -> + // increment = 1 / (OPL_SAMPLE_RATE/operatorFrequency) -> + phaseIncrement = operatorFrequency/OPL_SAMPLE_RATE; +} + +double PhaseGenerator::getPhase(OPL3 *OPL3, int vib) { + if(vib==1) + // phaseIncrement = (operatorFrequency * vibrato) / OPL_SAMPLE_RATE + phase += phaseIncrement*OPL3::OPL3Data->vibratoTable[OPL3->dvb][OPL3->vibratoIndex]; + else + // phaseIncrement = operatorFrequency / OPL_SAMPLE_RATE + phase += phaseIncrement; + // Originally clamped phase to [0,1), but that's not needed + return phase; +} + +void PhaseGenerator::keyOn() { + phase = 0; +} + +double RhythmChannel::getChannelOutput(OPL3 *OPL3) { + double channelOutput = 0, op1Output = 0, op2Output = 0; + + // Note that, different from the common channel, + // we do not check to see if the Operator's envelopes are Off. + // Instead, we always do the calculations, + // to update the publicly available phase. + op1Output = op1->getOperatorOutput(OPL3, Operator::noModulator); + op2Output = op2->getOperatorOutput(OPL3, Operator::noModulator); + channelOutput = (op1Output + op2Output) / 2; + + return channelOutput; +}; + +TopCymbalOperator::TopCymbalOperator(int baseAddress) +: Operator(baseAddress) +{ } + +TopCymbalOperator::TopCymbalOperator() +: Operator(topCymbalOperatorBaseAddress) +{ } + +double TopCymbalOperator::getOperatorOutput(OPL3 *OPL3, double modulator) { + double highHatOperatorPhase = + OPL3->highHatOperator.phase * OperatorDataStruct::multTable[OPL3->highHatOperator.mult]; + // The Top Cymbal operator uses its own phase together with the High Hat phase. + return getOperatorOutput(OPL3, modulator, highHatOperatorPhase); +} + +// This method is used here with the HighHatOperator phase +// as the externalPhase. +// Conversely, this method is also used through inheritance by the HighHatOperator, +// now with the TopCymbalOperator phase as the externalPhase. +double TopCymbalOperator::getOperatorOutput(OPL3 *OPL3, double modulator, double externalPhase) { + double envelopeInDB = envelopeGenerator.getEnvelope(OPL3, egt, am); + envelope = EnvelopeFromDB(envelopeInDB); + + phase = phaseGenerator.getPhase(OPL3, vib); + + int waveIndex = ws & ((OPL3->_new<<2) + 3); + double *waveform = OPL3::OperatorData->waveforms[waveIndex]; + + // Empirically tested multiplied phase for the Top Cymbal: + double carrierPhase = 8 * phase; + double modulatorPhase = externalPhase; + double modulatorOutput = getOutput(Operator::noModulator, modulatorPhase, waveform); + double carrierOutput = getOutput(modulatorOutput, carrierPhase, waveform); + + int cycles = 4; + double chopped = (carrierPhase * cycles) /* %cycles */; + chopped = chopped - floor(chopped / cycles) * cycles; + if( chopped > 0.1) carrierOutput = 0; + + return carrierOutput*2; +} + +HighHatOperator::HighHatOperator() +: TopCymbalOperator(highHatOperatorBaseAddress) +{ } + +double HighHatOperator::getOperatorOutput(OPL3 *OPL3, double modulator) { + double topCymbalOperatorPhase = + OPL3->topCymbalOperator.phase * OperatorDataStruct::multTable[OPL3->topCymbalOperator.mult]; + // The sound output from the High Hat resembles the one from + // Top Cymbal, so we use the parent method and modify its output + // accordingly afterwards. + double operatorOutput = TopCymbalOperator::getOperatorOutput(OPL3, modulator, topCymbalOperatorPhase); + if(operatorOutput == 0) operatorOutput = pr_opl3.GenRand_Real1()*envelope; + return operatorOutput; +} + +SnareDrumOperator::SnareDrumOperator() +: Operator(snareDrumOperatorBaseAddress) +{ } + +double SnareDrumOperator::getOperatorOutput(OPL3 *OPL3, double modulator) { + if(envelopeGenerator.stage == EnvelopeGenerator::OFF) return 0; + + double envelopeInDB = envelopeGenerator.getEnvelope(OPL3, egt, am); + envelope = EnvelopeFromDB(envelopeInDB); + + // If it is in OPL2 mode, use first four waveforms only: + int waveIndex = ws & ((OPL3->_new<<2) + 3); + double *waveform = OPL3::OperatorData->waveforms[waveIndex]; + + phase = OPL3->highHatOperator.phase * 2; + + double operatorOutput = getOutput(modulator, phase, waveform); + + double noise = pr_opl3.GenRand_Real1() * envelope; + + if(operatorOutput/envelope != 1 && operatorOutput/envelope != -1) { + if(operatorOutput > 0) operatorOutput = noise; + else if(operatorOutput < 0) operatorOutput = -noise; + else operatorOutput = 0; + } + + return operatorOutput*2; +} + +BassDrumChannel::BassDrumChannel(double startvol) +: Channel2op(bassDrumChannelBaseAddress, startvol, &my_op1, &my_op2), + my_op1(op1BaseAddress), my_op2(op2BaseAddress) +{ } + +double BassDrumChannel::getChannelOutput(OPL3 *OPL3) { + // Bass Drum ignores first operator, when it is in series. + if(cnt == 1) op1->ar=0; + return Channel2op::getChannelOutput(OPL3); +} + +void OPL3DataStruct::loadVibratoTable() { + + // According to the YMF262 datasheet, the OPL3 vibrato repetition rate is 6.1 Hz. + // According to the YMF278B manual, it is 6.0 Hz. + // The information that the vibrato table has 8 levels standing 1024 samples each + // was taken from the emulator by Jarek Burczynski and Tatsuyuki Satoh, + // with a frequency of 6,06689453125 Hz, what makes sense with the difference + // in the information on the datasheets. + + const double semitone = pow(2.0,1/12.0); + // A cent is 1/100 of a semitone: + const double cent = pow(semitone, 1/100.0); + + // When dvb=0, the depth is 7 cents, when it is 1, the depth is 14 cents. + const double DVB0 = pow(cent,7.0); + const double DVB1 = pow(cent,14.0); + int i; + for(i = 0; i<1024; i++) + vibratoTable[0][i] = vibratoTable[1][i] = 1; + for(;i<2048; i++) { + vibratoTable[0][i] = sqrt(DVB0); + vibratoTable[1][i] = sqrt(DVB1); + } + for(;i<3072; i++) { + vibratoTable[0][i] = DVB0; + vibratoTable[1][i] = DVB1; + } + for(;i<4096; i++) { + vibratoTable[0][i] = sqrt(DVB0); + vibratoTable[1][i] = sqrt(DVB1); + } + for(; i<5120; i++) + vibratoTable[0][i] = vibratoTable[1][i] = 1; + for(;i<6144; i++) { + vibratoTable[0][i] = 1/sqrt(DVB0); + vibratoTable[1][i] = 1/sqrt(DVB1); + } + for(;i<7168; i++) { + vibratoTable[0][i] = 1/DVB0; + vibratoTable[1][i] = 1/DVB1; + } + for(;i<8192; i++) { + vibratoTable[0][i] = 1/sqrt(DVB0); + vibratoTable[1][i] = 1/sqrt(DVB1); + } + +} + +void OPL3DataStruct::loadTremoloTable() +{ + // The tremolo depth is -1 dB when DAM = 0, and -4.8 dB when DAM = 1. + static const double tremoloDepth[] = {-1, -4.8}; + + // According to the YMF278B manual's OPL3 section graph, + // the tremolo waveform is not + // \ / a sine wave, but a single triangle waveform. + // \ / Thus, the period to achieve the tremolo depth is T/2, and + // \ / the increment in each T/2 section uses a frequency of 2*f. + // \/ Tremolo varies from 0 dB to depth, to 0 dB again, at frequency*2: + const double tremoloIncrement[] = { + calculateIncrement(tremoloDepth[0],0,1/(2*tremoloFrequency)), + calculateIncrement(tremoloDepth[1],0,1/(2*tremoloFrequency)) + }; + + int tremoloTableLength = (int)(OPL_SAMPLE_RATE/tremoloFrequency); + + // This is undocumented. The tremolo starts at the maximum attenuation, + // instead of at 0 dB: + tremoloTable[0][0] = tremoloDepth[0]; + tremoloTable[1][0] = tremoloDepth[1]; + int counter = 0; + // The first half of the triangle waveform: + while(tremoloTable[0][counter]<0) { + counter++; + tremoloTable[0][counter] = tremoloTable[0][counter-1] + tremoloIncrement[0]; + tremoloTable[1][counter] = tremoloTable[1][counter-1] + tremoloIncrement[1]; + } + // The second half of the triangle waveform: + while(tremoloTable[0][counter]>tremoloDepth[0] && counter> 8, reg & 0xFF, v); +} + +void OPL3::SetPanning(int c, float left, float right) +{ + if (FullPan) + { + Channel *channel; + + if (c < 9) + { + channel = channels[0][c]; + } + else + { + channel = channels[1][c - 9]; + } + channel->leftPan = left; + channel->rightPan = right; + } +} + +OPLEmul *JavaOPLCreate(bool stereo) +{ + return new OPL3(stereo); +} diff --git a/src/oplsynth/dosbox/opl.cpp b/src/oplsynth/dosbox/opl.cpp new file mode 100644 index 000000000..643414413 --- /dev/null +++ b/src/oplsynth/dosbox/opl.cpp @@ -0,0 +1,1447 @@ +/* + * Copyright (C) 2002-2011 The DOSBox Team + * OPL2/OPL3 emulation library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +/* + * Originally based on ADLIBEMU.C, an AdLib/OPL2 emulation library by Ken Silverman + * Copyright (C) 1998-2001 Ken Silverman + * Ken Silverman's official web site: "http://www.advsys.net/ken" + */ + +#include "doomtype.h" +#include "../opl.h" +#include "../muslib.h" +#include +#include "m_random.h" + +static FRandom pr_opl; + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef DWORD Bit32u; +typedef SDWORD Bit32s; +typedef WORD Bit16u; +typedef SWORD Bit16s; +typedef BYTE Bit8u; +typedef SBYTE Bit8s; + +#define OPLTYPE_IS_OPL3 +#undef PI + +#include "opl.h" + +static Bit16s wavtable[WAVEPREC*3]; // wave form table + +// key scale levels +static Bit8u kslev[8][16]; + +// key scale level lookup table +static const fltype kslmul[4] = { + 0.0, 0.5, 0.25, 1.0 // -> 0, 3, 1.5, 6 dB/oct +}; + +// frequency multiplicator lookup table +static const fltype frqmul_tab[16] = { + 0.5,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15 +}; + +// map a channel number to the register offset of the modulator (=register base) +static const Bit8u modulatorbase[9] = { + 0,1,2, + 8,9,10, + 16,17,18 +}; + +// map a register base to a modulator operator number or operator number +#if defined(OPLTYPE_IS_OPL3) +static const Bit8u regbase2modop[44] = { + 0,1,2,0,1,2,0,0,3,4,5,3,4,5,0,0,6,7,8,6,7,8, // first set + 18,19,20,18,19,20,0,0,21,22,23,21,22,23,0,0,24,25,26,24,25,26 // second set +}; +static const Bit8u regbase2op[44] = { + 0,1,2,9,10,11,0,0,3,4,5,12,13,14,0,0,6,7,8,15,16,17, // first set + 18,19,20,27,28,29,0,0,21,22,23,30,31,32,0,0,24,25,26,33,34,35 // second set +}; +#else +static const Bit8u regbase2modop[22] = { + 0,1,2,0,1,2,0,0,3,4,5,3,4,5,0,0,6,7,8,6,7,8 +}; +static const Bit8u regbase2op[22] = { + 0,1,2,9,10,11,0,0,3,4,5,12,13,14,0,0,6,7,8,15,16,17 +}; +#endif + + +// start of the waveform +static const Bit32u waveform[8] = { + WAVEPREC, + WAVEPREC>>1, + WAVEPREC, + (WAVEPREC*3)>>2, + 0, + 0, + (WAVEPREC*5)>>2, + WAVEPREC<<1 +}; + +// length of the waveform as mask +static const Bit32u wavemask[8] = { + WAVEPREC-1, + WAVEPREC-1, + (WAVEPREC>>1)-1, + (WAVEPREC>>1)-1, + WAVEPREC-1, + ((WAVEPREC*3)>>2)-1, + WAVEPREC>>1, + WAVEPREC-1 +}; + +// where the first entry resides +static const Bit32u wavestart[8] = { + 0, + WAVEPREC>>1, + 0, + WAVEPREC>>2, + 0, + 0, + 0, + WAVEPREC>>3 +}; + +// envelope generator function constants +static const fltype attackconst[4] = { + (fltype)(1/2.82624), + (fltype)(1/2.25280), + (fltype)(1/1.88416), + (fltype)(1/1.59744) +}; +static const fltype decrelconst[4] = { + (fltype)(1/39.28064), + (fltype)(1/31.41608), + (fltype)(1/26.17344), + (fltype)(1/22.44608) +}; + + +void operator_advance(op_type* op_pt, Bit32s vib) { + op_pt->wfpos = op_pt->tcount; // waveform position + + // advance waveform time + op_pt->tcount += op_pt->tinc; + op_pt->tcount += (Bit32s)(op_pt->tinc)*vib/FIXEDPT; + + op_pt->generator_pos += generator_add; +} + +void operator_advance_drums(op_type* op_pt1, Bit32s vib1, op_type* op_pt2, Bit32s vib2, op_type* op_pt3, Bit32s vib3) { + Bit32u c1 = op_pt1->tcount/FIXEDPT; + Bit32u c3 = op_pt3->tcount/FIXEDPT; + Bit32u phasebit = (((c1 & 0x88) ^ ((c1<<5) & 0x80)) | ((c3 ^ (c3<<2)) & 0x20)) ? 0x02 : 0x00; + + Bit32u noisebit = pr_opl.GenRand32() & 1; + + Bit32u snare_phase_bit = (Bit32u)(((Bitu)((op_pt1->tcount/FIXEDPT) / 0x100))&1); + + //Hihat + Bit32u inttm = (phasebit<<8) | (0x34<<(phasebit ^ (noisebit<<1))); + op_pt1->wfpos = inttm*FIXEDPT; // waveform position + // advance waveform time + op_pt1->tcount += op_pt1->tinc; + op_pt1->tcount += (Bit32s)(op_pt1->tinc)*vib1/FIXEDPT; + op_pt1->generator_pos += generator_add; + + //Snare + inttm = ((1+snare_phase_bit) ^ noisebit)<<8; + op_pt2->wfpos = inttm*FIXEDPT; // waveform position + // advance waveform time + op_pt2->tcount += op_pt2->tinc; + op_pt2->tcount += (Bit32s)(op_pt2->tinc)*vib2/FIXEDPT; + op_pt2->generator_pos += generator_add; + + //Cymbal + inttm = (1+phasebit)<<8; + op_pt3->wfpos = inttm*FIXEDPT; // waveform position + // advance waveform time + op_pt3->tcount += op_pt3->tinc; + op_pt3->tcount += (Bit32s)(op_pt3->tinc)*vib3/FIXEDPT; + op_pt3->generator_pos += generator_add; +} + + +// output level is sustained, mode changes only when operator is turned off (->release) +// or when the keep-sustained bit is turned off (->sustain_nokeep) +void operator_output(op_type* op_pt, Bit32s modulator, Bit32s trem) { + if (op_pt->op_state != OF_TYPE_OFF) { + op_pt->lastcval = op_pt->cval; + Bit32u i = (Bit32u)((op_pt->wfpos+modulator)/FIXEDPT); + + // wform: -16384 to 16383 (0x4000) + // trem : 32768 to 65535 (0x10000) + // step_amp: 0.0 to 1.0 + // vol : 1/2^14 to 1/2^29 (/0x4000; /1../0x8000) + + op_pt->cval = (Bit32s)(op_pt->step_amp*op_pt->vol*op_pt->cur_wform[i&op_pt->cur_wmask]*trem/16.0); + } +} + + +// no action, operator is off +void operator_off(op_type* /*op_pt*/) { +} + +// output level is sustained, mode changes only when operator is turned off (->release) +// or when the keep-sustained bit is turned off (->sustain_nokeep) +void operator_sustain(op_type* op_pt) { + Bit32u num_steps_add = op_pt->generator_pos/FIXEDPT; // number of (standardized) samples + for (Bit32u ct=0; ctcur_env_step++; + } + op_pt->generator_pos -= num_steps_add*FIXEDPT; +} + +// operator in release mode, if output level reaches zero the operator is turned off +void operator_release(op_type* op_pt) { + // ??? boundary? + if (op_pt->amp > 0.00000001) { + // release phase + op_pt->amp *= op_pt->releasemul; + } + + Bit32u num_steps_add = op_pt->generator_pos/FIXEDPT; // number of (standardized) samples + for (Bit32u ct=0; ctcur_env_step++; // sample counter + if ((op_pt->cur_env_step & op_pt->env_step_r)==0) { + if (op_pt->amp <= 0.00000001) { + // release phase finished, turn off this operator + op_pt->amp = 0.0; + if (op_pt->op_state == OF_TYPE_REL) { + op_pt->op_state = OF_TYPE_OFF; + } + } + op_pt->step_amp = op_pt->amp; + } + } + op_pt->generator_pos -= num_steps_add*FIXEDPT; +} + +// operator in decay mode, if sustain level is reached the output level is either +// kept (sustain level keep enabled) or the operator is switched into release mode +void operator_decay(op_type* op_pt) { + if (op_pt->amp > op_pt->sustain_level) { + // decay phase + op_pt->amp *= op_pt->decaymul; + } + + Bit32u num_steps_add = op_pt->generator_pos/FIXEDPT; // number of (standardized) samples + for (Bit32u ct=0; ctcur_env_step++; + if ((op_pt->cur_env_step & op_pt->env_step_d)==0) { + if (op_pt->amp <= op_pt->sustain_level) { + // decay phase finished, sustain level reached + if (op_pt->sus_keep) { + // keep sustain level (until turned off) + op_pt->op_state = OF_TYPE_SUS; + op_pt->amp = op_pt->sustain_level; + } else { + // next: release phase + op_pt->op_state = OF_TYPE_SUS_NOKEEP; + } + } + op_pt->step_amp = op_pt->amp; + } + } + op_pt->generator_pos -= num_steps_add*FIXEDPT; +} + +// operator in attack mode, if full output level is reached, +// the operator is switched into decay mode +void operator_attack(op_type* op_pt) { + op_pt->amp = ((op_pt->a3*op_pt->amp + op_pt->a2)*op_pt->amp + op_pt->a1)*op_pt->amp + op_pt->a0; + + Bit32u num_steps_add = op_pt->generator_pos/FIXEDPT; // number of (standardized) samples + for (Bit32u ct=0; ctcur_env_step++; // next sample + if ((op_pt->cur_env_step & op_pt->env_step_a)==0) { // check if next step already reached + if (op_pt->amp > 1.0) { + // attack phase finished, next: decay + op_pt->op_state = OF_TYPE_DEC; + op_pt->amp = 1.0; + op_pt->step_amp = 1.0; + } + op_pt->step_skip_pos_a <<= 1; + if (op_pt->step_skip_pos_a==0) op_pt->step_skip_pos_a = 1; + if (op_pt->step_skip_pos_a & op_pt->env_step_skip_a) { // check if required to skip next step + op_pt->step_amp = op_pt->amp; + } + } + } + op_pt->generator_pos -= num_steps_add*FIXEDPT; +} + + +typedef void (*optype_fptr)(op_type*); + +optype_fptr opfuncs[6] = { + operator_attack, + operator_decay, + operator_release, + operator_sustain, // sustain phase (keeping level) + operator_release, // sustain_nokeep phase (release-style) + operator_off +}; + +void DBOPL::change_attackrate(Bitu regbase, op_type* op_pt) { + Bits attackrate = adlibreg[ARC_ATTR_DECR+regbase]>>4; + if (attackrate) { + fltype f = (fltype)(pow(FL2,(fltype)attackrate+(op_pt->toff>>2)-1)*attackconst[op_pt->toff&3]*recipsamp); + // attack rate coefficients + op_pt->a0 = (fltype)(0.0377*f); + op_pt->a1 = (fltype)(10.73*f+1); + op_pt->a2 = (fltype)(-17.57*f); + op_pt->a3 = (fltype)(7.42*f); + + Bits step_skip = attackrate*4 + op_pt->toff; + Bits steps = step_skip >> 2; + op_pt->env_step_a = (1<<(steps<=12?12-steps:0))-1; + + Bits step_num = (step_skip<=48)?(4-(step_skip&3)):0; + static Bit8u step_skip_mask[5] = {0xff, 0xfe, 0xee, 0xba, 0xaa}; + op_pt->env_step_skip_a = step_skip_mask[step_num]; + +#if defined(OPLTYPE_IS_OPL3) + if (step_skip>=60) { +#else + if (step_skip>=62) { +#endif + op_pt->a0 = (fltype)(2.0); // something that triggers an immediate transition to amp:=1.0 + op_pt->a1 = (fltype)(0.0); + op_pt->a2 = (fltype)(0.0); + op_pt->a3 = (fltype)(0.0); + } + } else { + // attack disabled + op_pt->a0 = 0.0; + op_pt->a1 = 1.0; + op_pt->a2 = 0.0; + op_pt->a3 = 0.0; + op_pt->env_step_a = 0; + op_pt->env_step_skip_a = 0; + } +} + +void DBOPL::change_decayrate(Bitu regbase, op_type* op_pt) { + Bits decayrate = adlibreg[ARC_ATTR_DECR+regbase]&15; + // decaymul should be 1.0 when decayrate==0 + if (decayrate) { + fltype f = (fltype)(-7.4493*decrelconst[op_pt->toff&3]*recipsamp); + op_pt->decaymul = (fltype)(pow(FL2,f*pow(FL2,(fltype)(decayrate+(op_pt->toff>>2))))); + Bits steps = (decayrate*4 + op_pt->toff) >> 2; + op_pt->env_step_d = (1<<(steps<=12?12-steps:0))-1; + } else { + op_pt->decaymul = 1.0; + op_pt->env_step_d = 0; + } +} + +void DBOPL::change_releaserate(Bitu regbase, op_type* op_pt) { + Bits releaserate = adlibreg[ARC_SUSL_RELR+regbase]&15; + // releasemul should be 1.0 when releaserate==0 + if (releaserate) { + fltype f = (fltype)(-7.4493*decrelconst[op_pt->toff&3]*recipsamp); + op_pt->releasemul = (fltype)(pow(FL2,f*pow(FL2,(fltype)(releaserate+(op_pt->toff>>2))))); + Bits steps = (releaserate*4 + op_pt->toff) >> 2; + op_pt->env_step_r = (1<<(steps<=12?12-steps:0))-1; + } else { + op_pt->releasemul = 1.0; + op_pt->env_step_r = 0; + } +} + +void DBOPL::change_sustainlevel(Bitu regbase, op_type* op_pt) { + Bits sustainlevel = adlibreg[ARC_SUSL_RELR+regbase]>>4; + // sustainlevel should be 0.0 when sustainlevel==15 (max) + if (sustainlevel<15) { + op_pt->sustain_level = (fltype)(pow(FL2,(fltype)sustainlevel * (-FL05))); + } else { + op_pt->sustain_level = 0.0; + } +} + +void DBOPL::change_waveform(Bitu regbase, op_type* op_pt) { +#if defined(OPLTYPE_IS_OPL3) + if (regbase>=ARC_SECONDSET) regbase -= (ARC_SECONDSET-22); // second set starts at 22 +#endif + // waveform selection + op_pt->cur_wmask = wavemask[wave_sel[regbase]]; + op_pt->cur_wform = &wavtable[waveform[wave_sel[regbase]]]; + // (might need to be adapted to waveform type here...) +} + +void DBOPL::change_keepsustain(Bitu regbase, op_type* op_pt) { + op_pt->sus_keep = (adlibreg[ARC_TVS_KSR_MUL+regbase]&0x20)>0; + if (op_pt->op_state==OF_TYPE_SUS) { + if (!op_pt->sus_keep) op_pt->op_state = OF_TYPE_SUS_NOKEEP; + } else if (op_pt->op_state==OF_TYPE_SUS_NOKEEP) { + if (op_pt->sus_keep) op_pt->op_state = OF_TYPE_SUS; + } +} + +// enable/disable vibrato/tremolo LFO effects +void DBOPL::change_vibrato(Bitu regbase, op_type* op_pt) { + op_pt->vibrato = (adlibreg[ARC_TVS_KSR_MUL+regbase]&0x40)!=0; + op_pt->tremolo = (adlibreg[ARC_TVS_KSR_MUL+regbase]&0x80)!=0; +} + +// change amount of self-feedback +void DBOPL::change_feedback(Bitu chanbase, op_type* op_pt) { + Bits feedback = adlibreg[ARC_FEEDBACK+chanbase]&14; + if (feedback) op_pt->mfbi = (Bit32s)(pow(FL2,(fltype)((feedback>>1)+8))); + else op_pt->mfbi = 0; +} + +void DBOPL::change_frequency(Bitu chanbase, Bitu regbase, op_type* op_pt) { + // frequency + Bit32u frn = ((((Bit32u)adlibreg[ARC_KON_BNUM+chanbase])&3)<<8) + (Bit32u)adlibreg[ARC_FREQ_NUM+chanbase]; + // block number/octave + Bit32u oct = ((((Bit32u)adlibreg[ARC_KON_BNUM+chanbase])>>2)&7); + op_pt->freq_high = (Bit32s)((frn>>7)&7); + + // keysplit + Bit32u note_sel = (adlibreg[8]>>6)&1; + op_pt->toff = ((frn>>9)&(note_sel^1)) | ((frn>>8)¬e_sel); + op_pt->toff += (oct<<1); + + // envelope scaling (KSR) + if (!(adlibreg[ARC_TVS_KSR_MUL+regbase]&0x10)) op_pt->toff >>= 2; + + // 20+a0+b0: + op_pt->tinc = (Bit32u)((((fltype)(frn<>6]*kslev[oct][frn>>6]); + op_pt->vol = (fltype)(pow(FL2,(fltype)(vol_in * -0.125 - 14))); + + // operator frequency changed, care about features that depend on it + change_attackrate(regbase,op_pt); + change_decayrate(regbase,op_pt); + change_releaserate(regbase,op_pt); +} + +void DBOPL::enable_operator(Bitu regbase, op_type* op_pt, Bit32u act_type) { + // check if this is really an off-on transition + if (op_pt->act_state == OP_ACT_OFF) { + Bits wselbase = regbase; + if (wselbase>=ARC_SECONDSET) wselbase -= (ARC_SECONDSET-22); // second set starts at 22 + + op_pt->tcount = wavestart[wave_sel[wselbase]]*FIXEDPT; + + // start with attack mode + op_pt->op_state = OF_TYPE_ATT; + op_pt->act_state |= act_type; + } +} + +void DBOPL::disable_operator(op_type* op_pt, Bit32u act_type) { + // check if this is really an on-off transition + if (op_pt->act_state != OP_ACT_OFF) { + op_pt->act_state &= (~act_type); + if (op_pt->act_state == OP_ACT_OFF) { + if (op_pt->op_state != OF_TYPE_OFF) op_pt->op_state = OF_TYPE_REL; + } + } +} + +void DBOPL::Reset() { + Bit32u samplerate = (Bit32u)OPL_SAMPLE_RATE; + Bits i, j, oct; + + int_samplerate = samplerate; + + generator_add = (Bit32u)(INTFREQU*FIXEDPT/int_samplerate); + + + memset((void *)adlibreg,0,sizeof(adlibreg)); + memset((void *)op,0,sizeof(op_type)*MAXOPERATORS); + memset((void *)wave_sel,0,sizeof(wave_sel)); + + for (i=0;i=0;i--) { + frqmul[i] = (fltype)(frqmul_tab[i]*INTFREQU/(fltype)WAVEPREC*(fltype)FIXEDPT*recipsamp); + } + + status = 0; + opl_index = 0; + + + // create vibrato table + vib_table[0] = 8; + vib_table[1] = 4; + vib_table[2] = 0; + vib_table[3] = -4; + for (i=4; i(VIBTAB_SIZE*FIXEDPT_LFO/8192*INTFREQU/int_samplerate); + vibtab_pos = 0; + + for (i=0; i -0.5/6 to 0) + for (i=14; i<41; i++) trem_table_int[i] = Bit32s(-i+14); // downwards (26 to 0 -> 0 to -1/6) + for (i=41; i<53; i++) trem_table_int[i] = Bit32s(i-40-26); // upwards (1 to 12 -> -1/6 to -0.5/6) + + for (i=0; i>1);i++) { + wavtable[(i<<1) +WAVEPREC] = (Bit16s)(16384*sin((fltype)((i<<1) )*PI*2/WAVEPREC)); + wavtable[(i<<1)+1+WAVEPREC] = (Bit16s)(16384*sin((fltype)((i<<1)+1)*PI*2/WAVEPREC)); + wavtable[i] = wavtable[(i<<1) +WAVEPREC]; + // alternative: (zero-less) +/* wavtable[(i<<1) +WAVEPREC] = (Bit16s)(16384*sin((fltype)((i<<2)+1)*PI/WAVEPREC)); + wavtable[(i<<1)+1+WAVEPREC] = (Bit16s)(16384*sin((fltype)((i<<2)+3)*PI/WAVEPREC)); + wavtable[i] = wavtable[(i<<1)-1+WAVEPREC]; */ + } + for (i=0;i<(WAVEPREC>>3);i++) { + wavtable[i+(WAVEPREC<<1)] = wavtable[i+(WAVEPREC>>3)]-16384; + wavtable[i+((WAVEPREC*17)>>3)] = wavtable[i+(WAVEPREC>>2)]+16384; + } + + // key scale level table verified ([table in book]*8/3) + kslev[7][0] = 0; kslev[7][1] = 24; kslev[7][2] = 32; kslev[7][3] = 37; + kslev[7][4] = 40; kslev[7][5] = 43; kslev[7][6] = 45; kslev[7][7] = 47; + kslev[7][8] = 48; + for (i=9;i<16;i++) kslev[7][i] = (Bit8u)(i+41); + for (j=6;j>=0;j--) { + for (i=0;i<16;i++) { + oct = (Bits)kslev[j+1][i]-8; + if (oct < 0) oct = 0; + kslev[j][i] = (Bit8u)oct; + } + } + } + +} + + + +void DBOPL::WriteReg(int idx, int val) { + Bit32u second_set = (Bit32u)idx&0x100; + adlibreg[idx] = val; + + switch (idx&0xf0) { + case ARC_CONTROL: + // here we check for the second set registers, too: + switch (idx) { + case 0x02: // timer1 counter + case 0x03: // timer2 counter + break; + case 0x04: + // IRQ reset, timer mask/start + if (val&0x80) { + // clear IRQ bits in status register + status &= ~0x60; + } else { + status = 0; + } + break; +#if defined(OPLTYPE_IS_OPL3) + case 0x04|ARC_SECONDSET: + // 4op enable/disable switches for each possible channel + op[0].is_4op = (val&1)>0; + op[3].is_4op_attached = op[0].is_4op; + op[1].is_4op = (val&2)>0; + op[4].is_4op_attached = op[1].is_4op; + op[2].is_4op = (val&4)>0; + op[5].is_4op_attached = op[2].is_4op; + op[18].is_4op = (val&8)>0; + op[21].is_4op_attached = op[18].is_4op; + op[19].is_4op = (val&16)>0; + op[22].is_4op_attached = op[19].is_4op; + op[20].is_4op = (val&32)>0; + op[23].is_4op_attached = op[20].is_4op; + break; + case 0x05|ARC_SECONDSET: + break; +#endif + case 0x08: + // CSW, note select + break; + default: + break; + } + break; + case ARC_TVS_KSR_MUL: + case ARC_TVS_KSR_MUL+0x10: { + // tremolo/vibrato/sustain keeping enabled; key scale rate; frequency multiplication + int num = (int)idx&7; + Bitu base = (idx-ARC_TVS_KSR_MUL)&0xff; + if ((num<6) && (base<22)) { + Bitu modop = regbase2modop[second_set?(base+22):base]; + Bitu regbase = base+second_set; + Bitu chanbase = second_set?(modop-18+ARC_SECONDSET):modop; + + // change tremolo/vibrato and sustain keeping of this operator + op_type* op_ptr = &op[modop+((num<3) ? 0 : 9)]; + change_keepsustain(regbase,op_ptr); + change_vibrato(regbase,op_ptr); + + // change frequency calculations of this operator as + // key scale rate and frequency multiplicator can be changed +#if defined(OPLTYPE_IS_OPL3) + if ((adlibreg[0x105]&1) && (op[modop].is_4op_attached)) { + // operator uses frequency of channel + change_frequency(chanbase-3,regbase,op_ptr); + } else { + change_frequency(chanbase,regbase,op_ptr); + } +#else + change_frequency(chanbase,base,op_ptr); +#endif + } + } + break; + case ARC_KSL_OUTLEV: + case ARC_KSL_OUTLEV+0x10: { + // key scale level; output rate + int num = (int)idx&7; + Bitu base = (idx-ARC_KSL_OUTLEV)&0xff; + if ((num<6) && (base<22)) { + Bitu modop = regbase2modop[second_set?(base+22):base]; + Bitu chanbase = second_set?(modop-18+ARC_SECONDSET):modop; + + // change frequency calculations of this operator as + // key scale level and output rate can be changed + op_type* op_ptr = &op[modop+((num<3) ? 0 : 9)]; +#if defined(OPLTYPE_IS_OPL3) + Bitu regbase = base+second_set; + if ((adlibreg[0x105]&1) && (op[modop].is_4op_attached)) { + // operator uses frequency of channel + change_frequency(chanbase-3,regbase,op_ptr); + } else { + change_frequency(chanbase,regbase,op_ptr); + } +#else + change_frequency(chanbase,base,op_ptr); +#endif + } + } + break; + case ARC_ATTR_DECR: + case ARC_ATTR_DECR+0x10: { + // attack/decay rates + int num = (int)idx&7; + Bitu base = (idx-ARC_ATTR_DECR)&0xff; + if ((num<6) && (base<22)) { + Bitu regbase = base+second_set; + + // change attack rate and decay rate of this operator + op_type* op_ptr = &op[regbase2op[second_set?(base+22):base]]; + change_attackrate(regbase,op_ptr); + change_decayrate(regbase,op_ptr); + } + } + break; + case ARC_SUSL_RELR: + case ARC_SUSL_RELR+0x10: { + // sustain level; release rate + int num = (int)idx&7; + Bitu base = (idx-ARC_SUSL_RELR)&0xff; + if ((num<6) && (base<22)) { + Bitu regbase = base+second_set; + + // change sustain level and release rate of this operator + op_type* op_ptr = &op[regbase2op[second_set?(base+22):base]]; + change_releaserate(regbase,op_ptr); + change_sustainlevel(regbase,op_ptr); + } + } + break; + case ARC_FREQ_NUM: { + // 0xa0-0xa8 low8 frequency + Bitu base = (idx-ARC_FREQ_NUM)&0xff; + if (base<9) { + Bits opbase = second_set?(base+18):base; +#if defined(OPLTYPE_IS_OPL3) + if ((adlibreg[0x105]&1) && op[opbase].is_4op_attached) break; +#endif + // regbase of modulator: + Bits modbase = modulatorbase[base]+second_set; + + Bitu chanbase = base+second_set; + + change_frequency(chanbase,modbase,&op[opbase]); + change_frequency(chanbase,modbase+3,&op[opbase+9]); +#if defined(OPLTYPE_IS_OPL3) + // for 4op channels all four operators are modified to the frequency of the channel + if ((adlibreg[0x105]&1) && op[second_set?(base+18):base].is_4op) { + change_frequency(chanbase,modbase+8,&op[opbase+3]); + change_frequency(chanbase,modbase+3+8,&op[opbase+3+9]); + } +#endif + } + } + break; + case ARC_KON_BNUM: { + if (idx == ARC_PERC_MODE) { +#if defined(OPLTYPE_IS_OPL3) + if (second_set) return; +#endif + + if ((val&0x30) == 0x30) { // BassDrum active + enable_operator(16,&op[6],OP_ACT_PERC); + change_frequency(6,16,&op[6]); + enable_operator(16+3,&op[6+9],OP_ACT_PERC); + change_frequency(6,16+3,&op[6+9]); + } else { + disable_operator(&op[6],OP_ACT_PERC); + disable_operator(&op[6+9],OP_ACT_PERC); + } + if ((val&0x28) == 0x28) { // Snare active + enable_operator(17+3,&op[16],OP_ACT_PERC); + change_frequency(7,17+3,&op[16]); + } else { + disable_operator(&op[16],OP_ACT_PERC); + } + if ((val&0x24) == 0x24) { // TomTom active + enable_operator(18,&op[8],OP_ACT_PERC); + change_frequency(8,18,&op[8]); + } else { + disable_operator(&op[8],OP_ACT_PERC); + } + if ((val&0x22) == 0x22) { // Cymbal active + enable_operator(18+3,&op[8+9],OP_ACT_PERC); + change_frequency(8,18+3,&op[8+9]); + } else { + disable_operator(&op[8+9],OP_ACT_PERC); + } + if ((val&0x21) == 0x21) { // Hihat active + enable_operator(17,&op[7],OP_ACT_PERC); + change_frequency(7,17,&op[7]); + } else { + disable_operator(&op[7],OP_ACT_PERC); + } + + break; + } + // regular 0xb0-0xb8 + Bitu base = (idx-ARC_KON_BNUM)&0xff; + if (base<9) { + Bits opbase = second_set?(base+18):base; +#if defined(OPLTYPE_IS_OPL3) + if ((adlibreg[0x105]&1) && op[opbase].is_4op_attached) break; +#endif + // regbase of modulator: + Bits modbase = modulatorbase[base]+second_set; + + if (val&32) { + // operator switched on + enable_operator(modbase,&op[opbase],OP_ACT_NORMAL); // modulator (if 2op) + enable_operator(modbase+3,&op[opbase+9],OP_ACT_NORMAL); // carrier (if 2op) +#if defined(OPLTYPE_IS_OPL3) + // for 4op channels all four operators are switched on + if ((adlibreg[0x105]&1) && op[opbase].is_4op) { + // turn on chan+3 operators as well + enable_operator(modbase+8,&op[opbase+3],OP_ACT_NORMAL); + enable_operator(modbase+3+8,&op[opbase+3+9],OP_ACT_NORMAL); + } +#endif + } else { + // operator switched off + disable_operator(&op[opbase],OP_ACT_NORMAL); + disable_operator(&op[opbase+9],OP_ACT_NORMAL); +#if defined(OPLTYPE_IS_OPL3) + // for 4op channels all four operators are switched off + if ((adlibreg[0x105]&1) && op[opbase].is_4op) { + // turn off chan+3 operators as well + disable_operator(&op[opbase+3],OP_ACT_NORMAL); + disable_operator(&op[opbase+3+9],OP_ACT_NORMAL); + } +#endif + } + + Bitu chanbase = base+second_set; + + // change frequency calculations of modulator and carrier (2op) as + // the frequency of the channel has changed + change_frequency(chanbase,modbase,&op[opbase]); + change_frequency(chanbase,modbase+3,&op[opbase+9]); +#if defined(OPLTYPE_IS_OPL3) + // for 4op channels all four operators are modified to the frequency of the channel + if ((adlibreg[0x105]&1) && op[second_set?(base+18):base].is_4op) { + // change frequency calculations of chan+3 operators as well + change_frequency(chanbase,modbase+8,&op[opbase+3]); + change_frequency(chanbase,modbase+3+8,&op[opbase+3+9]); + } +#endif + } + } + break; + case ARC_FEEDBACK: { + // 0xc0-0xc8 feedback/modulation type (AM/FM) + Bitu base = (idx-ARC_FEEDBACK)&0xff; + if (base<9) { + Bits opbase = second_set?(base+18):base; + Bitu chanbase = base+second_set; + change_feedback(chanbase,&op[opbase]); +#if defined(OPLTYPE_IS_OPL3) + // OPL3 panning + if (!FullPan) + { + op[opbase].left_pan = (float)((val&0x10)>>4); + op[opbase].right_pan = (float)((val&0x20)>>5); + } +#endif + } + } + break; + case ARC_WAVE_SEL: + case ARC_WAVE_SEL+0x10: { + int num = (int)idx&7; + Bitu base = (idx-ARC_WAVE_SEL)&0xff; + if ((num<6) && (base<22)) { +#if defined(OPLTYPE_IS_OPL3) + Bits wselbase = second_set?(base+22):base; // for easier mapping onto wave_sel[] + // change waveform + if (adlibreg[0x105]&1) wave_sel[wselbase] = val&7; // opl3 mode enabled, all waveforms accessible + else wave_sel[wselbase] = val&3; + op_type* op_ptr = &op[regbase2modop[wselbase]+((num<3) ? 0 : 9)]; + change_waveform(wselbase,op_ptr); +#else + if (adlibreg[0x01]&0x20) { + // wave selection enabled, change waveform + wave_sel[base] = val&3; + op_type* op_ptr = &op[regbase2modop[base]+((num<3) ? 0 : 9)]; + change_waveform(base,op_ptr); + } +#endif + } + } + break; + default: + break; + } +} + +static void OPL_INLINE clipit16(float ival, float* outval) { + *outval += ival / 10240.f; +} + + + +// be careful with this +// uses cptr and chanval, outputs into outbufl(/outbufr) +// for opl3 check if opl3-mode is enabled (which uses stereo panning) +#undef CHANVAL_OUT +#if defined(OPLTYPE_IS_OPL3) +#define CHANVAL_OUT \ + if (adlibreg[0x105]&1) { \ + outbufl[i] += chanval*cptr[0].left_pan; \ + outbufr[i] += chanval*cptr[0].right_pan; \ + } else { \ + outbufl[i] += chanval; \ + } +#else +#define CHANVAL_OUT \ + outbufl[i] += chanval; +#endif + +void DBOPL::Update(float* sndptr, int numsamples) { + Bits i, endsamples; + op_type* cptr; + + float outbufl[BLOCKBUF_SIZE]; +#if defined(OPLTYPE_IS_OPL3) + // second output buffer (right channel for opl3 stereo) + float outbufr[BLOCKBUF_SIZE]; +#endif + + // vibrato/tremolo lookup tables (global, to possibly be used by all operators) + Bit32s vib_lut[BLOCKBUF_SIZE]; + Bit32s trem_lut[BLOCKBUF_SIZE]; + + Bits samples_to_process = numsamples; + + for (Bits cursmp=0; cursmpBLOCKBUF_SIZE) endsamples = BLOCKBUF_SIZE; + + memset((void*)&outbufl,0,endsamples*sizeof(Bit32s)); +#if defined(OPLTYPE_IS_OPL3) + // clear second output buffer (opl3 stereo) + if (adlibreg[0x105]&1) memset((void*)&outbufr,0,endsamples*sizeof(Bit32s)); +#endif + + // calculate vibrato/tremolo lookup tables + Bit32s vib_tshift = ((adlibreg[ARC_PERC_MODE]&0x40)==0) ? 1 : 0; // 14cents/7cents switching + for (i=0;i=VIBTAB_SIZE) vibtab_pos-=VIBTAB_SIZE*FIXEDPT_LFO; + vib_lut[i] = vib_table[vibtab_pos/FIXEDPT_LFO]>>vib_tshift; // 14cents (14/100 of a semitone) or 7cents + + // cycle through tremolo table + tremtab_pos += tremtab_add; + if (tremtab_pos/FIXEDPT_LFO>=TREMTAB_SIZE) tremtab_pos-=TREMTAB_SIZE*FIXEDPT_LFO; + if (adlibreg[ARC_PERC_MODE]&0x80) trem_lut[i] = trem_table[tremtab_pos/FIXEDPT_LFO]; + else trem_lut[i] = trem_table[TREMTAB_SIZE+tremtab_pos/FIXEDPT_LFO]; + } + + if (adlibreg[ARC_PERC_MODE]&0x20) { + //BassDrum + cptr = &op[6]; + if (adlibreg[ARC_FEEDBACK+6]&1) { + // additive synthesis + if (cptr[9].op_state != OF_TYPE_OFF) { + if (cptr[9].vibrato) { + vibval1 = vibval_var1; + for (i=0;i=0; cur_ch--) { + // skip drum/percussion operators + if ((adlibreg[ARC_PERC_MODE]&0x20) && (cur_ch >= 6) && (cur_ch < 9)) continue; + + Bitu k = cur_ch; +#if defined(OPLTYPE_IS_OPL3) + if (cur_ch < 9) { + cptr = &op[cur_ch]; + } else { + cptr = &op[cur_ch+9]; // second set is operator18-operator35 + k += (-9+256); // second set uses registers 0x100 onwards + } + // check if this operator is part of a 4-op + if ((adlibreg[0x105]&1) && cptr->is_4op_attached) continue; +#else + cptr = &op[cur_ch]; +#endif + + // check for FM/AM + if (adlibreg[ARC_FEEDBACK+k]&1) { +#if defined(OPLTYPE_IS_OPL3) + if ((adlibreg[0x105]&1) && cptr->is_4op) { + if (adlibreg[ARC_FEEDBACK+k+3]&1) { + // AM-AM-style synthesis (op1[fb] + (op2 * op3) + op4) + if (cptr[0].op_state != OF_TYPE_OFF) { + if (cptr[0].vibrato) { + vibval1 = vibval_var1; + for (i=0;iis_4op) { + if (adlibreg[ARC_FEEDBACK+k+3]&1) { + // FM-AM-style synthesis ((op1[fb] * op2) + (op3 * op4)) + if ((cptr[0].op_state != OF_TYPE_OFF) || (cptr[9].op_state != OF_TYPE_OFF)) { + if ((cptr[0].vibrato) && (cptr[0].op_state != OF_TYPE_OFF)) { + vibval1 = vibval_var1; + for (i=0;istereo) + for (i=0;istereo) + for (i=0;i= 9) + { + c += 9; + } + op[c].left_pan = left; + op[c].right_pan = right; + } +} + +DBOPL::DBOPL(bool fullpan) +{ + FullPan = fullpan; + Reset(); +} + +OPLEmul *DBOPLCreate(bool fullpan) +{ + return new DBOPL(fullpan); +} diff --git a/src/oplsynth/dosbox/opl.h b/src/oplsynth/dosbox/opl.h new file mode 100644 index 000000000..b4d7548ad --- /dev/null +++ b/src/oplsynth/dosbox/opl.h @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2002-2011 The DOSBox Team + * OPL2/OPL3 emulation library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +/* + * Originally based on ADLIBEMU.C, an AdLib/OPL2 emulation library by Ken Silverman + * Copyright (C) 1998-2001 Ken Silverman + * Ken Silverman's official web site: "http://www.advsys.net/ken" + */ + + +#define fltype double + +/* + define Bits, Bitu, Bit32s, Bit32u, Bit16s, Bit16u, Bit8s, Bit8u here +*/ +/* +#include +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; +*/ + + +/* + define attribution that inlines/forces inlining of a function (optional) +*/ +#define OPL_INLINE inline + + +#undef NUM_CHANNELS +#if defined(OPLTYPE_IS_OPL3) +#define NUM_CHANNELS 18 +#else +#define NUM_CHANNELS 9 +#endif + +#define MAXOPERATORS (NUM_CHANNELS*2) + + +#define FL05 ((fltype)0.5) +#define FL2 ((fltype)2.0) +#define PI ((fltype)3.1415926535897932384626433832795) + + +#define FIXEDPT 0x10000 // fixed-point calculations using 16+16 +#define FIXEDPT_LFO 0x1000000 // fixed-point calculations using 8+24 + +#define WAVEPREC 1024 // waveform precision (10 bits) + +#define INTFREQU ((fltype)(14318180.0 / 288.0)) // clocking of the chip + + +#define OF_TYPE_ATT 0 +#define OF_TYPE_DEC 1 +#define OF_TYPE_REL 2 +#define OF_TYPE_SUS 3 +#define OF_TYPE_SUS_NOKEEP 4 +#define OF_TYPE_OFF 5 + +#define ARC_CONTROL 0x00 +#define ARC_TVS_KSR_MUL 0x20 +#define ARC_KSL_OUTLEV 0x40 +#define ARC_ATTR_DECR 0x60 +#define ARC_SUSL_RELR 0x80 +#define ARC_FREQ_NUM 0xa0 +#define ARC_KON_BNUM 0xb0 +#define ARC_PERC_MODE 0xbd +#define ARC_FEEDBACK 0xc0 +#define ARC_WAVE_SEL 0xe0 + +#define ARC_SECONDSET 0x100 // second operator set for OPL3 + + +#define OP_ACT_OFF 0x00 +#define OP_ACT_NORMAL 0x01 // regular channel activated (bitmasked) +#define OP_ACT_PERC 0x02 // percussion channel activated (bitmasked) + +#define BLOCKBUF_SIZE 512 + + +// vibrato constants +#define VIBTAB_SIZE 8 +#define VIBFAC 70/50000 // no braces, integer mul/div + +// tremolo constants and table +#define TREMTAB_SIZE 53 +#define TREM_FREQ ((fltype)(3.7)) // tremolo at 3.7hz + + +/* operator struct definition + For OPL2 all 9 channels consist of two operators each, carrier and modulator. + Channel x has operators x as modulator and operators (9+x) as carrier. + For OPL3 all 18 channels consist either of two operators (2op mode) or four + operators (4op mode) which is determined through register4 of the second + adlib register set. + Only the channels 0,1,2 (first set) and 9,10,11 (second set) can act as + 4op channels. The two additional operators for a channel y come from the + 2op channel y+3 so the operatorss y, (9+y), y+3, (9+y)+3 make up a 4op + channel. +*/ +typedef struct operator_struct { + Bit32s cval, lastcval; // current output/last output (used for feedback) + Bit32u tcount, wfpos, tinc; // time (position in waveform) and time increment + fltype amp, step_amp; // and amplification (envelope) + fltype vol; // volume + fltype sustain_level; // sustain level + Bit32s mfbi; // feedback amount + fltype a0, a1, a2, a3; // attack rate function coefficients + fltype decaymul, releasemul; // decay/release rate functions + Bit32u op_state; // current state of operator (attack/decay/sustain/release/off) + Bit32u toff; + Bit32s freq_high; // highest three bits of the frequency, used for vibrato calculations + Bit16s* cur_wform; // start of selected waveform + Bit32u cur_wmask; // mask for selected waveform + Bit32u act_state; // activity state (regular, percussion) + bool sus_keep; // keep sustain level when decay finished + bool vibrato,tremolo; // vibrato/tremolo enable bits + + // variables used to provide non-continuous envelopes + Bit32u generator_pos; // for non-standard sample rates we need to determine how many samples have passed + Bits cur_env_step; // current (standardized) sample position + Bits env_step_a,env_step_d,env_step_r; // number of std samples of one step (for attack/decay/release mode) + Bit8u step_skip_pos_a; // position of 8-cyclic step skipping (always 2^x to check against mask) + Bits env_step_skip_a; // bitmask that determines if a step is skipped (respective bit is zero then) + +#if defined(OPLTYPE_IS_OPL3) + bool is_4op,is_4op_attached; // base of a 4op channel/part of a 4op channel + float left_pan,right_pan; // opl3 stereo panning amount +#endif +} op_type; + +// per-chip variables +class DBOPL : public OPLEmul +{ +private: + Bitu chip_num; + op_type op[MAXOPERATORS]; + + Bits int_samplerate; + + Bit8u status; + Bit32u opl_index; +#if defined(OPLTYPE_IS_OPL3) + Bit8u adlibreg[512]; // adlib register set (including second set) + Bit8u wave_sel[44]; // waveform selection +#else + Bit8u adlibreg[256]; // adlib register set + Bit8u wave_sel[22]; // waveform selection +#endif + + fltype recipsamp; // inverse of sampling rate + + // vibrato/tremolo tables + Bit32s vib_table[VIBTAB_SIZE]; + Bit32s trem_table[TREMTAB_SIZE*2]; + + Bit32s vibval_const[BLOCKBUF_SIZE]; + Bit32s tremval_const[BLOCKBUF_SIZE]; + + // vibrato value tables (used per-operator) + Bit32s vibval_var1[BLOCKBUF_SIZE]; + Bit32s vibval_var2[BLOCKBUF_SIZE]; + + // vibrato/trmolo value table pointers + Bit32s *vibval1, *vibval2, *vibval3, *vibval4; + Bit32s *tremval1, *tremval2, *tremval3, *tremval4; + + // calculated frequency multiplication values (depend on sampling rate) + fltype frqmul[16]; + + + // vibrato/tremolo increment/counter + Bit32u vibtab_pos; + Bit32u vibtab_add; + Bit32u tremtab_pos; + Bit32u tremtab_add; + + // Enable full MIDI panning; disable OPL3 panning + bool FullPan; + + + // enable an operator + void enable_operator(Bitu regbase, op_type* op_pt, Bit32u act_type); + + void disable_operator(op_type* op_pt, Bit32u act_type); + + // functions to change parameters of an operator + void change_frequency(Bitu chanbase, Bitu regbase, op_type* op_pt); + + void change_attackrate(Bitu regbase, op_type* op_pt); + void change_decayrate(Bitu regbase, op_type* op_pt); + void change_releaserate(Bitu regbase, op_type* op_pt); + void change_sustainlevel(Bitu regbase, op_type* op_pt); + void change_waveform(Bitu regbase, op_type* op_pt); + void change_keepsustain(Bitu regbase, op_type* op_pt); + void change_vibrato(Bitu regbase, op_type* op_pt); + void change_feedback(Bitu chanbase, op_type* op_pt); + + // general functions +public: + void Reset(); + void Update(float* sndptr, int numsamples); + void WriteReg(int idx, int val); + void SetPanning(int c, float left, float right); + + DBOPL(bool stereo); +}; + +static Bit32u generator_add; // should be a chip parameter diff --git a/src/oplsynth/fmopl.cpp b/src/oplsynth/fmopl.cpp index 40efe3164..4ecd03b14 100644 --- a/src/oplsynth/fmopl.cpp +++ b/src/oplsynth/fmopl.cpp @@ -100,7 +100,18 @@ Revision History: #include #include //#include "driver.h" /* use M.A.M.E. */ -#include "fmopl.h" +#include "opl.h" + +/* compiler dependence */ +#ifndef OSD_CPU_H +#define OSD_CPU_H +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +#endif #ifndef PI #define PI 3.14159265358979323846 @@ -115,7 +126,6 @@ Revision History: #define INLINE __inline #endif - #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (EG timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ @@ -152,47 +162,11 @@ Revision History: #define EG_REL 1 #define EG_OFF 0 -/* save output as raw 16-bit sample */ - -/*#define SAVE_SAMPLE*/ - -#ifdef SAVE_SAMPLE -static FILE *sample[1]; - #if 1 /*save to MONO file */ - #define SAVE_ALL_CHANNELS \ - { signed int pom = lt; \ - fputc((unsigned short)pom&0xff,sample[0]); \ - fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ - } - #else /*save to STEREO file */ - #define SAVE_ALL_CHANNELS \ - { signed int pom = lt; \ - fputc((unsigned short)pom&0xff,sample[0]); \ - fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ - pom = rt; \ - fputc((unsigned short)pom&0xff,sample[0]); \ - fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ - } - #endif -#endif - -/* #define LOG_CYM_FILE */ -#ifdef LOG_CYM_FILE - FILE * cymfile = NULL; -#endif - - - -#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ -#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ -#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ -#define OPL_TYPE_IO 0x08 /* I/O port */ - -/* ---------- Generic interface section ---------- */ -#define OPL_TYPE_YM3526 (0) -#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) -#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) +#define OPL_CLOCK 3579545 // master clock (Hz) +#define OPL_RATE 49716 // sampling rate (Hz) +#define OPL_TIMERBASE (OPL_CLOCK / 72.0) // Timer base time (==sampling time) +#define OPL_FREQBASE (OPL_TIMERBASE / OPL_RATE) // frequency base /* Saving is necessary for member of the 'R' mark for suspend/resume */ @@ -246,6 +220,8 @@ typedef struct{ UINT32 fc; /* Freq. Increment base */ UINT32 ksl_base; /* KeyScaleLevel Base step */ UINT8 kcode; /* key code (for key scaling) */ + float LeftVol; /* volumes for stereo panning */ + float RightVol; } OPL_CH; /* OPL state */ @@ -279,24 +255,12 @@ typedef struct fm_opl_f { int T[2]; /* timer counters */ UINT8 st[2]; /* timer enable */ - /* external event callback handlers */ - OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ - int TimerParam; /* TIMER parameter */ - OPL_IRQHANDLER IRQHandler; /* IRQ handler */ - int IRQParam; /* IRQ parameter */ - OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */ - int UpdateParam; /* stream update parameter */ - - UINT8 type; /* chip type */ UINT8 address; /* address register */ UINT8 status; /* status flag */ UINT8 statusmask; /* status mask */ UINT8 mode; /* Reg.08 : CSM,notesel,etc. */ - int clock; /* master clock (Hz) */ - int rate; /* sampling rate (Hz) */ - double freqbase; /* frequency base */ - double TimerBase; /* Timer base time (==sampling time)*/ + bool IsStereo; /* Write stereo output */ } FM_OPL; @@ -632,8 +596,6 @@ INLINE void OPL_STATUS_SET(FM_OPL *OPL,int flag) if(OPL->status & OPL->statusmask) { /* IRQ on */ OPL->status |= 0x80; - /* callback user interrupt handler (IRQ is OFF to ON) */ - if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); } } } @@ -648,8 +610,6 @@ INLINE void OPL_STATUS_RESET(FM_OPL *OPL,int flag) if (!(OPL->status & OPL->statusmask) ) { OPL->status &= 0x7f; - /* callback user interrupt handler (IRQ is ON to OFF) */ - if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); } } } @@ -876,7 +836,7 @@ INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsign #define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (LFO_AM & (OP)->AMmask)) /* calculate output */ -INLINE void OPL_CALC_CH( OPL_CH *CH, float *buffer ) +INLINE float OPL_CALC_CH( OPL_CH *CH ) { OPL_SLOT *SLOT; unsigned int env; @@ -905,8 +865,9 @@ INLINE void OPL_CALC_CH( OPL_CH *CH, float *buffer ) { output += op_calc(SLOT->Cnt, env, phase_modulation, SLOT->wavetable); /* [RH] Convert to floating point. */ - *buffer += float(output) / 10240; + return float(output) / 10240; } + return 0; } /* @@ -1113,12 +1074,19 @@ INLINE void OPL_CALC_RH( OPL_CH *CH, unsigned int noise ) /* generic table initialize */ -static int init_tables(void) +static void init_tables(void) { signed int i,x; signed int n; double o,m; + /* We only need to do this once. */ + static bool did_init = false; + + if (did_init) + { + return; + } for (x=0; x>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 ]>>i; } - #if 0 - logerror("tl %04i", x*2); - for (i=0; i<12; i++) - logerror(", [%02i] %5i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ] ); - logerror("\n"); - #endif } - /*logerror("FMOPL.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ - for (i=0; i>1; sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); - - /*logerror("FMOPL.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ } for (i=0; i>2)]; - - /*logerror("FMOPL.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); - logerror("FMOPL.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); - logerror("FMOPL.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] );*/ } - /*logerror("FMOPL.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ - -#ifdef SAVE_SAMPLE - sample[0]=fopen("sampsum.pcm","wb"); -#endif - - return 1; + did_init = true; } -static void OPLCloseTable( void ) -{ -#ifdef SAVE_SAMPLE - fclose(sample[0]); -#endif -} - - - static void OPL_initalize(FM_OPL *OPL) { int i; - /* frequency base */ - OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate : 0; -#if 0 - OPL->rate = (double)OPL->clock / 72.0; - OPL->freqbase = 1.0; -#endif - - /* Timer base time */ - OPL->TimerBase = 1.0 / ((double)OPL->clock / 72.0 ); - /* make fnumber -> increment counter table */ for( i=0 ; i < 1024 ; i++ ) { /* opn phase increment counter = 20bit */ - OPL->fn_tab[i] = (UINT32)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ -#if 0 - logerror("FMOPL.C: fn_tab[%4i] = %08x (dec=%8i)\n", - i, OPL->fn_tab[i]>>6, OPL->fn_tab[i]>>6 ); -#endif + OPL->fn_tab[i] = (UINT32)( (double)i * 64 * OPL_FREQBASE * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ } -#if 0 - for( i=0 ; i < 16 ; i++ ) - { - logerror("FMOPL.C: sl_tab[%i] = %08x\n", - i, sl_tab[i] ); - } - for( i=0 ; i < 8 ; i++ ) - { - int j; - logerror("FMOPL.C: ksl_tab[oct=%2i] =",i); - for (j=0; j<16; j++) - { - logerror("%08x ", ksl_tab[i*16+j] ); - } - logerror("\n"); - } -#endif - - /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ /* One entry from LFO_AM_TABLE lasts for 64 samples */ - OPL->lfo_am_inc = UINT32((1.0 / 64.0 ) * (1<freqbase); + OPL->lfo_am_inc = UINT32((1.0 / 64.0 ) * (1<lfo_pm_inc = UINT32((1.0 / 1024.0) * (1<freqbase); + OPL->lfo_pm_inc = UINT32((1.0 / 1024.0) * (1<lfo_am_inc = %8x ; OPL->lfo_pm_inc = %8x\n", OPL->lfo_am_inc, OPL->lfo_pm_inc);*/ - - OPL->eg_timer_add = UINT32((1<freqbase); + OPL->eg_timer_add = UINT32((1<eg_timer_overflow = UINT32(( 1 ) * (1<eg_timer_add, OPL->eg_timer_overflow);*/ + // [RH] Support full MIDI panning. (But default to mono and center panning.) + OPL->IsStereo = false; + for (int i = 0; i < 9; ++i) + { + OPL->P_CH[i].LeftVol = (float)CENTER_PANNING_POWER; + OPL->P_CH[i].RightVol = (float)CENTER_PANNING_POWER; + } } INLINE void FM_KEYON(OPL_SLOT *SLOT, UINT32 key_set) @@ -1414,31 +1324,17 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) int slot; int block_fnum; - /* adjust bus to 8 bits */ r &= 0xff; v &= 0xff; -#ifdef LOG_CYM_FILE - if ((cymfile) && (r!=0) ) - { - fputc( (unsigned char)r, cymfile ); - fputc( (unsigned char)v, cymfile ); - } -#endif - - switch(r&0xe0) { case 0x00: /* 00-1f:control */ switch(r&0x1f) { case 0x01: /* waveform select enable */ - if(OPL->type&OPL_TYPE_WAVESEL) - { - OPL->wavesel = v&0x20; - /* do not change the waveform previously selected */ - } + OPL->wavesel = v&0x20; break; case 0x02: /* Timer 1 */ OPL->T[0] = (256-v)*4; @@ -1462,16 +1358,12 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) /* timer 2 */ if(OPL->st[1] != st2) { - double interval = st2 ? (double)OPL->T[1]*OPL->TimerBase : 0.0; OPL->st[1] = st2; - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+1,interval); } /* timer 1 */ if(OPL->st[0] != st1) { - double interval = st1 ? (double)OPL->T[0]*OPL->TimerBase : 0.0; OPL->st[0] = st1; - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+0,interval); } } break; @@ -1624,58 +1516,6 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) } } -#ifdef LOG_CYM_FILE -static void cymfile_callback (int n) -{ - if (cymfile) - { - fputc( (unsigned char)0, cymfile ); - } -} -#endif - -/* lock/unlock for common table */ -static int OPL_LockTable(void) -{ - num_lock++; - if(num_lock>1) return 0; - - /* first time */ - - /* allocate total level table (128kb space) */ - if( !init_tables() ) - { - num_lock--; - return -1; - } - -#ifdef LOG_CYM_FILE - cymfile = fopen("3812_.cym","wb"); - if (cymfile) - timer_pulse ( TIME_IN_HZ(110), 0, cymfile_callback); /*110 Hz pulse timer*/ - else - logerror("Could not create file 3812_.cym\n"); -#endif - - return 0; -} - -static void OPL_UnLockTable(void) -{ - if(num_lock) num_lock--; - if(num_lock) return; - - /* last time */ - - OPLCloseTable(); - -#ifdef LOG_CYM_FILE - fclose (cymfile); - cymfile = NULL; -#endif - -} - static void OPLResetChip(FM_OPL *OPL) { int c,s; @@ -1709,246 +1549,123 @@ static void OPLResetChip(FM_OPL *OPL) } } -/* Create one of virtual YM3812 */ -/* 'clock' is chip clock in Hz */ -/* 'rate' is sampling rate */ -static FM_OPL *OPLCreate(int type, int clock, int rate) + +class YM3812 : public OPLEmul { - char *ptr; - FM_OPL *OPL; - int state_size; +private: + FM_OPL Chip; - if (OPL_LockTable() ==-1) return NULL; - - /* calculate OPL state size */ - state_size = sizeof(FM_OPL); - - /* allocate memory block */ - ptr = (char *)malloc(state_size); - - if (ptr==NULL) - return NULL; - - /* clear */ - memset(ptr,0,state_size); - - OPL = (FM_OPL *)ptr; - - ptr += sizeof(FM_OPL); - - OPL->type = type; - OPL->clock = clock; - OPL->rate = rate; - - /* init global tables */ - OPL_initalize(OPL); - - return OPL; -} - -/* Destroy one of virtual YM3812 */ -static void OPLDestroy(FM_OPL *OPL) -{ - OPL_UnLockTable(); - free(OPL); -} - -/* Optional handlers */ - -static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset) -{ - OPL->TimerHandler = TimerHandler; - OPL->TimerParam = channelOffset; -} -static void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param) -{ - OPL->IRQHandler = IRQHandler; - OPL->IRQParam = param; -} -static void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param) -{ - OPL->UpdateHandler = UpdateHandler; - OPL->UpdateParam = param; -} - -/* YM3812 I/O interface */ -static int OPLWrite(FM_OPL *OPL,int a,int v) -{ - if( !(a&1) ) - { /* address port */ - OPL->address = v & 0xff; - } - else - { /* data port */ - if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); - OPLWriteReg(OPL,OPL->address,v); - } - return OPL->status>>7; -} - -static unsigned char OPLRead(FM_OPL *OPL,int a) -{ - if( !(a&1) ) +public: + /* Create one of virtual YM3812 */ + YM3812(bool stereo) { - /* status port */ - /* OPL and OPL2 */ - return OPL->status & (OPL->statusmask|0x80); + init_tables(); + + /* clear */ + memset(&Chip, 0, sizeof(Chip)); + + /* init global tables */ + OPL_initalize(&Chip); + + Chip.IsStereo = stereo; + + Reset(); } - return 0xff; -} - -/* CSM Key Controll */ -INLINE void CSMKeyControll(OPL_CH *CH) -{ - FM_KEYON (&CH->SLOT[SLOT1], 4); - FM_KEYON (&CH->SLOT[SLOT2], 4); - - /* The key off should happen exactly one sample later - not implemented correctly yet */ - - FM_KEYOFF(&CH->SLOT[SLOT1], ~4); - FM_KEYOFF(&CH->SLOT[SLOT2], ~4); -} - - -static int OPLTimerOver(FM_OPL *OPL,int c) -{ - if( c ) - { /* Timer B */ - OPL_STATUS_SET(OPL,0x20); + /* YM3812 I/O interface */ + void WriteReg(int reg, int v) + { + OPLWriteReg(&Chip, reg & 0xff, v); } - else - { /* Timer A */ - OPL_STATUS_SET(OPL,0x40); - /* CSM mode key,TL controll */ - if( OPL->mode & 0x80 ) - { /* CSM mode total level latch and auto key on */ - int ch; - if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); - for(ch=0; ch<9; ch++) - CSMKeyControll( &OPL->P_CH[ch] ); + + void Reset() + { + OPLResetChip(&Chip); + } + + /* [RH] Full support for MIDI panning */ + void SetPanning(int c, float left, float right) + { + Chip.P_CH[c].LeftVol = left; + Chip.P_CH[c].RightVol = right; + } + + + /* + ** Generate samples for one of the YM3812's + ** + ** '*buffer' is the output buffer pointer + ** 'length' is the number of samples that should be generated + */ + void Update(float *buffer, int length) + { + int i; + + UINT8 rhythm = Chip.rhythm&0x20; + + UINT32 lfo_am_cnt_bak = Chip.lfo_am_cnt; + UINT32 eg_timer_bak = Chip.eg_timer; + UINT32 eg_cnt_bak = Chip.eg_cnt; + + UINT32 lfo_am_cnt_out = lfo_am_cnt_bak; + UINT32 eg_timer_out = eg_timer_bak; + UINT32 eg_cnt_out = eg_cnt_bak; + + for (i = 0; i <= (rhythm ? 5 : 8); ++i) + { + Chip.lfo_am_cnt = lfo_am_cnt_bak; + Chip.eg_timer = eg_timer_bak; + Chip.eg_cnt = eg_cnt_bak; + if (CalcVoice (&Chip, i, buffer, length)) + { + lfo_am_cnt_out = Chip.lfo_am_cnt; + eg_timer_out = Chip.eg_timer; + eg_cnt_out = Chip.eg_cnt; + } + } + + Chip.lfo_am_cnt = lfo_am_cnt_out; + Chip.eg_timer = eg_timer_out; + Chip.eg_cnt = eg_cnt_out; + + if (rhythm) /* Rhythm part */ + { + Chip.lfo_am_cnt = lfo_am_cnt_bak; + Chip.eg_timer = eg_timer_bak; + Chip.eg_cnt = eg_cnt_bak; + CalcRhythm (&Chip, buffer, length); } } - /* reload timer */ - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+c,(double)OPL->T[c]*OPL->TimerBase); - return OPL->status>>7; -} + FString GetVoiceString(void *chip) + { + FM_OPL *OPL = (FM_OPL *)chip; + char out[9*3]; -#define MAX_OPL_CHIPS 2 + for (int i = 0; i <= 8; ++i) + { + int color; + if (OPL != NULL && (OPL->P_CH[i].SLOT[0].state != EG_OFF || OPL->P_CH[i].SLOT[1].state != EG_OFF)) + { + color = 'D'; // Green means in use + } + else + { + color = 'A'; // Brick means free + } + out[i*3+0] = '\x1c'; + out[i*3+1] = color; + out[i*3+2] = '*'; + } + return FString (out, 9*3); + } +}; -static FM_OPL *OPL_YM3812[MAX_OPL_CHIPS]; /* array of pointers to the YM3812's */ -static int YM3812NumChips = 0; /* number of chips */ - -void *YM3812Init(int clock, int rate) +OPLEmul *YM3812Create(bool stereo) { /* emulator create */ - FM_OPL *YM3812 = OPLCreate(OPL_TYPE_YM3812,clock,rate); - if (YM3812) - YM3812ResetChip(YM3812); - return YM3812; -} - -void YM3812Shutdown(void *chip) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - - /* emulator shutdown */ - OPLDestroy(YM3812); -} -void YM3812ResetChip(void *chip) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - OPLResetChip(YM3812); -} - -int YM3812Write(void *chip, int a, int v) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - return OPLWrite(YM3812, a, v); -} - -unsigned char YM3812Read(void *chip, int a) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - /* YM3812 always returns bit2 and bit1 in HIGH state */ - return OPLRead(YM3812, a) | 0x06 ; -} -int YM3812TimerOver(void *chip, int c) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - return OPLTimerOver(YM3812, c); -} - -void YM3812SetTimerHandler(void *chip, OPL_TIMERHANDLER TimerHandler, int channelOffset) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - OPLSetTimerHandler(YM3812, TimerHandler, channelOffset); -} -void YM3812SetIRQHandler(void *chip,OPL_IRQHANDLER IRQHandler,int param) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - OPLSetIRQHandler(YM3812, IRQHandler, param); -} -void YM3812SetUpdateHandler(void *chip,OPL_UPDATEHANDLER UpdateHandler,int param) -{ - FM_OPL *YM3812 = (FM_OPL *)chip; - OPLSetUpdateHandler(YM3812, UpdateHandler, param); -} - - -/* -** Generate samples for one of the YM3812's -** -** 'which' is the virtual YM3812 number -** '*buffer' is the output buffer pointer -** 'length' is the number of samples that should be generated -*/ -void YM3812UpdateOne(void *chip, float *buffer, int length) -{ - FM_OPL *OPL = (FM_OPL *)chip; - int i; - - if (OPL == NULL) - { - return; - } - - UINT8 rhythm = OPL->rhythm&0x20; - - UINT32 lfo_am_cnt_bak = OPL->lfo_am_cnt; - UINT32 eg_timer_bak = OPL->eg_timer; - UINT32 eg_cnt_bak = OPL->eg_cnt; - - UINT32 lfo_am_cnt_out = lfo_am_cnt_bak; - UINT32 eg_timer_out = eg_timer_bak; - UINT32 eg_cnt_out = eg_cnt_bak; - - for (i = 0; i <= (rhythm ? 5 : 8); ++i) - { - OPL->lfo_am_cnt = lfo_am_cnt_bak; - OPL->eg_timer = eg_timer_bak; - OPL->eg_cnt = eg_cnt_bak; - if (CalcVoice (OPL, i, buffer, length)) - { - lfo_am_cnt_out = OPL->lfo_am_cnt; - eg_timer_out = OPL->eg_timer; - eg_cnt_out = OPL->eg_cnt; - } - } - - OPL->lfo_am_cnt = lfo_am_cnt_out; - OPL->eg_timer = eg_timer_out; - OPL->eg_cnt = eg_cnt_out; - - if (rhythm) /* Rhythm part */ - { - OPL->lfo_am_cnt = lfo_am_cnt_bak; - OPL->eg_timer = eg_timer_bak; - OPL->eg_cnt = eg_cnt_bak; - CalcRhythm (OPL, buffer, length); - } + return new YM3812(stereo); } // [RH] Render a whole voice at once. If nothing else, it lets us avoid @@ -1969,7 +1686,16 @@ static bool CalcVoice (FM_OPL *OPL, int voice, float *buffer, int length) advance_lfo(OPL); output = 0; - OPL_CALC_CH(CH, buffer + i); + float sample = OPL_CALC_CH(CH); + if (!OPL->IsStereo) + { + buffer[i] += sample; + } + else + { + buffer[i*2] += sample * CH->LeftVol; + buffer[i*2+1] += sample * CH->RightVol; + } advance(OPL, voice, voice); } @@ -1987,34 +1713,21 @@ static bool CalcRhythm (FM_OPL *OPL, float *buffer, int length) output = 0; OPL_CALC_RH(&OPL->P_CH[0], OPL->noise_rng & 1); /* [RH] Convert to floating point. */ - buffer[i] += float(output) / 10240; + float sample = float(output) / 10240; + if (!OPL->IsStereo) + { + buffer[i] += sample; + } + else + { + // [RH] Always use center panning for rhythm. + // The MIDI player doesn't use the rhythm section anyway. + buffer[i*2] += sample * CENTER_PANNING_POWER; + buffer[i*2+1] += sample * CENTER_PANNING_POWER; + } advance(OPL, 6, 8); advance_noise(OPL); } return true; } - -FString YM3812GetVoiceString(void *chip) -{ - FM_OPL *OPL = (FM_OPL *)chip; - char out[9*3]; - - for (int i = 0; i <= 8; ++i) - { - int color; - - if (OPL != NULL && (OPL->P_CH[i].SLOT[0].state != EG_OFF || OPL->P_CH[i].SLOT[1].state != EG_OFF)) - { - color = 'D'; // Green means in use - } - else - { - color = 'A'; // Brick means free - } - out[i*3+0] = '\x1c'; - out[i*3+1] = color; - out[i*3+2] = '*'; - } - return FString (out, 9*3); -} diff --git a/src/oplsynth/fmopl.h b/src/oplsynth/fmopl.h deleted file mode 100644 index 2b3eefb6e..000000000 --- a/src/oplsynth/fmopl.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef __FMOPL_H_ -#define __FMOPL_H_ - -#include "zstring.h" - -// Multiplying OPL_SAMPLE_RATE by ADLIB_CLOCK_MUL gives the number -// Adlib clocks per second, as used by the RAWADATA file format. - -/* compiler dependence */ -#ifndef OSD_CPU_H -#define OSD_CPU_H -typedef unsigned char UINT8; /* unsigned 8bit */ -typedef unsigned short UINT16; /* unsigned 16bit */ -typedef unsigned int UINT32; /* unsigned 32bit */ -typedef signed char INT8; /* signed 8bit */ -typedef signed short INT16; /* signed 16bit */ -typedef signed int INT32; /* signed 32bit */ -#endif - - -typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); -typedef void (*OPL_IRQHANDLER)(int param,int irq); -typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); -typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); -typedef unsigned char (*OPL_PORTHANDLER_R)(int param); - - -void *YM3812Init(int clock, int rate); -void YM3812Shutdown(void *chip); -void YM3812ResetChip(void *chip); -int YM3812Write(void *chip, int a, int v); -unsigned char YM3812Read(void *chip, int a); -int YM3812TimerOver(void *chip, int c); -void YM3812UpdateOne(void *chip, float *buffer, int length); - -void YM3812SetTimerHandler(void *chip, OPL_TIMERHANDLER TimerHandler, int channelOffset); -void YM3812SetIRQHandler(void *chip, OPL_IRQHANDLER IRQHandler, int param); -void YM3812SetUpdateHandler(void *chip, OPL_UPDATEHANDLER UpdateHandler, int param); - -FString YM3812GetVoiceString(void *chip); - -#endif diff --git a/src/oplsynth/mlopl_io.cpp b/src/oplsynth/mlopl_io.cpp index 614c11f59..b9d06629f 100644 --- a/src/oplsynth/mlopl_io.cpp +++ b/src/oplsynth/mlopl_io.cpp @@ -42,7 +42,12 @@ #include #endif #include "muslib.h" -#include "fmopl.h" +#include "opl.h" +#include "c_cvars.h" + +#define HALF_PI (PI*0.5) + +EXTERN_CVAR(Int, opl_core) OPLio::~OPLio() { @@ -58,8 +63,15 @@ void OPLio::WriteDelay(int ticks) void OPLio::OPLwriteReg(int which, uint reg, uchar data) { - YM3812Write (chips[which], 0, reg); - YM3812Write (chips[which], 1, data); + if (IsOPL3) + { + reg |= (which & 1) << 8; + which >>= 1; + } + if (chips[which] != NULL) + { + chips[which]->WriteReg(reg, data); + } } /* @@ -68,12 +80,11 @@ void OPLio::OPLwriteReg(int which, uint reg, uchar data) */ void OPLio::OPLwriteChannel(uint regbase, uint channel, uchar data1, uchar data2) { - static const uint op_num[] = { - 0x000, 0x001, 0x002, 0x008, 0x009, 0x00A, 0x010, 0x011, 0x012, - 0x100, 0x101, 0x102, 0x108, 0x109, 0x10A, 0x110, 0x111, 0x112}; + static const uint op_num[OPL2CHANNELS] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12}; - uint reg = regbase+op_num[channel]; - uint which = reg>>8; + uint which = channel / OPL2CHANNELS; + uint reg = regbase + op_num[channel % OPL2CHANNELS]; OPLwriteReg (which, reg, data1); OPLwriteReg (which, reg+3, data2); } @@ -84,12 +95,8 @@ void OPLio::OPLwriteChannel(uint regbase, uint channel, uchar data1, uchar data2 */ void OPLio::OPLwriteValue(uint regbase, uint channel, uchar value) { - static const uint reg_num[] = { - 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, - 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108}; - - uint reg = regbase+reg_num[channel]; - uint which = reg>>8; + uint which = channel / OPL2CHANNELS; + uint reg = regbase + (channel % OPL2CHANNELS); OPLwriteReg (which, reg, value); } @@ -252,6 +259,20 @@ void OPLio::OPLwritePan(uint channel, struct OPL2instrument *instr, int pan) else bits = 0x30; // both OPLwriteValue(0xC0, channel, instr->feedback | bits); + + // Set real panning if we're using emulated chips. + int chanper = IsOPL3 ? OPL3CHANNELS : OPL2CHANNELS; + int which = channel / chanper; + if (chips[which] != NULL) + { + // This is the MIDI-recommended pan formula. 0 and 1 are + // both hard left so that 64 can be perfectly center. + // (Note that the 'pan' passed to this function is the + // MIDI pan position, subtracted by 64.) + double level = (pan <= -63) ? 0 : (pan + 64 - 1) / 126.0; + chips[which]->SetPanning(channel % chanper, + (float)cos(HALF_PI * level), (float)sin(HALF_PI * level)); + } } } @@ -298,40 +319,45 @@ void OPLio::OPLshutup(void) /* * Initialize hardware upon startup */ -int OPLio::OPLinit(uint numchips) +int OPLio::OPLinit(uint numchips, bool stereo, bool initopl3) { - assert(numchips >= 1 && numchips <= 2); - chips[0] = YM3812Init (3579545, int(OPL_SAMPLE_RATE)); - chips[1] = NULL; - if (chips[0] != NULL) + assert(numchips >= 1 && numchips <= countof(chips)); + uint i; + IsOPL3 = (opl_core == 1 || opl_core == 2); + + memset(chips, 0, sizeof(chips)); + if (IsOPL3) { - if (numchips > 1) + numchips = (numchips + 1) >> 1; + } + for (i = 0; i < numchips; ++i) + { + OPLEmul *chip = IsOPL3 ? (opl_core == 1 ? DBOPLCreate(stereo) : JavaOPLCreate(stereo)) : YM3812Create(stereo); + if (chip == NULL) { - chips[1] = YM3812Init (3579545, int(OPL_SAMPLE_RATE)); - if (chips[1] == NULL) - { - YM3812Shutdown(chips[0]); - chips[0] = NULL; - return -1; - } + break; } + chips[i] = chip; } - else - { - return -1; - } - OPLchannels = OPL2CHANNELS * numchips; - OPLwriteInitState(); - return 0; + NumChips = i; + OPLchannels = i * (IsOPL3 ? OPL3CHANNELS : OPL2CHANNELS); + OPLwriteInitState(initopl3); + return i; } -void OPLio::OPLwriteInitState() +void OPLio::OPLwriteInitState(bool initopl3) { - for (uint i = 0; i < OPLchannels / OPL2CHANNELS; ++i) + for (uint i = 0; i < NumChips; ++i) { - OPLwriteReg (i, 0x01, 0x20); // enable Waveform Select - OPLwriteReg (i, 0x0B, 0x40); // turn off CSW mode - OPLwriteReg (i, 0xBD, 0x00); // set vibrato/tremolo depth to low, set melodic mode + int chip = i << (int)IsOPL3; + if (IsOPL3 && initopl3) + { + OPLwriteReg(chip, 0x105, 0x01); // enable YMF262/OPL3 mode + OPLwriteReg(chip, 0x104, 0x00); // disable 4-operator mode + } + OPLwriteReg(chip, 0x01, 0x20); // enable Waveform Select + OPLwriteReg(chip, 0x0B, 0x40); // turn off CSW mode + OPLwriteReg(chip, 0xBD, 0x00); // set vibrato/tremolo depth to low, set melodic mode } OPLshutup(); } @@ -341,8 +367,12 @@ void OPLio::OPLwriteInitState() */ void OPLio::OPLdeinit(void) { - YM3812Shutdown (chips[0]); - chips[0] = NULL; - YM3812Shutdown (chips[1]); - chips[1] = NULL; + for (size_t i = 0; i < countof(chips); ++i) + { + if (chips[i] != NULL) + { + delete chips[i]; + chips[i] = NULL; + } + } } diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index ade5b5c12..79bb10226 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -41,7 +41,7 @@ #include "m_swap.h" #include "w_wad.h" #include "v_text.h" -#include "fmopl.h" +#include "opl.h" // MACROS ------------------------------------------------------------------ @@ -60,10 +60,14 @@ // EXTERNAL DATA DECLARATIONS ---------------------------------------------- +EXTERN_CVAR(Int, opl_numchips) + // PRIVATE DATA DEFINITIONS ------------------------------------------------ // PUBLIC DATA DEFINITIONS ------------------------------------------------- +CVAR(Bool, opl_fullpan, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); + // CODE -------------------------------------------------------------------- //========================================================================== @@ -74,6 +78,7 @@ OPLMIDIDevice::OPLMIDIDevice() { + FullPan = opl_fullpan; FWadLump data = Wads.OpenLumpName("GENMIDI"); OPLloadBank(data); SampleRate = (int)OPL_SAMPLE_RATE; @@ -89,11 +94,11 @@ OPLMIDIDevice::OPLMIDIDevice() int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) { - if (io == NULL || io->OPLinit(TwoChips + 1)) + if (io == NULL || 0 == (NumChips = io->OPLinit(opl_numchips, FullPan, true))) { return 1; } - int ret = OpenStream(14, SoundStream::Mono, callback, userdata); + int ret = OpenStream(14, (FullPan || io->IsOPL3) ? 0 : SoundStream::Mono, callback, userdata); if (ret == 0) { OPLstopMusic(); diff --git a/src/oplsynth/music_opldumper_mididevice.cpp b/src/oplsynth/music_opldumper_mididevice.cpp index 51e80a801..0bb9ac14d 100644 --- a/src/oplsynth/music_opldumper_mididevice.cpp +++ b/src/oplsynth/music_opldumper_mididevice.cpp @@ -39,7 +39,7 @@ #include "doomdef.h" #include "m_swap.h" #include "w_wad.h" -#include "fmopl.h" +#include "opl.h" // MACROS ------------------------------------------------------------------ @@ -139,7 +139,7 @@ DiskWriterIO::~DiskWriterIO() // //========================================================================== -int DiskWriterIO::OPLinit(uint numchips) +int DiskWriterIO::OPLinit(uint numchips, bool dontcare) { // If the file extension is unknown or not present, the default format // is RAW. Otherwise, you can use DRO. @@ -155,7 +155,7 @@ int DiskWriterIO::OPLinit(uint numchips) if (File == NULL) { Printf("Could not open %s for writing.\n", Filename.GetChars()); - return -1; + return 0; } if (Format == FMT_RDOS) @@ -188,8 +188,8 @@ int DiskWriterIO::OPLinit(uint numchips) CurIntTime = 0; CurChip = 0; OPLchannels = OPL2CHANNELS * numchips; - OPLwriteInitState(); - return 0; + OPLwriteInitState(false); + return numchips; } //========================================================================== diff --git a/src/oplsynth/muslib.h b/src/oplsynth/muslib.h index 7cc533ea5..404ce1e63 100644 --- a/src/oplsynth/muslib.h +++ b/src/oplsynth/muslib.h @@ -141,7 +141,8 @@ struct OP2instrEntry { /* From MLOPL_IO.CPP */ #define OPL2CHANNELS 9 #define OPL3CHANNELS 18 -#define MAXCHANNELS 18 +#define MAXOPL2CHIPS 8 +#define MAXCHANNELS (OPL2CHANNELS * MAXOPL2CHIPS) /* Channel Flags: */ @@ -175,16 +176,18 @@ struct OPLio { void OPLwritePan(uint channel, struct OPL2instrument *instr, int pan); void OPLwriteInstrument(uint channel, struct OPL2instrument *instr); void OPLshutup(void); - void OPLwriteInitState(); + void OPLwriteInitState(bool initopl3); - virtual int OPLinit(uint numchips); + virtual int OPLinit(uint numchips, bool stereo=false, bool initopl3=false); virtual void OPLdeinit(void); virtual void OPLwriteReg(int which, uint reg, uchar data); virtual void SetClockRate(double samples_per_tick); virtual void WriteDelay(int ticks); + class OPLEmul *chips[MAXOPL2CHIPS]; uint OPLchannels; - void *chips[2]; + uint NumChips; + bool IsOPL3; }; struct DiskWriterIO : public OPLio @@ -192,7 +195,7 @@ struct DiskWriterIO : public OPLio DiskWriterIO(const char *filename); ~DiskWriterIO(); - int OPLinit(uint numchips); + int OPLinit(uint numchips, bool notused=false); void OPLdeinit(); void OPLwriteReg(int which, uint reg, uchar data); void SetClockRate(double samples_per_tick); @@ -291,7 +294,6 @@ enum MUSctrl { ctrlPoly, }; -#define OPL_SAMPLE_RATE 49716.0 #define ADLIB_CLOCK_MUL 24.0 #endif // __MUSLIB_H_ diff --git a/src/oplsynth/opl.h b/src/oplsynth/opl.h new file mode 100644 index 000000000..eb46c9955 --- /dev/null +++ b/src/oplsynth/opl.h @@ -0,0 +1,29 @@ +#ifndef OPL_H +#define OPL_H + +#include "zstring.h" + +// Abstract base class for OPL emulators + +class OPLEmul +{ +public: + OPLEmul() {} + virtual ~OPLEmul() {} + + virtual void Reset() = 0; + virtual void WriteReg(int reg, int v) = 0; + virtual void Update(float *buffer, int length) = 0; + virtual void SetPanning(int c, float left, float right) = 0; + virtual FString GetVoiceString() { return FString(); } +}; + +OPLEmul *YM3812Create(bool stereo); +OPLEmul *DBOPLCreate(bool stereo); +OPLEmul *JavaOPLCreate(bool stereo); + +#define OPL_SAMPLE_RATE 49716.0 +#define CENTER_PANNING_POWER 0.70710678118 /* [RH] volume at center for EQP */ + + +#endif \ No newline at end of file diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index 31eef55fd..335e6d66e 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -7,7 +7,7 @@ #include "opl_mus_player.h" #include "doomtype.h" -#include "fmopl.h" +#include "opl.h" #include "w_wad.h" #include "templates.h" #include "c_cvars.h" @@ -16,15 +16,16 @@ #define IMF_RATE 700.0 -EXTERN_CVAR (Bool, opl_onechip) +EXTERN_CVAR (Int, opl_numchips) OPLmusicBlock::OPLmusicBlock() { scoredata = NULL; NextTickIn = 0; LastOffset = 0; - TwoChips = !opl_onechip; + NumChips = MIN(*opl_numchips, 2); Looping = false; + FullPan = false; io = NULL; io = new OPLio; } @@ -36,10 +37,9 @@ OPLmusicBlock::~OPLmusicBlock() void OPLmusicBlock::ResetChips () { - TwoChips = !opl_onechip; ChipAccess.Enter(); io->OPLdeinit (); - io->OPLinit (TwoChips + 1); + NumChips = io->OPLinit(MIN(*opl_numchips, 2), FullPan); ChipAccess.Leave(); } @@ -76,7 +76,7 @@ fail: delete[] scoredata; memcpy(scoredata, &musiccache[0], len); } - if (io->OPLinit (TwoChips + 1)) + if (0 == (NumChips = io->OPLinit(NumChips))) { goto fail; } @@ -222,13 +222,12 @@ void OPLmusicFile::Restart () bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) { - float *samples = (float *)buff; - float *samples1; - int numsamples = numbytes / sizeof(float); + float *samples1 = (float *)buff; + int stereoshift = (int)(FullPan | io->IsOPL3); + int numsamples = numbytes / (sizeof(float) << stereoshift); bool prevEnded = false; bool res = true; - samples1 = samples; memset(buff, 0, numbytes); ChipAccess.Enter(); @@ -237,17 +236,20 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) double ticky = NextTickIn; int tick_in = int(NextTickIn); int samplesleft = MIN(numsamples, tick_in); + size_t i; if (samplesleft > 0) { - YM3812UpdateOne (io->chips[0], samples1, samplesleft); - YM3812UpdateOne (io->chips[1], samples1, samplesleft); - OffsetSamples(samples1, samplesleft); + for (i = 0; i < io->NumChips; ++i) + { + io->chips[i]->Update(samples1, samplesleft); + } + OffsetSamples(samples1, samplesleft << stereoshift); assert(NextTickIn == ticky); NextTickIn -= samplesleft; assert (NextTickIn >= 0); numsamples -= samplesleft; - samples1 += samplesleft; + samples1 += samplesleft << stereoshift; } if (NextTickIn < 1) @@ -260,9 +262,11 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) { if (numsamples > 0) { - YM3812UpdateOne (io->chips[0], samples1, numsamples); - YM3812UpdateOne (io->chips[1], samples1, numsamples); - OffsetSamples(samples1, numsamples); + for (i = 0; i < io->NumChips; ++i) + { + io->chips[i]->Update(samples1, samplesleft); + } + OffsetSamples(samples1, numsamples << stereoshift); } res = false; break; @@ -407,10 +411,7 @@ int OPLmusicFile::PlayTick () break; default: // It's something to stuff into the OPL chip - if (WhichChip == 0 || TwoChips) - { - io->OPLwriteReg(WhichChip, reg, data); - } + io->OPLwriteReg(WhichChip, reg, data); break; } } @@ -450,10 +451,7 @@ int OPLmusicFile::PlayTick () { data = *score++; } - if (WhichChip == 0 || TwoChips) - { - io->OPLwriteReg(WhichChip, reg, data); - } + io->OPLwriteReg(WhichChip, reg, data); } break; @@ -481,7 +479,7 @@ int OPLmusicFile::PlayTick () { return (data + 1) << 8; } - else if (code < to_reg_size && (which = 0 || TwoChips)) + else if (code < to_reg_size) { io->OPLwriteReg(which, to_reg[code], data); } @@ -523,14 +521,14 @@ OPLmusicFile::OPLmusicFile(const OPLmusicFile *source, const char *filename) SamplesPerTick = source->SamplesPerTick; RawPlayer = source->RawPlayer; score = source->score; - TwoChips = source->TwoChips; + NumChips = source->NumChips; WhichChip = 0; if (io != NULL) { delete io; } io = new DiskWriterIO(filename); - io->OPLinit(TwoChips + 1); + NumChips = io->OPLinit(NumChips); Restart(); } diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index d1357e0e0..b0eb4b6c8 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -18,9 +18,10 @@ protected: double NextTickIn; double SamplesPerTick; - bool TwoChips; + int NumChips; bool Looping; double LastOffset; + bool FullPan; FCriticalSection ChipAccess; }; diff --git a/src/p_3dfloors.cpp b/src/p_3dfloors.cpp index 02c2702ff..56b34ca0f 100644 --- a/src/p_3dfloors.cpp +++ b/src/p_3dfloors.cpp @@ -108,7 +108,7 @@ void F3DFloor::UpdateColormap(FDynamicColormap *&map) // Add one 3D floor to the sector // //========================================================================== -static void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flags,int transluc) +static void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flags, int alpha) { F3DFloor* ffloor; unsigned i; @@ -179,7 +179,7 @@ static void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flag ffloor->flags = flags; ffloor->master = master; - ffloor->alpha = transluc; + ffloor->alpha = alpha; ffloor->top.vindex = ffloor->bottom.vindex = -1; // The engine cannot handle sloped translucent floors. Sorry @@ -200,7 +200,11 @@ static void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flag // kg3D - software renderer only hack // this is really required because of ceilingclip and floorclip - if(flags & FF_BOTHPLANES) P_Add3DFloor(sec, sec2, master, FF_EXISTS | FF_THISINSIDE | FF_RENDERPLANES | FF_NOSHADE | FF_SEETHROUGH | FF_SHOOTTHROUGH | (flags & FF_INVERTSECTOR), transluc); + if(flags & FF_BOTHPLANES) + { + P_Add3DFloor(sec, sec2, master, FF_EXISTS | FF_THISINSIDE | FF_RENDERPLANES | FF_NOSHADE | FF_SEETHROUGH | FF_SHOOTTHROUGH | + (flags & (FF_INVERTSECTOR | FF_TRANSLUCENT | FF_ADDITIVETRANS)), alpha); + } } //========================================================================== @@ -208,7 +212,7 @@ static void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flag // Creates all 3D floors defined by one linedef // //========================================================================== -static int P_Set3DFloor(line_t * line, int param,int param2, int alpha) +static int P_Set3DFloor(line_t * line, int param, int param2, int alpha) { int s,i; int flags; diff --git a/src/p_3dfloors.h b/src/p_3dfloors.h index f9915348e..d0bca5e8f 100644 --- a/src/p_3dfloors.h +++ b/src/p_3dfloors.h @@ -36,7 +36,7 @@ typedef enum FF_FADEWALLS = 0x8000000, // Applies real fog to walls and doesn't blend the view FF_ADDITIVETRANS = 0x10000000, // Render this floor with additive translucency FF_FLOOD = 0x20000000, // extends towards the next lowest flooding or solid 3D floor or the bottom of the sector - FF_THISINSIDE = 0x40000000, // hack for software 3D with FF_BOTHPLANES + FF_THISINSIDE = 0x40000000, // hack for software 3D with FF_BOTHPLANES } ffloortype_e; // This is for the purpose of Sector_SetContents: diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 81da86259..5b733f029 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -73,6 +73,7 @@ #include "po_man.h" #include "actorptrselect.h" #include "farchive.h" +#include "decallib.h" #include "g_shared/a_pickups.h" @@ -80,7 +81,6 @@ extern FILE *Logfile; FRandom pr_acs ("ACS"); - // I imagine this much stack space is probably overkill, but it could // potentially get used with recursive functions. #define STACK_SIZE 4096 @@ -93,6 +93,7 @@ FRandom pr_acs ("ACS"); #define HUDMSG_COLORSTRING (0x40000000) #define HUDMSG_ADDBLEND (0x20000000) #define HUDMSG_ALPHA (0x10000000) +#define HUDMSG_NOWRAP (0x08000000) // HUD message layers; these are not flags #define HUDMSG_LAYER_SHIFT 12 @@ -111,14 +112,19 @@ FRandom pr_acs ("ACS"); #define NOT_FLOOR 8 #define NOT_CEILING 16 +// SpawnDecal flags +#define SDF_ABSANGLE 1 +#define SDF_PERMANENT 2 + struct CallReturn { - CallReturn(int pc, ScriptFunction *func, FBehavior *module, SDWORD *locals, bool discard) + CallReturn(int pc, ScriptFunction *func, FBehavior *module, SDWORD *locals, bool discard, unsigned int runaway) : ReturnFunction(func), ReturnModule(module), ReturnLocals(locals), ReturnAddress(pc), - bDiscardResult(discard) + bDiscardResult(discard), + EntryInstrCount(runaway) {} ScriptFunction *ReturnFunction; @@ -126,6 +132,7 @@ struct CallReturn SDWORD *ReturnLocals; int ReturnAddress; int bDiscardResult; + unsigned int EntryInstrCount; }; static DLevelScript *P_GetScriptGoing (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module, @@ -139,7 +146,31 @@ struct FBehavior::ArrayInfo }; TArray FBehavior::StaticModules; +TArray ACS_StringBuilderStack; +#define STRINGBUILDER_START(Builder) if (Builder.IsNotEmpty() || ACS_StringBuilderStack.Size()) { ACS_StringBuilderStack.Push(Builder); Builder = ""; } +#define STRINGBUILDER_FINISH(Builder) if (!ACS_StringBuilderStack.Pop(Builder)) { Builder = ""; } + +//============================================================================ +// +// uallong +// +// Read a possibly unaligned four-byte little endian integer from memory. +// +//============================================================================ + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) +inline int uallong(const int &foo) +{ + return foo; +} +#else +inline int uallong(const int &foo) +{ + const unsigned char *bar = (const unsigned char *)&foo; + return bar[0] | (bar[1] << 8) | (bar[2] << 16) | (bar[3] << 24); +} +#endif //============================================================================ // @@ -156,21 +187,567 @@ SDWORD ACS_GlobalVars[NUM_GLOBALVARS]; FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS]; +//---------------------------------------------------------------------------- +// +// Global ACS strings (Formerly known as On the fly strings) +// +// This special string table is part of the global state. Programmatically +// generated strings (e.g. those returned by strparam) are stored here. +// PCD_TAGSTRING also now stores strings in this table instead of simply +// tagging strings with their library ID. +// +// Identical strings map to identical string identifiers. +// +// When the string table needs to grow to hold more strings, a garbage +// collection is first attempted to see if more room can be made to store +// strings without growing. A string is concidered in use if any value +// in any of these variable blocks contains a valid ID in the global string +// table: +// * The active area of the ACS stack +// * All running scripts' local variables +// * All map variables +// * All world variables +// * All global variables +// It's not important whether or not they are really used as strings, only +// that they might be. A string is also concidered in use if its lock count +// is non-zero, even if none of the above variable blocks referenced it. +// +// To keep track of local and map variables for nonresident maps in a hub, +// when a map's state is archived, all strings found in its local and map +// variables are locked. When a map is revisited in a hub, all strings found +// in its local and map variables are unlocked. Locking and unlocking are +// cumulative operations. +// +// What this all means is that: +// * Strings returned by strparam last indefinitely. No longer do they +// disappear at the end of the tic they were generated. +// * You can pass library strings around freely without having to worry +// about always having the same libraries loaded in the same order on +// every map that needs to use those strings. +// +//---------------------------------------------------------------------------- + +ACSStringPool GlobalACSStrings; + +ACSStringPool::ACSStringPool() +{ + memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); + FirstFreeEntry = 0; +} + //============================================================================ // -// On the fly strings +// ACSStringPool :: Clear +// +// Remove all strings from the pool. // //============================================================================ -#define LIB_ACSSTRINGS_ONTHEFLY 0x7fff -#define ACSSTRING_OR_ONTHEFLY (LIB_ACSSTRINGS_ONTHEFLY<<16) +void ACSStringPool::Clear() +{ + Pool.Clear(); + memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); + FirstFreeEntry = 0; +} -TArray - ACS_StringsOnTheFly, - ACS_StringBuilderStack; +//============================================================================ +// +// ACSStringPool :: AddString +// +// Returns a valid string identifier (including library ID) or -1 if we ran +// out of room. Identical strings will return identical values. +// +//============================================================================ -#define STRINGBUILDER_START(Builder) if (Builder.IsNotEmpty() || ACS_StringBuilderStack.Size()) { ACS_StringBuilderStack.Push(Builder); Builder = ""; } -#define STRINGBUILDER_FINISH(Builder) if (!ACS_StringBuilderStack.Pop(Builder)) { Builder = ""; } +int ACSStringPool::AddString(const char *str, const SDWORD *stack, int stackdepth) +{ + size_t len = strlen(str); + unsigned int h = SuperFastHash(str, len); + unsigned int bucketnum = h % NUM_BUCKETS; + int i = FindString(str, len, h, bucketnum); + if (i >= 0) + { + return i | STRPOOL_LIBRARYID_OR; + } + FString fstr(str); + return InsertString(fstr, h, bucketnum, stack, stackdepth); +} + +int ACSStringPool::AddString(FString &str, const SDWORD *stack, int stackdepth) +{ + unsigned int h = SuperFastHash(str.GetChars(), str.Len()); + unsigned int bucketnum = h % NUM_BUCKETS; + int i = FindString(str, str.Len(), h, bucketnum); + if (i >= 0) + { + return i | STRPOOL_LIBRARYID_OR; + } + return InsertString(str, h, bucketnum, stack, stackdepth); +} + +//============================================================================ +// +// ACSStringPool :: GetString +// +//============================================================================ + +const char *ACSStringPool::GetString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + if ((unsigned)strnum < Pool.Size() && Pool[strnum].Next != FREE_ENTRY) + { + return Pool[strnum].Str; + } + return NULL; +} + +//============================================================================ +// +// ACSStringPool :: LockString +// +// Prevents this string from being purged. +// +//============================================================================ + +void ACSStringPool::LockString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + assert((unsigned)strnum < Pool.Size()); + Pool[strnum].LockCount++; +} + +//============================================================================ +// +// ACSStringPool :: UnlockString +// +// When equally mated with LockString, allows this string to be purged. +// +//============================================================================ + +void ACSStringPool::UnlockString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + assert((unsigned)strnum < Pool.Size()); + assert(Pool[strnum].LockCount > 0); + Pool[strnum].LockCount--; +} + +//============================================================================ +// +// ACSStringPool :: MarkString +// +// Prevent this string from being purged during the next call to PurgeStrings. +// This does not carry over to subsequent calls of PurgeStrings. +// +//============================================================================ + +void ACSStringPool::MarkString(int strnum) +{ + assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); + strnum &= ~LIBRARYID_MASK; + assert((unsigned)strnum < Pool.Size()); + Pool[strnum].LockCount |= 0x80000000; +} + +//============================================================================ +// +// ACSStringPool :: LockStringArray +// +// Prevents several strings from being purged. Entries not in this pool will +// be silently ignored. The idea here is to pass this function a block of +// ACS variables. Everything that looks like it might be a string in the pool +// is locked, even if it's not actually used as such. It's better to keep +// more strings than we need than to throw away ones we do need. +// +//============================================================================ + +void ACSStringPool::LockStringArray(const int *strnum, unsigned int count) +{ + for (unsigned int i = 0; i < count; ++i) + { + int num = strnum[i]; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + Pool[num].LockCount++; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: UnlockStringArray +// +// Reverse of LockStringArray. +// +//============================================================================ + +void ACSStringPool::UnlockStringArray(const int *strnum, unsigned int count) +{ + for (unsigned int i = 0; i < count; ++i) + { + int num = strnum[i]; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + assert(Pool[num].LockCount > 0); + Pool[num].LockCount--; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: MarkStringArray +// +// Array version of MarkString. +// +//============================================================================ + +void ACSStringPool::MarkStringArray(const int *strnum, unsigned int count) +{ + for (unsigned int i = 0; i < count; ++i) + { + int num = strnum[i]; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + Pool[num].LockCount |= 0x80000000; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: MarkStringMap +// +// World/global variables version of MarkString. +// +//============================================================================ + +void ACSStringPool::MarkStringMap(const FWorldGlobalArray &aray) +{ + FWorldGlobalArray::ConstIterator it(aray); + FWorldGlobalArray::ConstPair *pair; + + while (it.NextPair(pair)) + { + int num = pair->Value; + if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) + { + num &= ~LIBRARYID_MASK; + if ((unsigned)num < Pool.Size()) + { + Pool[num].LockCount |= 0x80000000; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: UnlockAll +// +// Resets every entry's lock count to 0. Used when doing a partial reset of +// ACS state such as travelling to a new hub. +// +//============================================================================ + +void ACSStringPool::UnlockAll() +{ + for (unsigned int i = 0; i < Pool.Size(); ++i) + { + Pool[i].LockCount = 0; + } +} + +//============================================================================ +// +// ACSStringPool :: PurgeStrings +// +// Remove all unlocked strings from the pool. +// +//============================================================================ + +void ACSStringPool::PurgeStrings() +{ + // Clear the hash buckets. We'll rebuild them as we decide what strings + // to keep and which to toss. + memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); + size_t usedcount = 0, freedcount = 0; + for (unsigned int i = 0; i < Pool.Size(); ++i) + { + PoolEntry *entry = &Pool[i]; + if (entry->Next != FREE_ENTRY) + { + if (entry->LockCount == 0) + { + freedcount++; + // Mark this entry as free. + entry->Next = FREE_ENTRY; + if (i < FirstFreeEntry) + { + FirstFreeEntry = i; + } + // And free the string. + entry->Str = ""; + } + else + { + usedcount++; + // Rehash this entry. + unsigned int h = entry->Hash % NUM_BUCKETS; + entry->Next = PoolBuckets[h]; + PoolBuckets[h] = i; + // Remove MarkString's mark. + entry->LockCount &= 0x7FFFFFFF; + } + } + } +} + +//============================================================================ +// +// ACSStringPool :: FindString +// +// Finds a string in the pool. Does not include the library ID in the returned +// value. Returns -1 if the string does not exist in the pool. +// +//============================================================================ + +int ACSStringPool::FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum) +{ + unsigned int i = PoolBuckets[bucketnum]; + while (i != NO_ENTRY) + { + PoolEntry *entry = &Pool[i]; + assert(entry->Next != FREE_ENTRY); + if (entry->Hash == h && entry->Str.Len() == len && + memcmp(entry->Str.GetChars(), str, len) == 0) + { + return i; + } + i = entry->Next; + } + return -1; +} + +//============================================================================ +// +// ACSStringPool :: InsertString +// +// Inserts a new string into the pool. +// +//============================================================================ + +int ACSStringPool::InsertString(FString &str, unsigned int h, unsigned int bucketnum, const SDWORD *stack, int stackdepth) +{ + unsigned int index = FirstFreeEntry; + if (index >= MIN_GC_SIZE && index == Pool.Max()) + { // We will need to grow the array. Try a garbage collection first. + P_CollectACSGlobalStrings(stack, stackdepth); + index = FirstFreeEntry; + } + if (FirstFreeEntry >= STRPOOL_LIBRARYID_OR) + { // If we go any higher, we'll collide with the library ID marker. + return -1; + } + if (index == Pool.Size()) + { // There were no free entries; make a new one. + Pool.Reserve(1); + FirstFreeEntry++; + } + else + { // Scan for the next free entry + unsigned int i; + for (i = FirstFreeEntry + 1; i < Pool.Size() && Pool[i].Next != FREE_ENTRY; ++i) + { + } + FirstFreeEntry = i; + } + PoolEntry *entry = &Pool[index]; + entry->Str = str; + entry->Hash = h; + entry->Next = PoolBuckets[bucketnum]; + entry->LockCount = 0; + PoolBuckets[bucketnum] = index; + return index | STRPOOL_LIBRARYID_OR; +} + +//============================================================================ +// +// ACSStringPool :: ReadStrings +// +// Reads strings from a PNG chunk. +// +//============================================================================ + +void ACSStringPool::ReadStrings(PNGHandle *png, DWORD id) +{ + Clear(); + + size_t len = M_FindPNGChunk(png, id); + if (len != 0) + { + FPNGChunkArchive arc(png->File->GetFile(), id, len); + int32 i, j, poolsize; + unsigned int h, bucketnum; + char *str = NULL; + + arc << poolsize; + + Pool.Resize(poolsize); + i = 0; + j = arc.ReadCount(); + while (j >= 0) + { + // Mark skipped entries as free + for (; i < j; ++i) + { + Pool[i].Next = FREE_ENTRY; + Pool[i].LockCount = 0; + } + arc << str; + h = SuperFastHash(str, strlen(str)); + bucketnum = h % NUM_BUCKETS; + Pool[i].Str = str; + Pool[i].Hash = h; + Pool[i].LockCount = arc.ReadCount(); + Pool[i].Next = PoolBuckets[bucketnum]; + PoolBuckets[bucketnum] = i; + i++; + j = arc.ReadCount(); + } + if (str != NULL) + { + delete[] str; + } + } +} + +//============================================================================ +// +// ACSStringPool :: WriteStrings +// +// Writes strings to a PNG chunk. +// +//============================================================================ + +void ACSStringPool::WriteStrings(FILE *file, DWORD id) const +{ + int32 i, poolsize = (int32)Pool.Size(); + + if (poolsize == 0) + { // No need to write if we don't have anything. + return; + } + FPNGChunkArchive arc(file, id); + + arc << poolsize; + for (i = 0; i < poolsize; ++i) + { + PoolEntry *entry = &Pool[i]; + if (entry->Next != FREE_ENTRY) + { + arc.WriteCount(i); + arc.WriteString(entry->Str); + arc.WriteCount(entry->LockCount); + } + } + arc.WriteCount(-1); +} + +//============================================================================ +// +// ACSStringPool :: Dump +// +// Lists all strings in the pool. +// +//============================================================================ + +void ACSStringPool::Dump() const +{ + for (unsigned int i = 0; i < Pool.Size(); ++i) + { + if (Pool[i].Next != FREE_ENTRY) + { + Printf("%4u. (%2d) \"%s\"\n", i, Pool[i].LockCount, Pool[i].Str.GetChars()); + } + } +} + +//============================================================================ +// +// P_MarkWorldVarStrings +// +//============================================================================ + +void P_MarkWorldVarStrings() +{ + GlobalACSStrings.MarkStringArray(ACS_WorldVars, countof(ACS_WorldVars)); + for (size_t i = 0; i < countof(ACS_WorldArrays); ++i) + { + GlobalACSStrings.MarkStringMap(ACS_WorldArrays[i]); + } +} + +//============================================================================ +// +// P_MarkGlobalVarStrings +// +//============================================================================ + +void P_MarkGlobalVarStrings() +{ + GlobalACSStrings.MarkStringArray(ACS_GlobalVars, countof(ACS_GlobalVars)); + for (size_t i = 0; i < countof(ACS_GlobalArrays); ++i) + { + GlobalACSStrings.MarkStringMap(ACS_GlobalArrays[i]); + } +} + +//============================================================================ +// +// P_CollectACSGlobalStrings +// +// Garbage collect ACS global strings. +// +//============================================================================ + +void P_CollectACSGlobalStrings(const SDWORD *stack, int stackdepth) +{ + if (stack != NULL && stackdepth != 0) + { + GlobalACSStrings.MarkStringArray(stack, stackdepth); + } + FBehavior::StaticMarkLevelVarStrings(); + P_MarkWorldVarStrings(); + P_MarkGlobalVarStrings(); + GlobalACSStrings.PurgeStrings(); +} + +#ifdef _DEBUG +CCMD(acsgc) +{ + P_CollectACSGlobalStrings(NULL, 0); +} +CCMD(globstr) +{ + GlobalACSStrings.Dump(); +} +#endif //============================================================================ // @@ -199,7 +776,7 @@ static FString ScriptPresentation(int script) //============================================================================ // -// +// P_ClearACSVars // //============================================================================ @@ -219,12 +796,22 @@ void P_ClearACSVars(bool alsoglobal) { ACS_GlobalArrays[i].Clear (); } + // Since we cleared all ACS variables, we know nothing refers to them + // anymore. + GlobalACSStrings.Clear(); + } + else + { + // Purge any strings that aren't referenced by global variables, since + // they're the only possible references left. + P_MarkGlobalVarStrings(); + GlobalACSStrings.PurgeStrings(); } } //============================================================================ // -// +// WriteVars // //============================================================================ @@ -382,6 +969,7 @@ void P_ReadACSVars(PNGHandle *png) ReadVars (png, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); ReadArrayVars (png, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); ReadArrayVars (png, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); + GlobalACSStrings.ReadStrings(png, MAKE_ID('a','s','T','r')); } //============================================================================ @@ -396,6 +984,7 @@ void P_WriteACSVars(FILE *stdfile) WriteVars (stdfile, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); WriteArrayVars (stdfile, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); WriteArrayVars (stdfile, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); + GlobalACSStrings.WriteStrings(stdfile, MAKE_ID('a','s','T','r')); } //---- Inventory functions --------------------------------------// @@ -867,6 +1456,84 @@ FBehavior *FBehavior::StaticGetModule (int lib) return StaticModules[lib]; } +void FBehavior::StaticMarkLevelVarStrings() +{ + // Mark map variables. + for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + StaticModules[modnum]->MarkMapVarStrings(); + } + // Mark running scripts' local variables. + if (DACSThinker::ActiveThinker != NULL) + { + for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) + { + script->MarkLocalVarStrings(); + } + } +} + +void FBehavior::StaticLockLevelVarStrings() +{ + // Lock map variables. + for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + StaticModules[modnum]->LockMapVarStrings(); + } + // Lock running scripts' local variables. + if (DACSThinker::ActiveThinker != NULL) + { + for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) + { + script->LockLocalVarStrings(); + } + } +} + +void FBehavior::StaticUnlockLevelVarStrings() +{ + // Unlock map variables. + for (DWORD modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + StaticModules[modnum]->UnlockMapVarStrings(); + } + // Unlock running scripts' local variables. + if (DACSThinker::ActiveThinker != NULL) + { + for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) + { + script->UnlockLocalVarStrings(); + } + } +} + +void FBehavior::MarkMapVarStrings() const +{ + GlobalACSStrings.MarkStringArray(MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + GlobalACSStrings.MarkStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } +} + +void FBehavior::LockMapVarStrings() const +{ + GlobalACSStrings.LockStringArray(MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + GlobalACSStrings.LockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } +} + +void FBehavior::UnlockMapVarStrings() const +{ + GlobalACSStrings.UnlockStringArray(MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + GlobalACSStrings.UnlockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } +} + void FBehavior::StaticSerializeModuleStates (FArchive &arc) { DWORD modnum; @@ -1006,7 +1673,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) LumpNum = lumpnum; memset (MapVarStore, 0, sizeof(MapVarStore)); ModuleName[0] = 0; - + FunctionProfileData = NULL; // Now that everything is set up, record this module as being among the loaded modules. // We need to do this before resolving any imports, because an import might (indirectly) @@ -1018,7 +1685,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) // 1. If not, corrupt modules cause memory leaks // 2. Corrupt modules won't be reported when a level is being loaded if this function quits before // adding it to the list. - LibraryID = StaticModules.Push (this) << 16; + LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT; if (fr == NULL) len = Wads.LumpLength (lumpnum); @@ -1130,7 +1797,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { MapVars[i] = &MapVarStore[i]; } - //LibraryID = StaticModules.Push (this) << 16; + //LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT; } else { @@ -1141,6 +1808,15 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { NumFunctions = LittleLong(((DWORD *)Functions)[1]) / 8; Functions += 8; + FunctionProfileData = new ACSProfileInfo[NumFunctions]; + } + + // Load JUMP points + chunk = (DWORD *)FindChunk (MAKE_ID('J','U','M','P')); + if (chunk != NULL) + { + for (i = 0;i < (int)LittleLong(chunk[1]);i += 4) + JumpPoints.Push(LittleLong(chunk[2 + i/4])); } // Initialize this object's map variables @@ -1221,7 +1897,12 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { for (DWORD i = 0; i < chunk[1]/4; ++i) { - MapVarStore[chunk[i+2]] |= LibraryID; +// MapVarStore[chunk[i+2]] |= LibraryID; + const char *str = LookupString(MapVarStore[chunk[i+2]]); + if (str != NULL) + { + MapVarStore[chunk[i+2]] = GlobalACSStrings.AddString(str, NULL, 0); + } } } @@ -1236,11 +1917,54 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) SDWORD *elems = ArrayStore[arraynum].Elements; for (int j = ArrayStore[arraynum].ArraySize; j > 0; --j, ++elems) { - *elems |= LibraryID; +// *elems |= LibraryID; + const char *str = LookupString(*elems); + if (str != NULL) + { + *elems = GlobalACSStrings.AddString(str, NULL, 0); + } } } } } + + // [BL] Newer version of ASTR for structure aware compilers although we only have one array per chunk + chunk = (DWORD *)FindChunk (MAKE_ID('A','T','A','G')); + while (chunk != NULL) + { + const BYTE* chunkData = (const BYTE*)(chunk + 2); + // First byte is version, it should be 0 + if(*chunkData++ == 0) + { + int arraynum = MapVarStore[uallong(*(const int*)(chunkData))]; + chunkData += 4; + if ((unsigned)arraynum < (unsigned)NumArrays) + { + SDWORD *elems = ArrayStore[arraynum].Elements; + // Ending zeros may be left out. + for (int j = MIN(chunk[1]-5, ArrayStore[arraynum].ArraySize); j > 0; --j, ++elems, ++chunkData) + { + // For ATAG, a value of 0 = Integer, 1 = String, 2 = FunctionPtr + // Our implementation uses the same tags for both String and FunctionPtr + if (*chunkData == 2) + { + *elems |= LibraryID; + } + else if (*chunkData == 1) + { + const char *str = LookupString(*elems); + if (str != NULL) + { + *elems = GlobalACSStrings.AddString(str, NULL, 0); + } + } + } + i += 4+ArrayStore[arraynum].ArraySize; + } + } + + chunk = (DWORD *)NextChunk ((BYTE *)chunk); + } } // Load required libraries. @@ -1392,6 +2116,11 @@ FBehavior::~FBehavior () delete[] ArrayStore; ArrayStore = NULL; } + if (FunctionProfileData != NULL) + { + delete[] FunctionProfileData; + FunctionProfileData = NULL; + } if (Data != NULL) { delete[] Data; @@ -1863,15 +2592,12 @@ BYTE *FBehavior::NextChunk (BYTE *chunk) const const char *FBehavior::StaticLookupString (DWORD index) { - DWORD lib = index >> 16; - - switch (lib) - { - case LIB_ACSSTRINGS_ONTHEFLY: - index &= 0xffff; - return (ACS_StringsOnTheFly.Size() > index) ? ACS_StringsOnTheFly[index].GetChars() : NULL; - } + DWORD lib = index >> LIBRARYID_SHIFT; + if (lib == STRPOOL_LIBRARYID) + { + return GlobalACSStrings.GetString(index); + } if (lib >= (DWORD)StaticModules.Size()) { return NULL; @@ -2088,7 +2814,7 @@ void DACSThinker::Tick () script = next; } - ACS_StringsOnTheFly.Clear(); +// GlobalACSStrings.Clear(); if (ACS_StringBuilderStack.Size()) { @@ -2154,7 +2880,7 @@ void DLevelScript::Serialize (FArchive &arc) if (arc.IsStoring ()) { - WORD lib = activeBehavior->GetLibraryID() >> 16; + WORD lib = activeBehavior->GetLibraryID() >> LIBRARYID_SHIFT; arc << lib; i = activeBehavior->PC2Ofs (pc); arc << i; @@ -2169,6 +2895,23 @@ void DLevelScript::Serialize (FArchive &arc) arc << activefont << hudwidth << hudheight; + if (SaveVersion >= 3960) + { + arc << ClipRectLeft << ClipRectTop << ClipRectWidth << ClipRectHeight + << WrapWidth; + } + else + { + ClipRectLeft = ClipRectTop = ClipRectWidth = ClipRectHeight = WrapWidth = 0; + } + if (SaveVersion >= 4058) + { + arc << InModuleScriptNumber; + } + else + { // Don't worry about locating profiling info for old saves. + InModuleScriptNumber = -1; + } } DLevelScript::DLevelScript () @@ -2283,13 +3026,9 @@ int DLevelScript::ThingCount (int type, int stringid, int tid, int tag) int count = 0; bool replacemented = false; - if (type >= MAX_SPAWNABLES) + if (type > 0) { - return 0; - } - else if (type > 0) - { - kind = SpawnableThings[type]; + kind = P_GetSpawnableType(type); if (kind == NULL) return 0; } @@ -2765,6 +3504,10 @@ enum APROP_Mass = 32, APROP_Accuracy = 33, APROP_Stamina = 34, + APROP_Height = 35, + APROP_Radius = 36, + APROP_ReactionTime = 37, + APROP_MeleeRange = 38, }; // These are needed for ACS's APROP_RenderStyle @@ -2976,13 +3719,17 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) actor->stamina = value; break; + case APROP_ReactionTime: + actor->reactiontime = value; + break; + default: // do nothing. break; } } -int DLevelScript::GetActorProperty (int tid, int property) +int DLevelScript::GetActorProperty (int tid, int property, const SDWORD *stack, int stackdepth) { AActor *actor = SingleActorFromTID (tid, activator); @@ -3044,6 +3791,18 @@ int DLevelScript::GetActorProperty (int tid, int property) case APROP_Mass: return actor->Mass; case APROP_Accuracy: return actor->accuracy; case APROP_Stamina: return actor->stamina; + case APROP_Height: return actor->height; + case APROP_Radius: return actor->radius; + case APROP_ReactionTime:return actor->reactiontime; + case APROP_MeleeRange: return actor->meleerange; + + case APROP_SeeSound: return GlobalACSStrings.AddString(actor->SeeSound, stack, stackdepth); + case APROP_AttackSound: return GlobalACSStrings.AddString(actor->AttackSound, stack, stackdepth); + case APROP_PainSound: return GlobalACSStrings.AddString(actor->PainSound, stack, stackdepth); + case APROP_DeathSound: return GlobalACSStrings.AddString(actor->DeathSound, stack, stackdepth); + case APROP_ActiveSound: return GlobalACSStrings.AddString(actor->ActiveSound, stack, stackdepth); + case APROP_Species: return GlobalACSStrings.AddString(actor->GetSpecies(), stack, stackdepth); + case APROP_NameTag: return GlobalACSStrings.AddString(actor->GetTag(), stack, stackdepth); default: return 0; } @@ -3084,7 +3843,11 @@ int DLevelScript::CheckActorProperty (int tid, int property, int value) case APROP_Mass: case APROP_Accuracy: case APROP_Stamina: - return (GetActorProperty(tid, property) == value); + case APROP_Height: + case APROP_Radius: + case APROP_ReactionTime: + case APROP_MeleeRange: + return (GetActorProperty(tid, property, NULL, 0) == value); // Boolean values need to compare to a binary version of value case APROP_Ambush: @@ -3096,9 +3859,10 @@ int DLevelScript::CheckActorProperty (int tid, int property, int value) case APROP_Notarget: case APROP_Notrigger: case APROP_Dormant: - return (GetActorProperty(tid, property) == (!!value)); + return (GetActorProperty(tid, property, NULL, 0) == (!!value)); - // Strings are not covered by GetActorProperty, so make the check here + // Strings are covered by GetActorProperty, but they're fairly + // heavy-duty, so make the check here. case APROP_SeeSound: string = actor->SeeSound; break; case APROP_AttackSound: string = actor->AttackSound; break; case APROP_PainSound: string = actor->PainSound; break; @@ -3327,6 +4091,38 @@ int DLevelScript::DoClassifyActor(int tid) return classify; } +enum +{ + SOUND_See, + SOUND_Attack, + SOUND_Pain, + SOUND_Death, + SOUND_Active, + SOUND_Use, + SOUND_Bounce, + SOUND_WallBounce, + SOUND_CrushPain, + SOUND_Howl, +}; + +static FSoundID GetActorSound(const AActor *actor, int soundtype) +{ + switch (soundtype) + { + case SOUND_See: return actor->SeeSound; + case SOUND_Attack: return actor->AttackSound; + case SOUND_Pain: return actor->PainSound; + case SOUND_Death: return actor->DeathSound; + case SOUND_Active: return actor->ActiveSound; + case SOUND_Use: return actor->UseSound; + case SOUND_Bounce: return actor->BounceSound; + case SOUND_WallBounce: return actor->WallBounceSound; + case SOUND_CrushPain: return actor->CrushPainSound; + case SOUND_Howl: return actor->GetClass()->HowlSound; + default: return 0; + } +} + enum EACSFunctions { ACSF_GetLineUDMFInt=1, @@ -3379,10 +4175,33 @@ enum EACSFunctions ACSF_Sqrt, ACSF_FixedSqrt, ACSF_VectorLength, + ACSF_SetHUDClipRect, + ACSF_SetHUDWrapWidth, + ACSF_SetCVar, + ACSF_GetUserCVar, + ACSF_SetUserCVar, + ACSF_GetCVarString, + ACSF_SetCVarString, + ACSF_GetUserCVarString, + ACSF_SetUserCVarString, + ACSF_LineAttack, + ACSF_PlaySound, + ACSF_StopSound, + ACSF_strcmp, + ACSF_stricmp, + ACSF_StrLeft, + ACSF_StrRight, + ACSF_StrMid, + ACSF_GetActorClass, + ACSF_GetWeapon, + ACSF_SoundVolume, + ACSF_PlayActorSound, + ACSF_SpawnDecal, + ACSF_CheckFont, // ZDaemon - ACSF_GetTeamScore = 19620, - ACSF_SetTeamScore, + ACSF_GetTeamScore = 19620, // (int team) + ACSF_SetTeamScore, // (int team, int value) }; int DLevelScript::SideFromID(int id, int side) @@ -3474,7 +4293,167 @@ static int GetUserVariable(AActor *self, FName varname, int index) return 0; } -int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) +// Converts fixed- to floating-point as required. +static void DoSetCVar(FBaseCVar *cvar, int value, bool is_string, bool force=false) +{ + UCVarValue val; + ECVarType type; + + // For serverinfo variables, only the arbitrator should set it. + // The actual change to this cvar will not show up until it's + // been replicated to all peers. + if ((cvar->GetFlags() & CVAR_SERVERINFO) && consoleplayer != Net_Arbitrator) + { + return; + } + if (is_string) + { + val.String = FBehavior::StaticLookupString(value); + type = CVAR_String; + } + else if (cvar->GetRealType() == CVAR_Float) + { + val.Float = FIXED2FLOAT(value); + type = CVAR_Float; + } + else + { + val.Int = value; + type = CVAR_Int; + } + if (force) + { + cvar->ForceSet(val, type, true); + } + else + { + cvar->SetGenericRep(val, type); + } +} + +// Converts floating- to fixed-point as required. +static int DoGetCVar(FBaseCVar *cvar, bool is_string, const SDWORD *stack, int stackdepth) +{ + UCVarValue val; + + if (is_string) + { + val = cvar->GetGenericRep(CVAR_String); + return GlobalACSStrings.AddString(val.String, stack, stackdepth); + } + else if (cvar->GetRealType() == CVAR_Float) + { + val = cvar->GetGenericRep(CVAR_Float); + return FLOAT2FIXED(val.Float); + } + else + { + val = cvar->GetGenericRep(CVAR_Int); + return val.Int; + } +} + +static int GetUserCVar(int playernum, const char *cvarname, bool is_string, const SDWORD *stack, int stackdepth) +{ + if ((unsigned)playernum >= MAXPLAYERS || !playeringame[playernum]) + { + return 0; + } + FBaseCVar **cvar_p = players[playernum].userinfo.CheckKey(FName(cvarname, true)); + FBaseCVar *cvar; + if (cvar_p == NULL || (cvar = *cvar_p) == NULL || (cvar->GetFlags() & CVAR_IGNORE)) + { + return 0; + } + return DoGetCVar(cvar, is_string, stack, stackdepth); +} + +static int GetCVar(AActor *activator, const char *cvarname, bool is_string, const SDWORD *stack, int stackdepth) +{ + FBaseCVar *cvar = FindCVar(cvarname, NULL); + // Either the cvar doesn't exist, or it's for a mod that isn't loaded, so return 0. + if (cvar == NULL || (cvar->GetFlags() & CVAR_IGNORE)) + { + return 0; + } + else + { + // For userinfo cvars, redirect to GetUserCVar + if (cvar->GetFlags() & CVAR_USERINFO) + { + if (activator == NULL || activator->player == NULL) + { + return 0; + } + return GetUserCVar(int(activator->player - players), cvarname, is_string, stack, stackdepth); + } + return DoGetCVar(cvar, is_string, stack, stackdepth); + } +} + +static int SetUserCVar(int playernum, const char *cvarname, int value, bool is_string) +{ + if ((unsigned)playernum >= MAXPLAYERS || !playeringame[playernum]) + { + return 0; + } + FBaseCVar **cvar_p = players[playernum].userinfo.CheckKey(FName(cvarname, true)); + FBaseCVar *cvar; + // Only mod-created cvars may be set. + if (cvar_p == NULL || (cvar = *cvar_p) == NULL || (cvar->GetFlags() & CVAR_IGNORE) || !(cvar->GetFlags() & CVAR_MOD)) + { + return 0; + } + DoSetCVar(cvar, value, is_string); + + // If we are this player, then also reflect this change in the local version of this cvar. + if (playernum == consoleplayer) + { + FBaseCVar *cvar = FindCVar(cvarname, NULL); + // If we can find it in the userinfo, then we should also be able to find it in the normal cvar list, + // but check just to be safe. + if (cvar != NULL) + { + DoSetCVar(cvar, value, is_string, true); + } + } + + return 1; +} + +static int SetCVar(AActor *activator, const char *cvarname, int value, bool is_string) +{ + FBaseCVar *cvar = FindCVar(cvarname, NULL); + // Only mod-created cvars may be set. + if (cvar == NULL || (cvar->GetFlags() & (CVAR_IGNORE|CVAR_NOSET)) || !(cvar->GetFlags() & CVAR_MOD)) + { + return 0; + } + // For userinfo cvars, redirect to SetUserCVar + if (cvar->GetFlags() & CVAR_USERINFO) + { + if (activator == NULL || activator->player == NULL) + { + return 0; + } + return SetUserCVar(int(activator->player - players), cvarname, value, is_string); + } + DoSetCVar(cvar, value, is_string); + return 1; +} + +static bool DoSpawnDecal(AActor *actor, const FDecalTemplate *tpl, int flags, angle_t angle, fixed_t zofs, fixed_t distance) +{ + if (!(flags & SDF_ABSANGLE)) + { + angle += actor->angle; + } + return NULL != ShootDecal(tpl, actor, actor->Sector, actor->x, actor->y, + actor->z + (actor->height>>1) - actor->floorclip + actor->GetBobOffset() + zofs, + angle, distance, !!(flags & SDF_PERMANENT)); +} + +int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args, const SDWORD *stack, int stackdepth) { AActor *actor; switch(funcIndex) @@ -3692,7 +4671,7 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) FName varname(FBehavior::StaticLookupString(args[1]), true); if (varname != NAME_None) { - AActor *a = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); + AActor *a = SingleActorFromTID(args[0], activator); return a != NULL ? GetUserVariable(a, varname, 0) : 0; } return 0; @@ -3731,7 +4710,7 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) FName varname(FBehavior::StaticLookupString(args[1]), true); if (varname != NAME_None) { - AActor *a = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); + AActor *a = SingleActorFromTID(args[0], activator); return a != NULL ? GetUserVariable(a, varname, args[2]) : 0; } return 0; @@ -3743,10 +4722,16 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) case ACSF_CheckActorClass: { - AActor *a = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); + AActor *a = SingleActorFromTID(args[0], activator); return a == NULL ? false : a->GetClass()->TypeName == FName(FBehavior::StaticLookupString(args[1])); } + case ACSF_GetActorClass: + { + AActor *a = SingleActorFromTID(args[0], activator); + return GlobalACSStrings.AddString(a == NULL ? "None" : a->GetClass()->TypeName.GetChars(), stack, stackdepth); + } + case ACSF_SoundSequenceOnActor: { const char *seqname = FBehavior::StaticLookupString(args[1]); @@ -3893,11 +4878,9 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) case ACSF_UniqueTID: return P_FindUniqueTID(argCount > 0 ? args[0] : 0, argCount > 1 ? args[1] : 0); - break; case ACSF_IsTIDUsed: return P_IsTIDUsed(args[0]); - break; case ACSF_Sqrt: return xs_FloorToInt(sqrt(double(args[0]))); @@ -3908,6 +4891,308 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) case ACSF_VectorLength: return FLOAT2FIXED(TVector2(FIXED2DBL(args[0]), FIXED2DBL(args[1])).Length()); + case ACSF_SetHUDClipRect: + ClipRectLeft = argCount > 0 ? args[0] : 0; + ClipRectTop = argCount > 1 ? args[1] : 0; + ClipRectWidth = argCount > 2 ? args[2] : 0; + ClipRectHeight = argCount > 3 ? args[3] : 0; + WrapWidth = argCount > 4 ? args[4] : 0; + break; + + case ACSF_SetHUDWrapWidth: + WrapWidth = argCount > 0 ? args[0] : 0; + break; + + case ACSF_GetCVarString: + if (argCount == 1) + { + return GetCVar(activator, FBehavior::StaticLookupString(args[0]), true, stack, stackdepth); + } + break; + + case ACSF_SetCVar: + if (argCount == 2) + { + return SetCVar(activator, FBehavior::StaticLookupString(args[0]), args[1], false); + } + break; + + case ACSF_SetCVarString: + if (argCount == 2) + { + return SetCVar(activator, FBehavior::StaticLookupString(args[0]), args[1], true); + } + break; + + case ACSF_GetUserCVar: + if (argCount == 2) + { + return GetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), false, stack, stackdepth); + } + break; + + case ACSF_GetUserCVarString: + if (argCount == 2) + { + return GetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), true, stack, stackdepth); + } + break; + + case ACSF_SetUserCVar: + if (argCount == 3) + { + return SetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), args[2], false); + } + break; + + case ACSF_SetUserCVarString: + if (argCount == 3) + { + return SetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), args[2], true); + } + break; + + //[RC] A bullet firing function for ACS. Thanks to DavidPH. + case ACSF_LineAttack: + { + fixed_t angle = args[1] << FRACBITS; + fixed_t pitch = args[2] << FRACBITS; + int damage = args[3]; + FName pufftype = argCount > 4 && args[4]? FName(FBehavior::StaticLookupString(args[4])) : NAME_BulletPuff; + FName damagetype = argCount > 5 && args[5]? FName(FBehavior::StaticLookupString(args[5])) : NAME_None; + fixed_t range = argCount > 6 && args[6]? args[6] : MISSILERANGE; + + if (args[0] == 0) + { + P_LineAttack(activator, angle, range, pitch, damage, damagetype, pufftype); + } + else + { + AActor *source; + FActorIterator it(args[0]); + + while ((source = it.Next()) != NULL) + { + P_LineAttack(activator, angle, range, pitch, damage, damagetype, pufftype); + } + } + } + break; + + case ACSF_PlaySound: + case ACSF_PlayActorSound: + // PlaySound(tid, "SoundName", channel, volume, looping, attenuation) + { + FSoundID sid; + + if (funcIndex == ACSF_PlaySound) + { + const char *lookup = FBehavior::StaticLookupString(args[1]); + if (lookup != NULL) + { + sid = lookup; + } + } + if (sid != 0 || funcIndex == ACSF_PlayActorSound) + { + FActorIterator it(args[0]); + AActor *spot; + + int chan = argCount > 2 ? args[2] : CHAN_BODY; + float vol = argCount > 3 ? FIXED2FLOAT(args[3]) : 1.f; + INTBOOL looping = argCount > 4 ? args[4] : false; + float atten = argCount > 5 ? FIXED2FLOAT(args[5]) : ATTN_NORM; + + if (args[0] == 0) + { + spot = activator; + goto doplaysound; + } + while ((spot = it.Next()) != NULL) + { +doplaysound: if (funcIndex == ACSF_PlayActorSound) + { + sid = GetActorSound(spot, args[1]); + } + if (sid != 0) + { + if (!looping) + { + S_Sound(spot, chan, sid, vol, atten); + } + else if (!S_IsActorPlayingSomething(spot, chan, sid)) + { + S_Sound(spot, chan | CHAN_LOOP, sid, vol, atten); + } + } + } + } + } + break; + + case ACSF_StopSound: + { + int chan = argCount > 1 ? args[1] : CHAN_BODY; + + if (args[0] == 0) + { + S_StopSound(activator, chan); + } + else + { + FActorIterator it(args[0]); + AActor *spot; + + while ((spot = it.Next()) != NULL) + { + S_StopSound(spot, chan); + } + } + } + break; + + case ACSF_SoundVolume: + // SoundVolume(int tid, int channel, fixed volume) + { + int chan = args[1]; + float volume = FIXED2FLOAT(args[2]); + + if (args[0] == 0) + { + S_ChangeSoundVolume(activator, chan, volume); + } + else + { + FActorIterator it(args[0]); + AActor *spot; + + while ((spot = it.Next()) != NULL) + { + S_ChangeSoundVolume(spot, chan, volume); + } + } + } + break; + + case ACSF_strcmp: + case ACSF_stricmp: + if (argCount >= 2) + { + const char *a, *b; + a = FBehavior::StaticLookupString(args[0]); + b = FBehavior::StaticLookupString(args[1]); + + // Don't crash on invalid strings. + if (a == NULL) a = ""; + if (b == NULL) b = ""; + + if (argCount > 2) + { + int n = args[2]; + return (funcIndex == ACSF_strcmp) ? strncmp(a, b, n) : strnicmp(a, b, n); + } + else + { + return (funcIndex == ACSF_strcmp) ? strcmp(a, b) : stricmp(a, b); + } + } + break; + + case ACSF_StrLeft: + case ACSF_StrRight: + if (argCount >= 2) + { + const char *oldstr = FBehavior::StaticLookupString(args[0]); + if (oldstr == NULL || *oldstr == '\0') + { + return GlobalACSStrings.AddString("", stack, stackdepth); + } + size_t oldlen = strlen(oldstr); + size_t newlen = args[1]; + + if (oldlen < newlen) + { + newlen = oldlen; + } + FString newstr(funcIndex == ACSF_StrLeft ? oldstr : oldstr + oldlen - newlen, newlen); + return GlobalACSStrings.AddString(newstr, stack, stackdepth); + } + break; + + case ACSF_StrMid: + if (argCount >= 3) + { + const char *oldstr = FBehavior::StaticLookupString(args[0]); + if (oldstr == NULL || *oldstr == '\0') + { + return GlobalACSStrings.AddString("", stack, stackdepth); + } + size_t oldlen = strlen(oldstr); + size_t pos = args[1]; + size_t newlen = args[2]; + + if (pos >= oldlen) + { + return GlobalACSStrings.AddString("", stack, stackdepth); + } + if (pos + newlen > oldlen || pos + newlen < pos) + { + newlen = oldlen - pos; + } + return GlobalACSStrings.AddString(FString(oldstr + pos, newlen), stack, stackdepth); + } + break; + + case ACSF_GetWeapon: + if (activator == NULL || activator->player == NULL || // Non-players do not have weapons + activator->player->ReadyWeapon == NULL) + { + return GlobalACSStrings.AddString("None", stack, stackdepth); + } + else + { + return GlobalACSStrings.AddString(activator->player->ReadyWeapon->GetClass()->TypeName.GetChars(), stack, stackdepth); + } + + case ACSF_SpawnDecal: + // int SpawnDecal(int tid, str decalname, int flags, fixed angle, int zoffset, int distance) + // Returns number of decals spawned (not including spreading) + { + int count = 0; + const FDecalTemplate *tpl = DecalLibrary.GetDecalByName(FBehavior::StaticLookupString(args[1])); + if (tpl != NULL) + { + int flags = (argCount > 2) ? args[2] : 0; + angle_t angle = (argCount > 3) ? (args[3] << FRACBITS) : 0; + fixed_t zoffset = (argCount > 4) ? (args[4] << FRACBITS) : 0; + fixed_t distance = (argCount > 5) ? (args[5] << FRACBITS) : 64*FRACUNIT; + + if (args[0] == 0) + { + if (activator != NULL) + { + count += DoSpawnDecal(activator, tpl, flags, angle, zoffset, distance); + } + } + else + { + FActorIterator it(args[0]); + AActor *actor; + + while ((actor = it.Next()) != NULL) + { + count += DoSpawnDecal(actor, tpl, flags, angle, zoffset, distance); + } + } + } + return count; + } + break; + + case ACSF_CheckFont: + // bool CheckFont(str fontname) + return V_GetFont(FBehavior::StaticLookupString(args[0])) != NULL; + default: break; } @@ -3945,20 +5230,6 @@ inline int getshort (int *&pc) return res; } -// Read a possibly unaligned four-byte little endian integer. -#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) -inline int uallong(int &foo) -{ - return foo; -} -#else -inline int uallong(int &foo) -{ - unsigned char *bar = (unsigned char *)&foo; - return bar[0] | (bar[1] << 8) | (bar[2] << 16) | (bar[3] << 24); -} -#endif - int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; @@ -4025,7 +5296,7 @@ int DLevelScript::RunScript () int sp = 0; int *pc = this->pc; ACSFormat fmt = activeBehavior->GetFormat(); - int runaway = 0; // used to prevent infinite loops + unsigned int runaway = 0; // used to prevent infinite loops int pcd; FString work; const char *lookup; @@ -4072,7 +5343,8 @@ int DLevelScript::RunScript () break; case PCD_TAGSTRING: - Stack[sp-1] |= activeBehavior->GetLibraryID(); + //Stack[sp-1] |= activeBehavior->GetLibraryID(); + Stack[sp-1] = GlobalACSStrings.AddString(activeBehavior->LookupString(Stack[sp-1]), Stack, sp); break; case PCD_PUSHNUMBER: @@ -4270,7 +5542,7 @@ int DLevelScript::RunScript () int argCount = NEXTBYTE; int funcIndex = NEXTSHORT; - int retval = CallFunction(argCount, funcIndex, &STACK(argCount)); + int retval = CallFunction(argCount, funcIndex, &STACK(argCount), Stack, sp); sp -= argCount-1; STACK(1) = retval; } @@ -4329,7 +5601,7 @@ int DLevelScript::RunScript () } sp += i; ::new(&Stack[sp]) CallReturn(activeBehavior->PC2Ofs(pc), activeFunction, - activeBehavior, mylocals, pcd == PCD_CALLDISCARD); + activeBehavior, mylocals, pcd == PCD_CALLDISCARD, runaway); sp += (sizeof(CallReturn) + sizeof(int) - 1) / sizeof(int); pc = module->Ofs2PC (func->Address); activeFunction = func; @@ -4358,6 +5630,7 @@ int DLevelScript::RunScript () } sp -= sizeof(CallReturn)/sizeof(int); retsp = &Stack[sp]; + activeBehavior->GetFunctionProfileData(activeFunction)->AddRun(runaway - ret->EntryInstrCount); sp = int(locals - Stack); pc = ret->ReturnModule->Ofs2PC(ret->ReturnAddress); activeFunction = ret->ReturnFunction; @@ -5131,6 +6404,11 @@ int DLevelScript::RunScript () pc = activeBehavior->Ofs2PC (LittleLong(*pc)); break; + case PCD_GOTOSTACK: + pc = activeBehavior->Jump2PC (STACK(1)); + sp--; + break; + case PCD_IFGOTO: if (STACK(1)) pc = activeBehavior->Ofs2PC (LittleLong(*pc)); @@ -5352,7 +6630,7 @@ scriptwait: if (activationline != NULL) { activationline->special = 0; - DPrintf("Cleared line special on line %d\n", activationline - lines); + DPrintf("Cleared line special on line %d\n", (int)(activationline - lines)); } break; @@ -5493,7 +6771,7 @@ scriptwait: } if (player) { - work += player->userinfo.netname; + work += player->userinfo.GetName(); } else if (activator) { @@ -5730,7 +7008,16 @@ scriptwait: } break; } + msg->SetClipRect(ClipRectLeft, ClipRectTop, ClipRectWidth, ClipRectHeight); + if (WrapWidth != 0) + { + msg->SetWrapWidth(WrapWidth); + } msg->SetVisibility((type & HUDMSG_VISIBILITY_MASK) >> HUDMSG_VISIBILITY_SHIFT); + if (type & HUDMSG_NOWRAP) + { + msg->SetNoWrap(true); + } if (type & HUDMSG_ALPHA) { msg->SetAlpha(alpha); @@ -6691,7 +7978,7 @@ scriptwait: break; case PCD_GETACTORPROPERTY: - STACK(2) = GetActorProperty (STACK(2), STACK(1)); + STACK(2) = GetActorProperty (STACK(2), STACK(1), Stack, sp); sp -= 1; break; @@ -6773,18 +8060,7 @@ scriptwait: break; case PCD_GETCVAR: - { - FBaseCVar *cvar = FindCVar (FBehavior::StaticLookupString (STACK(1)), NULL); - if (cvar == NULL) - { - STACK(1) = 0; - } - else - { - UCVarValue val = cvar->GetGenericRep (CVAR_Int); - STACK(1) = val.Int; - } - } + STACK(1) = GetCVar(activator, FBehavior::StaticLookupString(STACK(1)), false, Stack, sp); break; case PCD_SETHUDSIZE: @@ -6968,14 +8244,14 @@ scriptwait: userinfo_t *userinfo = &pl->userinfo; switch (STACK(1)) { - case PLAYERINFO_TEAM: STACK(2) = userinfo->team; break; + case PLAYERINFO_TEAM: STACK(2) = userinfo->GetTeam(); break; case PLAYERINFO_AIMDIST: STACK(2) = userinfo->GetAimDist(); break; - case PLAYERINFO_COLOR: STACK(2) = userinfo->color; break; - case PLAYERINFO_GENDER: STACK(2) = userinfo->gender; break; - case PLAYERINFO_NEVERSWITCH: STACK(2) = userinfo->neverswitch; break; - case PLAYERINFO_MOVEBOB: STACK(2) = userinfo->MoveBob; break; - case PLAYERINFO_STILLBOB: STACK(2) = userinfo->StillBob; break; - case PLAYERINFO_PLAYERCLASS: STACK(2) = userinfo->PlayerClass; break; + case PLAYERINFO_COLOR: STACK(2) = userinfo->GetColor(); break; + case PLAYERINFO_GENDER: STACK(2) = userinfo->GetGender(); break; + case PLAYERINFO_NEVERSWITCH: STACK(2) = userinfo->GetNeverSwitch(); break; + case PLAYERINFO_MOVEBOB: STACK(2) = userinfo->GetMoveBob(); break; + case PLAYERINFO_STILLBOB: STACK(2) = userinfo->GetStillBob(); break; + case PLAYERINFO_PLAYERCLASS: STACK(2) = userinfo->GetPlayerClassNum(); break; case PLAYERINFO_DESIREDFOV: STACK(2) = (int)pl->DesiredFOV; break; case PLAYERINFO_FOV: STACK(2) = (int)pl->FOV; break; default: STACK(2) = 0; break; @@ -7072,19 +8348,13 @@ scriptwait: if (tag == 0) { - if (activator->player) + if (activator != NULL && activator->player) { - if (P_MorphPlayer(activator->player, activator->player, playerclass, duration, style, morphflash, unmorphflash)) - { - changes++; - } + changes += P_MorphPlayer(activator->player, activator->player, playerclass, duration, style, morphflash, unmorphflash); } else { - if (P_MorphMonster(activator, monsterclass, duration, style, morphflash, unmorphflash)) - { - changes++; - } + changes += P_MorphMonster(activator, monsterclass, duration, style, morphflash, unmorphflash); } } else @@ -7096,17 +8366,12 @@ scriptwait: { if (actor->player) { - if (P_MorphPlayer(activator->player, actor->player, playerclass, duration, style, morphflash, unmorphflash)) - { - changes++; - } + changes += P_MorphPlayer(activator == NULL ? NULL : activator->player, + actor->player, playerclass, duration, style, morphflash, unmorphflash); } else { - if (P_MorphMonster(actor, monsterclass, duration, style, morphflash, unmorphflash)) - { - changes++; - } + changes += P_MorphMonster(actor, monsterclass, duration, style, morphflash, unmorphflash); } } } @@ -7179,15 +8444,7 @@ scriptwait: case PCD_SAVESTRING: // Saves the string { - unsigned int str_otf = ACS_StringsOnTheFly.Push(work); - if (str_otf > 0xffff) - { - PushToStack(-1); - } - else - { - PushToStack((SDWORD)str_otf|ACSSTRING_OR_ONTHEFLY); - } + PushToStack(GlobalACSStrings.AddString(work, Stack, sp)); STRINGBUILDER_FINISH(work); } break; @@ -7235,7 +8492,12 @@ scriptwait: { case PCD_STRCPYTOMAPCHRANGE: { - Stack[sp-6] = activeBehavior->CopyStringToArray(STACK(5), index, capacity, lookup); + int a = STACK(5); + if (a < NUM_MAPVARS && a > 0 && + activeBehavior->MapVars[a]) + { + Stack[sp-6] = activeBehavior->CopyStringToArray(*(activeBehavior->MapVars[a]), index, capacity, lookup); + } } break; case PCD_STRCPYTOWORLDCHRANGE: @@ -7273,6 +8535,11 @@ scriptwait: } } + if (runaway != 0 && InModuleScriptNumber >= 0) + { + activeBehavior->GetScriptPtr(InModuleScriptNumber)->ProfileData.AddRun(runaway); + } + if (state == SCRIPT_DivideBy0) { Printf ("Divide by zero in %s\n", ScriptPresentation(script).GetChars()); @@ -7339,12 +8606,15 @@ DLevelScript::DLevelScript (AActor *who, line_t *where, int num, const ScriptPtr localvars[i] = args[i]; } pc = module->GetScriptAddress(code); + InModuleScriptNumber = module->GetScriptIndex(code); activator = who; activationline = where; backSide = flags & ACS_BACKSIDE; activefont = SmallFont; hudwidth = hudheight = 0; + ClipRectLeft = ClipRectTop = ClipRectWidth = ClipRectHeight = WrapWidth = 0; state = SCRIPT_Running; + // Hexen waited one second before executing any open scripts. I didn't realize // this when I wrote my ACS implementation. Now that I know, it's still best to // run them right away because there are several map properties that can't be @@ -7471,7 +8741,7 @@ int P_StartScript (AActor *who, line_t *where, int script, const char *map, cons if (!(scriptdata->Flags & SCRIPTF_Net)) { Printf(PRINT_BOLD, "%s tried to puke %s (\n", - who->player->userinfo.netname, ScriptPresentation(script).GetChars()); + who->player->userinfo.GetName(), ScriptPresentation(script).GetChars()); for (int i = 0; i < argcount; ++i) { Printf(PRINT_BOLD, "%d%s", args[i], i == argcount-1 ? "" : ", "); @@ -7600,3 +8870,261 @@ void DACSThinker::DumpScriptStatus () script = script->next; } } + +// Profiling support -------------------------------------------------------- + +ACSProfileInfo::ACSProfileInfo() +{ + Reset(); +} + +void ACSProfileInfo::Reset() +{ + TotalInstr = 0; + NumRuns = 0; + MinInstrPerRun = UINT_MAX; + MaxInstrPerRun = 0; +} + +void ACSProfileInfo::AddRun(unsigned int num_instr) +{ + TotalInstr += num_instr; + NumRuns++; + if (num_instr < MinInstrPerRun) + { + MinInstrPerRun = num_instr; + } + if (num_instr > MaxInstrPerRun) + { + MaxInstrPerRun = num_instr; + } +} + +void ArrangeScriptProfiles(TArray &profiles) +{ + for (unsigned int mod_num = 0; mod_num < FBehavior::StaticModules.Size(); ++mod_num) + { + FBehavior *module = FBehavior::StaticModules[mod_num]; + ProfileCollector prof; + prof.Module = module; + for (int i = 0; i < module->NumScripts; ++i) + { + prof.Index = i; + prof.ProfileData = &module->Scripts[i].ProfileData; + profiles.Push(prof); + } + } +} + +void ArrangeFunctionProfiles(TArray &profiles) +{ + for (unsigned int mod_num = 0; mod_num < FBehavior::StaticModules.Size(); ++mod_num) + { + FBehavior *module = FBehavior::StaticModules[mod_num]; + ProfileCollector prof; + prof.Module = module; + for (int i = 0; i < module->NumFunctions; ++i) + { + ScriptFunction *func = (ScriptFunction *)module->Functions + i; + if (func->ImportNum == 0) + { + prof.Index = i; + prof.ProfileData = module->FunctionProfileData + i; + profiles.Push(prof); + } + } + } +} + +void ClearProfiles(TArray &profiles) +{ + for (unsigned int i = 0; i < profiles.Size(); ++i) + { + profiles[i].ProfileData->Reset(); + } +} + +static int STACK_ARGS sort_by_total_instr(const void *a_, const void *b_) +{ + const ProfileCollector *a = (const ProfileCollector *)a_; + const ProfileCollector *b = (const ProfileCollector *)b_; + + assert(a != NULL && a->ProfileData != NULL); + assert(b != NULL && b->ProfileData != NULL); + return (int)(b->ProfileData->TotalInstr - a->ProfileData->TotalInstr); +} + +static int STACK_ARGS sort_by_min(const void *a_, const void *b_) +{ + const ProfileCollector *a = (const ProfileCollector *)a_; + const ProfileCollector *b = (const ProfileCollector *)b_; + + return b->ProfileData->MinInstrPerRun - a->ProfileData->MinInstrPerRun; +} + +static int STACK_ARGS sort_by_max(const void *a_, const void *b_) +{ + const ProfileCollector *a = (const ProfileCollector *)a_; + const ProfileCollector *b = (const ProfileCollector *)b_; + + return b->ProfileData->MaxInstrPerRun - a->ProfileData->MaxInstrPerRun; +} + +static int STACK_ARGS sort_by_avg(const void *a_, const void *b_) +{ + const ProfileCollector *a = (const ProfileCollector *)a_; + const ProfileCollector *b = (const ProfileCollector *)b_; + + int a_avg = a->ProfileData->NumRuns == 0 ? 0 : int(a->ProfileData->TotalInstr / a->ProfileData->NumRuns); + int b_avg = b->ProfileData->NumRuns == 0 ? 0 : int(b->ProfileData->TotalInstr / b->ProfileData->NumRuns); + return b_avg - a_avg; +} + +static int STACK_ARGS sort_by_runs(const void *a_, const void *b_) +{ + const ProfileCollector *a = (const ProfileCollector *)a_; + const ProfileCollector *b = (const ProfileCollector *)b_; + + return b->ProfileData->NumRuns - a->ProfileData->NumRuns; +} + +static void ShowProfileData(TArray &profiles, long ilimit, + int (STACK_ARGS *sorter)(const void *, const void *), bool functions) +{ + static const char *const typelabels[2] = { "script", "function" }; + + if (profiles.Size() == 0) + { + return; + } + + unsigned int limit; + char modname[13]; + char scriptname[21]; + + qsort(&profiles[0], profiles.Size(), sizeof(ProfileCollector), sorter); + + if (ilimit > 0) + { + Printf(TEXTCOLOR_ORANGE "Top %ld %ss:\n", ilimit, typelabels[functions]); + limit = (unsigned int)ilimit; + } + else + { + Printf(TEXTCOLOR_ORANGE "All %ss:\n", typelabels[functions]); + limit = UINT_MAX; + } + + Printf(TEXTCOLOR_YELLOW "Module %-20s Total Runs Avg Min Max\n", typelabels[functions]); + Printf(TEXTCOLOR_YELLOW "------------ -------------------- ---------- ------- ------- ------- -------\n"); + for (unsigned int i = 0; i < limit && i < profiles.Size(); ++i) + { + ProfileCollector *prof = &profiles[i]; + if (prof->ProfileData->NumRuns == 0) + { // Don't list ones that haven't run. + continue; + } + + // Module name + mysnprintf(modname, sizeof(modname), "%s", prof->Module->GetModuleName()); + + // Script/function name + if (functions) + { + DWORD *fnames = (DWORD *)prof->Module->FindChunk(MAKE_ID('F','N','A','M')); + if (prof->Index >= 0 && prof->Index < (int)LittleLong(fnames[2])) + { + mysnprintf(scriptname, sizeof(scriptname), "%s", + (char *)(fnames + 2) + LittleLong(fnames[3+prof->Index])); + } + else + { + mysnprintf(scriptname, sizeof(scriptname), "Function %d", prof->Index); + } + } + else + { + mysnprintf(scriptname, sizeof(scriptname), "%s", + ScriptPresentation(prof->Module->GetScriptPtr(prof->Index)->Number).GetChars() + 7); + } + Printf("%-12s %-20s%11llu%8u%8u%8u%8u\n", + modname, scriptname, + prof->ProfileData->TotalInstr, + prof->ProfileData->NumRuns, + unsigned(prof->ProfileData->TotalInstr / prof->ProfileData->NumRuns), + prof->ProfileData->MinInstrPerRun, + prof->ProfileData->MaxInstrPerRun + ); + } +} + +CCMD(acsprofile) +{ + static int (STACK_ARGS *sort_funcs[])(const void*, const void *) = + { + sort_by_total_instr, + sort_by_min, + sort_by_max, + sort_by_avg, + sort_by_runs + }; + static const char *sort_names[] = { "total", "min", "max", "avg", "runs" }; + static const BYTE sort_match_len[] = { 1, 2, 2, 1, 1 }; + + TArray ScriptProfiles, FuncProfiles; + long limit = 10; + int (STACK_ARGS *sorter)(const void *, const void *) = sort_by_total_instr; + + assert(countof(sort_names) == countof(sort_match_len)); + + ArrangeScriptProfiles(ScriptProfiles); + ArrangeFunctionProfiles(FuncProfiles); + + if (argv.argc() > 1) + { + // `acsprofile clear` will zero all profiling information collected so far. + if (stricmp(argv[1], "clear") == 0) + { + ClearProfiles(ScriptProfiles); + ClearProfiles(FuncProfiles); + return; + } + for (int i = 1; i < argv.argc(); ++i) + { + // If it's a number, set the display limit. + char *endptr; + long num = strtol(argv[i], &endptr, 0); + if (endptr != argv[i]) + { + limit = num; + continue; + } + // If it's a name, set the sort method. We accept partial matches for + // options that are shorter than the sort name. + size_t optlen = strlen(argv[i]); + unsigned int j; + for (j = 0; j < countof(sort_names); ++j) + { + if (optlen < sort_match_len[j] || optlen > strlen(sort_names[j])) + { // Too short or long to match. + continue; + } + if (strnicmp(argv[i], sort_names[j], optlen) == 0) + { + sorter = sort_funcs[j]; + break; + } + } + if (j == countof(sort_names)) + { + Printf("Unknown option '%s'\n", argv[i]); + Printf("acsprofile clear : Reset profiling information\n"); + Printf("acsprofile [total|min|max|avg|runs] []\n"); + return; + } + } + } + + ShowProfileData(ScriptProfiles, limit, sorter, false); + ShowProfileData(FuncProfiles, limit, sorter, true); +} diff --git a/src/p_acs.h b/src/p_acs.h index d2ae2038b..1a0fb3f60 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -69,11 +69,80 @@ extern FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS]; extern SDWORD ACS_GlobalVars[NUM_GLOBALVARS]; extern FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS]; +#define LIBRARYID_MASK 0xFFF00000 +#define LIBRARYID_SHIFT 20 + +// Global ACS string table +#define STRPOOL_LIBRARYID (INT_MAX >> LIBRARYID_SHIFT) +#define STRPOOL_LIBRARYID_OR (STRPOOL_LIBRARYID << LIBRARYID_SHIFT) + +class ACSStringPool +{ +public: + ACSStringPool(); + int AddString(const char *str, const SDWORD *stack, int stackdepth); + int AddString(FString &str, const SDWORD *stack, int stackdepth); + const char *GetString(int strnum); + void LockString(int strnum); + void UnlockString(int strnum); + void UnlockAll(); + void MarkString(int strnum); + void LockStringArray(const int *strnum, unsigned int count); + void UnlockStringArray(const int *strnum, unsigned int count); + void MarkStringArray(const int *strnum, unsigned int count); + void MarkStringMap(const FWorldGlobalArray &array); + void PurgeStrings(); + void Clear(); + void Dump() const; + void ReadStrings(PNGHandle *png, DWORD id); + void WriteStrings(FILE *file, DWORD id) const; + +private: + int FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum); + int InsertString(FString &str, unsigned int h, unsigned int bucketnum, const SDWORD *stack, int stackdepth); + + enum { NUM_BUCKETS = 251 }; + enum { FREE_ENTRY = 0xFFFFFFFE }; // Stored in PoolEntry's Next field + enum { NO_ENTRY = 0xFFFFFFFF }; + enum { MIN_GC_SIZE = 100 }; // Don't auto-collect until there are this many strings + struct PoolEntry + { + FString Str; + unsigned int Hash; + unsigned int Next; + unsigned int LockCount; + }; + TArray Pool; + unsigned int PoolBuckets[NUM_BUCKETS]; + unsigned int FirstFreeEntry; +}; +extern ACSStringPool GlobalACSStrings; + +void P_CollectACSGlobalStrings(const SDWORD *stack, int stackdepth); void P_ReadACSVars(PNGHandle *); void P_WriteACSVars(FILE*); void P_ClearACSVars(bool); void P_SerializeACSScriptNumber(FArchive &arc, int &scriptnum, bool was2byte); +struct ACSProfileInfo +{ + unsigned long long TotalInstr; + unsigned int NumRuns; + unsigned int MinInstrPerRun; + unsigned int MaxInstrPerRun; + + ACSProfileInfo(); + void AddRun(unsigned int num_instr); + void Reset(); +}; + +struct ProfileCollector +{ + ACSProfileInfo *ProfileData; + class FBehavior *Module; + int Index; +}; + // The in-memory version struct ScriptPtr { @@ -83,6 +152,8 @@ struct ScriptPtr BYTE ArgCount; WORD VarCount; WORD Flags; + + ACSProfileInfo ProfileData; }; // The present ZDoom version @@ -165,6 +236,7 @@ public: void StartTypedScripts (WORD type, AActor *activator, bool always, int arg1, bool runNow); DWORD PC2Ofs (int *pc) const { return (DWORD)((BYTE *)pc - Data); } int *Ofs2PC (DWORD ofs) const { return (int *)(Data + ofs); } + int *Jump2PC (DWORD jumpPoint) const { return Ofs2PC(JumpPoints[jumpPoint]); } ACSFormat GetFormat() const { return Format; } ScriptFunction *GetFunction (int funcnum, FBehavior *&module) const; int GetArrayVal (int arraynum, int index) const; @@ -176,6 +248,13 @@ public: int FindMapArray (const char *arrayname) const; int GetLibraryID () const { return LibraryID; } int *GetScriptAddress (const ScriptPtr *ptr) const { return (int *)(ptr->Address + Data); } + int GetScriptIndex (const ScriptPtr *ptr) const { ptrdiff_t index = ptr - Scripts; return index >= NumScripts ? -1 : (int)index; } + ScriptPtr *GetScriptPtr(int index) const { return index >= 0 && index < NumScripts ? &Scripts[index] : NULL; } + int GetLumpNum() const { return LumpNum; } + const char *GetModuleName() const { return ModuleName; } + ACSProfileInfo *GetFunctionProfileData(int index) { return index >= 0 && index < NumFunctions ? &FunctionProfileData[index] : NULL; } + ACSProfileInfo *GetFunctionProfileData(ScriptFunction *func) { return GetFunctionProfileData((int)(func - (ScriptFunction *)Functions)); } + const char *LookupString (DWORD index) const; SDWORD *MapVars[NUM_MAPVARS]; @@ -185,6 +264,9 @@ public: static bool StaticCheckAllGood (); static FBehavior *StaticGetModule (int lib); static void StaticSerializeModuleStates (FArchive &arc); + static void StaticMarkLevelVarStrings(); + static void StaticLockLevelVarStrings(); + static void StaticUnlockLevelVarStrings(); static const ScriptPtr *StaticFindScript (int script, FBehavior *&module); static const char *StaticLookupString (DWORD index); @@ -203,6 +285,7 @@ private: ScriptPtr *Scripts; int NumScripts; BYTE *Functions; + ACSProfileInfo *FunctionProfileData; int NumFunctions; ArrayInfo *ArrayStore; int NumArrays; @@ -213,6 +296,7 @@ private: TArray Imports; DWORD LibraryID; char ModuleName[9]; + TArray JumpPoints; static TArray StaticModules; @@ -222,10 +306,16 @@ private: void UnencryptStrings (); void UnescapeStringTable(BYTE *chunkstart, BYTE *datastart, bool haspadding); int FindStringInChunk (DWORD *chunk, const char *varname) const; - const char *LookupString (DWORD index) const; void SerializeVars (FArchive &arc); void SerializeVarSet (FArchive &arc, SDWORD *vars, int max); + + void MarkMapVarStrings() const; + void LockMapVarStrings() const; + void UnlockMapVarStrings() const; + + friend void ArrangeScriptProfiles(TArray &profiles); + friend void ArrangeFunctionProfiles(TArray &profiles); }; class DLevelScript : public DObject @@ -602,6 +692,7 @@ public: /*360*/ PCD_CALLSTACK, // from Eternity PCD_SCRIPTWAITNAMED, PCD_TRANSLATIONRANGE3, + PCD_GOTOSTACK, /*363*/ PCODE_COMMAND_COUNT }; @@ -694,6 +785,21 @@ public: inline void SetState (EScriptState newstate) { state = newstate; } inline EScriptState GetState () { return state; } + DLevelScript *GetNext() const { return next; } + + void MarkLocalVarStrings() const + { + GlobalACSStrings.MarkStringArray(localvars, numlocalvars); + } + void LockLocalVarStrings() const + { + GlobalACSStrings.LockStringArray(localvars, numlocalvars); + } + void UnlockLocalVarStrings() const + { + GlobalACSStrings.UnlockStringArray(localvars, numlocalvars); + } + protected: DLevelScript *next, *prev; int script; @@ -707,7 +813,10 @@ protected: bool backSide; FFont *activefont; int hudwidth, hudheight; + int ClipRectLeft, ClipRectTop, ClipRectWidth, ClipRectHeight; + int WrapWidth; FBehavior *activeBehavior; + int InModuleScriptNumber; void Link (); void Unlink (); @@ -724,7 +833,7 @@ protected: int DoSpawnSpot (int type, int spot, int tid, int angle, bool forced); int DoSpawnSpotFacing (int type, int spot, int tid, bool forced); int DoClassifyActor (int tid); - int CallFunction(int argCount, int funcIndex, SDWORD *args); + int CallFunction(int argCount, int funcIndex, SDWORD *args, const SDWORD *stack, int stackdepth); void DoFadeTo (int r, int g, int b, int a, fixed_t time); void DoFadeRange (int r1, int g1, int b1, int a1, @@ -732,7 +841,7 @@ protected: void DoSetFont (int fontnum); void SetActorProperty (int tid, int property, int value); void DoSetActorProperty (AActor *actor, int property, int value); - int GetActorProperty (int tid, int property); + int GetActorProperty (int tid, int property, const SDWORD *stack, int stackdepth); int CheckActorProperty (int tid, int property, int value); int GetPlayerInput (int playernum, int inputnum); @@ -768,6 +877,7 @@ private: DLevelScript *Scripts; // List of all running scripts friend class DLevelScript; + friend class FBehavior; }; // The structure used to control scripts between maps diff --git a/src/p_buildmap.cpp b/src/p_buildmap.cpp index f5a335411..0c94945e8 100644 --- a/src/p_buildmap.cpp +++ b/src/p_buildmap.cpp @@ -175,12 +175,12 @@ bool P_IsBuildMap(MapData *map) return true; } - numsectors = LittleShort(*(WORD *)(data + 20)); + const int numsec = LittleShort(*(WORD *)(data + 20)); int numwalls; - if (len < 26 + numsectors*sizeof(sectortype) || - (numwalls = LittleShort(*(WORD *)(data + 22 + numsectors*sizeof(sectortype))), - len < 24 + numsectors*sizeof(sectortype) + numwalls*sizeof(walltype)) || + if (len < 26 + numsec*sizeof(sectortype) || + (numwalls = LittleShort(*(WORD *)(data + 22 + numsec*sizeof(sectortype))), + len < 24 + numsec*sizeof(sectortype) + numwalls*sizeof(walltype)) || LittleLong(*(DWORD *)data) != 7 || LittleShort(*(WORD *)(data + 16)) >= 2048) { // Can't possibly be a version 7 BUILD map @@ -210,19 +210,20 @@ bool P_LoadBuildMap (BYTE *data, size_t len, FMapThing **sprites, int *numspr) return P_LoadBloodMap (data, len, sprites, numspr); } - numsectors = LittleShort(*(WORD *)(data + 20)); + const int numsec = LittleShort(*(WORD *)(data + 20)); int numwalls; int numsprites; - if (len < 26 + numsectors*sizeof(sectortype) || - (numwalls = LittleShort(*(WORD *)(data + 22 + numsectors*sizeof(sectortype))), - len < 24 + numsectors*sizeof(sectortype) + numwalls*sizeof(walltype)) || + if (len < 26 + numsec*sizeof(sectortype) || + (numwalls = LittleShort(*(WORD *)(data + 22 + numsec*sizeof(sectortype))), + len < 24 + numsec*sizeof(sectortype) + numwalls*sizeof(walltype)) || LittleLong(*(DWORD *)data) != 7 || LittleShort(*(WORD *)(data + 16)) >= 2048) { // Can't possibly be a version 7 BUILD map return false; } + numsectors = numsec; LoadSectors ((sectortype *)(data + 22)); LoadWalls ((walltype *)(data + 24 + numsectors*sizeof(sectortype)), numwalls, (sectortype *)(data + 22)); diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index ae483fc66..c49c33575 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -550,6 +550,15 @@ bool P_Move (AActor *actor) { actor->z = savedz; } + else + { // The monster just hit the floor, so trigger any actions. + if (actor->floorsector->SecActTarget != NULL && + actor->floorz == actor->floorsector->floorplane.ZatPoint(actor->x, actor->y)) + { + actor->floorsector->SecActTarget->TriggerAction(actor, SECSPAC_HitFloor); + } + P_CheckFor3DFloorHit(actor); + } } } @@ -1869,7 +1878,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LookEx) { if (!(flags & LOF_NOSOUNDCHECK)) { - targ = (self->flags & MF_NOSECTOR)? self->Sector->SoundTarget : self->LastHeard; + targ = (i_compatflags & COMPATF_SOUNDTARGET || self->flags & MF_NOSECTOR)? + self->Sector->SoundTarget : self->LastHeard; if (targ != NULL) { // [RH] If the soundtarget is dead, don't chase it @@ -2801,9 +2811,18 @@ void A_Face (AActor *self, AActor *other, angle_t max_turn, angle_t max_pitch) { // [DH] Don't need to do proper fixed->double conversion, since the // result is only used in a ratio. - double dist_x = self->target->x - self->x; - double dist_y = self->target->y - self->y; - double dist_z = self->target->z - self->z; + double dist_x = other->x - self->x; + double dist_y = other->y - self->y; + // Positioning ala missile spawning, 32 units above foot level + fixed_t source_z = self->z + 32*FRACUNIT + self->GetBobOffset(); + fixed_t target_z = other->z + 32*FRACUNIT + other->GetBobOffset(); + // If the target z is above the target's head, reposition to the middle of + // its body. + if (target_z >= other->z + other->height) + { + target_z = other->z + other->height / 2; + } + double dist_z = target_z - source_z; double dist = sqrt(dist_x*dist_x + dist_y*dist_y + dist_z*dist_z); int other_pitch = (int)rad2bam(asin(dist_z / dist)); @@ -3043,17 +3062,22 @@ void ModifyDropAmount(AInventory *inv, int dropamount) int amount = static_cast(inv->GetClass())->DropAmount; if (amount <= 0) { - amount = FixedMul(inv->Amount, dropammofactor); + amount = MAX(1, FixedMul(inv->Amount, dropammofactor)); } inv->Amount = amount; inv->ItemFlags |= flagmask; } + else if (inv->IsKindOf (RUNTIME_CLASS(AWeaponGiver))) + { + static_cast(inv)->DropAmmoFactor = dropammofactor; + inv->ItemFlags |= flagmask; + } else if (inv->IsKindOf (RUNTIME_CLASS(AWeapon))) { // The same goes for ammo from a weapon. static_cast(inv)->AmmoGive1 = FixedMul(static_cast(inv)->AmmoGive1, dropammofactor); static_cast(inv)->AmmoGive2 = FixedMul(static_cast(inv)->AmmoGive2, dropammofactor); - inv->ItemFlags|=flagmask; + inv->ItemFlags |= flagmask; } else if (inv->IsKindOf (RUNTIME_CLASS(ADehackedPickup))) { @@ -3109,6 +3133,8 @@ AInventory *P_DropItem (AActor *source, PClassActor *type, int dropamount, int c ModifyDropAmount(inv, dropamount); if (inv->SpecialDropAction (source)) { + // The special action indicates that the item should not spawn + inv->Destroy(); return NULL; } return inv; diff --git a/src/p_floor.cpp b/src/p_floor.cpp index d88886b14..e9768479b 100644 --- a/src/p_floor.cpp +++ b/src/p_floor.cpp @@ -375,18 +375,18 @@ manual_floor: floor->m_Direction = (floor->m_FloorDestDist > sec->floorplane.d) ? -1 : 1; break; - case DFloor::floorRaiseAndCrush: + case DFloor::floorRaiseAndCrushDoom: floor->m_Crush = crush; case DFloor::floorRaiseToLowestCeiling: floor->m_Direction = 1; newheight = sec->FindLowestCeilingSurrounding (&spot); - if (floortype == DFloor::floorRaiseAndCrush) + if (floortype == DFloor::floorRaiseAndCrushDoom) newheight -= 8 * FRACUNIT; ceilingheight = sec->FindLowestCeilingPoint (&spot2); floor->m_FloorDestDist = sec->floorplane.PointToDist (spot, newheight); if (sec->floorplane.ZatPointDist (spot2, floor->m_FloorDestDist) > ceilingheight) floor->m_FloorDestDist = sec->floorplane.PointToDist (spot2, - floortype == DFloor::floorRaiseAndCrush ? ceilingheight - 8*FRACUNIT : ceilingheight); + floortype == DFloor::floorRaiseAndCrushDoom ? ceilingheight - 8*FRACUNIT : ceilingheight); break; case DFloor::floorRaiseToHighest: @@ -407,6 +407,13 @@ manual_floor: floor->m_FloorDestDist = sec->floorplane.PointToDist (spot, newheight); break; + case DFloor::floorRaiseAndCrush: + floor->m_Crush = crush; + floor->m_Direction = 1; + newheight = sec->FindLowestCeilingPoint (&spot) - 8*FRACUNIT; + floor->m_FloorDestDist = sec->floorplane.PointToDist (spot, newheight); + break; + case DFloor::floorRaiseToCeiling: floor->m_Direction = 1; newheight = sec->FindLowestCeilingPoint (&spot); diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp index ee32df4b0..694c1684b 100644 --- a/src/p_glnodes.cpp +++ b/src/p_glnodes.cpp @@ -230,18 +230,18 @@ bool P_CheckForGLNodes() static int firstglvertex; static bool format5; -static bool LoadGLVertexes(FileReader * f, wadlump_t * lump) +static bool LoadGLVertexes(FileReader * lump) { BYTE *gldata; int i; firstglvertex = numvertexes; - int gllen=lump->Size; + int gllen=lump->GetLength(); gldata = new BYTE[gllen]; - f->Seek(lump->FilePos, SEEK_SET); - f->Read(gldata, gllen); + lump->Seek(0, SEEK_SET); + lump->Read(gldata, gllen); if (*(int *)gldata == gNd5) { @@ -310,16 +310,16 @@ static inline int checkGLVertex3(int num) // //========================================================================== -static bool LoadGLSegs(FileReader * f, wadlump_t * lump) +static bool LoadGLSegs(FileReader * lump) { char *data; int i; line_t *ldef=NULL; - numsegs = lump->Size; + numsegs = lump->GetLength(); data= new char[numsegs]; - f->Seek(lump->FilePos, SEEK_SET); - f->Read(data, lump->Size); + lump->Seek(0, SEEK_SET); + lump->Read(data, numsegs); segs=NULL; #ifdef _MSC_VER @@ -442,15 +442,15 @@ static bool LoadGLSegs(FileReader * f, wadlump_t * lump) // //========================================================================== -static bool LoadGLSubsectors(FileReader * f, wadlump_t * lump) +static bool LoadGLSubsectors(FileReader * lump) { char * datab; int i; - numsubsectors = lump->Size; + numsubsectors = lump->GetLength(); datab = new char[numsubsectors]; - f->Seek(lump->FilePos, SEEK_SET); - f->Read(datab, lump->Size); + lump->Seek(0, SEEK_SET); + lump->Read(datab, numsubsectors); if (numsubsectors == 0) { @@ -524,7 +524,7 @@ static bool LoadGLSubsectors(FileReader * f, wadlump_t * lump) // //========================================================================== -static bool LoadNodes (FileReader * f, wadlump_t * lump) +static bool LoadNodes (FileReader * lump) { const int NF_SUBSECTOR = 0x8000; const int GL5_NF_SUBSECTOR = (1 << 31); @@ -538,15 +538,15 @@ static bool LoadNodes (FileReader * f, wadlump_t * lump) if (!format5) { mapnode_t* mn, * basemn; - numnodes = lump->Size / sizeof(mapnode_t); + numnodes = lump->GetLength() / sizeof(mapnode_t); if (numnodes == 0) return false; nodes = new node_t[numnodes]; - f->Seek(lump->FilePos, SEEK_SET); + lump->Seek(0, SEEK_SET); basemn = mn = new mapnode_t[numnodes]; - f->Read(mn, lump->Size); + lump->Read(mn, lump->GetLength()); used = (WORD *)alloca (sizeof(WORD)*numnodes); memset (used, 0, sizeof(WORD)*numnodes); @@ -598,15 +598,15 @@ static bool LoadNodes (FileReader * f, wadlump_t * lump) else { gl5_mapnode_t* mn, * basemn; - numnodes = lump->Size / sizeof(gl5_mapnode_t); + numnodes = lump->GetLength() / sizeof(gl5_mapnode_t); if (numnodes == 0) return false; nodes = new node_t[numnodes]; - f->Seek(lump->FilePos, SEEK_SET); + lump->Seek(0, SEEK_SET); basemn = mn = new gl5_mapnode_t[numnodes]; - f->Read(mn, lump->Size); + lump->Read(mn, lump->GetLength()); used = (WORD *)alloca (sizeof(WORD)*numnodes); memset (used, 0, sizeof(WORD)*numnodes); @@ -664,19 +664,19 @@ static bool LoadNodes (FileReader * f, wadlump_t * lump) // //========================================================================== -static bool DoLoadGLNodes(FileReader * f, wadlump_t * lumps) +static bool DoLoadGLNodes(FileReader ** lumps) { - if (!LoadGLVertexes(f, &lumps[0])) + if (!LoadGLVertexes(lumps[0])) { return false; } - if (!LoadGLSegs(f, &lumps[1])) + if (!LoadGLSegs(lumps[1])) { delete [] segs; segs = NULL; return false; } - if (!LoadGLSubsectors(f, &lumps[2])) + if (!LoadGLSubsectors(lumps[2])) { delete [] subsectors; subsectors = NULL; @@ -684,7 +684,7 @@ static bool DoLoadGLNodes(FileReader * f, wadlump_t * lumps) segs = NULL; return false; } - if (!LoadNodes(f, &lumps[3])) + if (!LoadNodes(lumps[3])) { delete [] nodes; nodes = NULL; @@ -794,18 +794,20 @@ static int FindGLNodesInWAD(int labellump) // FindGLNodesInWAD // // Looks for GL nodes in the same WAD as the level itself -// When this function returns the file pointer points to -// the directory entry of the GL_VERTS lump +// Function returns the lump number within the file. Returns -1 if the input +// resource file is NULL. // //=========================================================================== -static int FindGLNodesInFile(FileReader * f, const char * label) +static int FindGLNodesInFile(FResourceFile * f, const char * label) { + // No file open? Probably shouldn't happen but assume no GL nodes + if(!f) + return -1; + FString glheader; bool mustcheck=false; - DWORD id, dirofs, numentries; - DWORD offset, size; - char lumpname[9]; + DWORD numentries = f->LumpCount(); glheader.Format("GL_%.8s", label); if (glheader.Len()>8) @@ -814,25 +816,17 @@ static int FindGLNodesInFile(FileReader * f, const char * label) mustcheck=true; } - f->Seek(0, SEEK_SET); - (*f) >> id >> numentries >> dirofs; - - if ((id == IWAD_ID || id == PWAD_ID) && numentries > 4) + if (numentries > 4) { - f->Seek(dirofs, SEEK_SET); for(DWORD i=0;i> offset >> size; - f->Read(lumpname, 8); - if (!strnicmp(lumpname, glheader, 8)) + if (!strnicmp(f->GetLump(i)->Name, glheader, 8)) { if (mustcheck) { char check[16]={0}; - int filepos = f->Tell(); - f->Seek(offset, SEEK_SET); - f->Read(check, 16); - f->Seek(filepos, SEEK_SET); + FileReader *fr = f->GetLump(i)->GetReader(); + fr->Read(check, 16); if (MatchHeader(label, check)) return i; } else return i; @@ -851,17 +845,20 @@ static int FindGLNodesInFile(FileReader * f, const char * label) bool P_LoadGLNodes(MapData * map) { - if (map->MapLumps[ML_GLZNODES].Size != 0) + if (map->MapLumps[ML_GLZNODES].Reader && map->MapLumps[ML_GLZNODES].Reader->GetLength() != 0) { - const int idcheck = MAKE_ID('Z','G','L','N'); - const int idcheck2 = MAKE_ID('Z','G','L','2'); - const int idcheck3 = MAKE_ID('X','G','L','N'); - const int idcheck4 = MAKE_ID('X','G','L','2'); + const int idcheck1a = MAKE_ID('Z','G','L','N'); + const int idcheck2a = MAKE_ID('Z','G','L','2'); + const int idcheck3a = MAKE_ID('Z','G','L','3'); + const int idcheck1b = MAKE_ID('X','G','L','N'); + const int idcheck2b = MAKE_ID('X','G','L','2'); + const int idcheck3b = MAKE_ID('X','G','L','3'); int id; map->Seek(ML_GLZNODES); map->file->Read (&id, 4); - if (id == idcheck || id == idcheck2 || id == idcheck3 || id == idcheck4) + if (id == idcheck1a || id == idcheck2a || id == idcheck3a || + id == idcheck1b || id == idcheck2b || id == idcheck3b) { try { @@ -894,13 +891,12 @@ bool P_LoadGLNodes(MapData * map) if (!CheckCachedNodes(map)) { - wadlump_t gwalumps[4]; + FileReader *gwalumps[4] = { NULL, NULL, NULL, NULL }; char path[256]; int li; int lumpfile = Wads.GetLumpFile(map->lumpnum); - bool mapinwad = map->file == Wads.GetFileReader(lumpfile); - FileReader * fr = map->file; - FILE * f_gwa = NULL; + bool mapinwad = map->InWad; + FResourceFile * f_gwa = map->resource; const char * name = Wads.GetWadFullName(lumpfile); @@ -913,10 +909,9 @@ bool P_LoadGLNodes(MapData * map) // GL nodes are loaded with a WAD for(int i=0;i<4;i++) { - gwalumps[i].FilePos=Wads.GetLumpOffset(li+i+1); - gwalumps[i].Size=Wads.LumpLength(li+i+1); + gwalumps[i]=Wads.ReopenLumpNum(li+i+1); } - return DoLoadGLNodes(fr, gwalumps); + return DoLoadGLNodes(gwalumps); } else { @@ -928,41 +923,37 @@ bool P_LoadGLNodes(MapData * map) strcpy(ext, ".gwa"); // Todo: Compare file dates - f_gwa = fopen(path, "rb"); + f_gwa = FResourceFile::OpenResourceFile(path, NULL, true); if (f_gwa==NULL) return false; - fr = new FileReader(f_gwa); - strncpy(map->MapLumps[0].Name, Wads.GetLumpFullName(map->lumpnum), 8); } } } bool result = false; - li = FindGLNodesInFile(fr, map->MapLumps[0].Name); + li = FindGLNodesInFile(f_gwa, map->MapLumps[0].Name); if (li!=-1) { static const char check[][9]={"GL_VERT","GL_SEGS","GL_SSECT","GL_NODES"}; result=true; for(unsigned i=0; i<4;i++) { - (*fr) >> gwalumps[i].FilePos; - (*fr) >> gwalumps[i].Size; - fr->Read(gwalumps[i].Name, 8); - if (strnicmp(gwalumps[i].Name, check[i], 8)) + if (strnicmp(f_gwa->GetLump(i)->Name, check[i], 8)) { result=false; break; } + else + gwalumps[i] = f_gwa->GetLump(i)->NewReader(); } - if (result) result = DoLoadGLNodes(fr, gwalumps); + if (result) result = DoLoadGLNodes(gwalumps); } - if (f_gwa) - { - delete fr; - fclose(f_gwa); - } + if (f_gwa != map->resource) + delete f_gwa; + for(unsigned int i = 0;i < 4;++i) + delete gwalumps[i]; return result; } else return true; diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 69a26f84e..cd3ff8e4a 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -62,10 +62,11 @@ static FRandom pr_obituary ("Obituary"); static FRandom pr_botrespawn ("BotRespawn"); static FRandom pr_killmobj ("ActorDie"); -static FRandom pr_damagemobj ("ActorTakeDamage"); +FRandom pr_damagemobj ("ActorTakeDamage"); static FRandom pr_lightning ("LightningDamage"); static FRandom pr_poison ("PoisonDamage"); static FRandom pr_switcher ("SwitchTarget"); +static FRandom pr_kickbackdir ("KickbackDir"); CVAR (Bool, cl_showsprees, true, CVAR_ARCHIVE) CVAR (Bool, cl_showmultikills, true, CVAR_ARCHIVE) @@ -191,7 +192,7 @@ void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgf if (self->player == NULL || self->player->mo != self || !show_obituaries) return; - gender = self->player->userinfo.gender; + gender = self->player->userinfo.GetGender(); // Treat voodoo dolls as unknown deaths if (inflictor && inflictor->player && inflictor->player->mo != inflictor) @@ -269,7 +270,7 @@ void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgf attacker->player->fragcount -= 2; attacker->player->frags[attacker->player - players]++; self = attacker; - gender = self->player->userinfo.gender; + gender = self->player->userinfo.GetGender(); mysnprintf (gendermessage, countof(gendermessage), "OB_FRIENDLY%c", '1' + (pr_obituary() & 3)); message = GStrings(gendermessage); } @@ -316,7 +317,7 @@ void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgf } SexMessage (message, gendermessage, gender, - self->player->userinfo.netname, attacker->player->userinfo.netname); + self->player->userinfo.GetName(), attacker->player->userinfo.GetName()); Printf (PRINT_MEDIUM, "%s\n", gendermessage); } @@ -342,7 +343,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) int realgibhealth = realthis->GetGibHealth(); if (realthis->health >= realgibhealth) { - realthis->health = realgibhealth -1; // if morphed was gibbed, so must original be (where allowed) + realthis->health = realgibhealth -1; // if morphed was gibbed, so must original be (where allowed)l } } realthis->Die(source, inflictor, dmgflags); @@ -451,8 +452,8 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) if (deathmatch && player->spreecount >= 5 && cl_showsprees) { SexMessage (GStrings("SPREEKILLSELF"), buff, - player->userinfo.gender, player->userinfo.netname, - player->userinfo.netname); + player->userinfo.GetGender(), player->userinfo.GetName(), + player->userinfo.GetName()); StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); } @@ -499,8 +500,8 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) { if (!AnnounceSpreeLoss (this)) { - SexMessage (GStrings("SPREEOVER"), buff, player->userinfo.gender, - player->userinfo.netname, source->player->userinfo.netname); + SexMessage (GStrings("SPREEOVER"), buff, player->userinfo.GetGender(), + player->userinfo.GetName(), source->player->userinfo.GetName()); StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); } @@ -509,8 +510,8 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) { if (!AnnounceSpree (source)) { - SexMessage (spreemsg, buff, player->userinfo.gender, - player->userinfo.netname, source->player->userinfo.netname); + SexMessage (spreemsg, buff, player->userinfo.GetGender(), + player->userinfo.GetName(), source->player->userinfo.GetName()); StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); } @@ -559,8 +560,8 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) if (!AnnounceMultikill (source)) { - SexMessage (multimsg, buff, player->userinfo.gender, - player->userinfo.netname, source->player->userinfo.netname); + SexMessage (multimsg, buff, player->userinfo.GetGender(), + player->userinfo.GetName(), source->player->userinfo.GetName()); StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, 1.5f, 0.8f, 0, 0, CR_RED, 3.f, 0.5f), MAKE_ID('M','K','I','L')); } @@ -883,7 +884,9 @@ static inline bool MustForcePain(AActor *target, AActor *inflictor) } -void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags) +// Returns the amount of damage actually inflicted upon the target, or -1 if +// the damage was cancelled. +int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags) { unsigned ang; player_t *player = NULL; @@ -896,7 +899,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (target == NULL || !((target->flags & MF_SHOOTABLE) || (target->flags6 & MF6_VULNERABLE))) { // Shouldn't happen - return; + return -1; } // Spectral targets only take damage from spectral projectiles. @@ -904,14 +907,14 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage { if (inflictor == NULL || !(inflictor->flags4 & MF4_SPECTRAL)) { - return; + return -1; } } if (target->health <= 0) { if (inflictor && mod == NAME_Ice) { - return; + return -1; } else if (target->flags & MF_ICECORPSE) // frozen { @@ -919,7 +922,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage target->flags6 |= MF6_SHATTERING; target->velx = target->vely = target->velz = 0; } - return; + return -1; } if ((target->flags2 & MF2_INVULNERABLE) && damage < TELEFRAG_DAMAGE && !(flags & DMG_FORCED)) { // actor is invulnerable @@ -927,7 +930,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage { if (inflictor == NULL || !(inflictor->flags3 & MF3_FOILINVUL)) { - return; + return -1; } } else @@ -935,7 +938,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage // Players are optionally excluded from getting thrust by damage. if (static_cast(target)->PlayerFlags & PPF_NOTHRUSTWHENINVUL) { - return; + return -1; } } @@ -963,7 +966,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (target->flags2 & MF2_DORMANT) { // Invulnerable, and won't wake up - return; + return -1; } player = target->player; if (player && damage > 1 && damage < TELEFRAG_DAMAGE) @@ -979,19 +982,19 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (player != NULL) { if (!deathmatch && inflictor->FriendPlayer > 0) - return; + return -1; } else if (target->flags4 & MF4_SPECTRAL) { if (inflictor->FriendPlayer == 0 && !target->IsHostile(inflictor)) - return; + return -1; } } damage = inflictor->DoSpecialDamage (target, damage, mod); if (damage == -1) { - return; + return -1; } } // Handle active damage modifiers (e.g. PowerDamage) @@ -1005,7 +1008,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage { goto dopain; } - return; + return -1; } } // Handle passive damage modifiers (e.g. PowerProtection) @@ -1019,7 +1022,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage { goto dopain; } - return; + return -1; } } @@ -1036,7 +1039,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage { goto dopain; } - return; + return -1; } } @@ -1044,7 +1047,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage } if (damage == -1) { - return; + return -1; } // Push the target unless the source's weapon's kickback is 0. // (i.e. Gauntlets/Chainsaw) @@ -1066,9 +1069,18 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (kickback) { AActor *origin = (source && (flags & DMG_INFLICTOR_IS_PUFF))? source : inflictor; - - ang = R_PointToAngle2 (origin->x, origin->y, - target->x, target->y); + + // If the origin and target are in exactly the same spot, choose a random direction. + // (Most likely cause is from telefragging somebody during spawning because they + // haven't moved from their spawn spot at all.) + if (origin->x == target->x && origin->y == target->y) + { + ang = pr_kickbackdir.GenRand32(); + } + else + { + ang = R_PointToAngle2 (origin->x, origin->y, target->x, target->y); + } // Calculate this as float to avoid overflows so that the // clamping that had to be done here can be removed. @@ -1128,7 +1140,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage { // Still allow telefragging :-( damage = (int)((float)damage * level.teamdamage); if (damage <= 0) - return; + return damage; } } @@ -1157,7 +1169,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (damage < TELEFRAG_DAMAGE && ((player->mo->flags2 & MF2_INVULNERABLE) || (player->cheats & CF_GODMODE))) { // player is invulnerable, so don't hurt him - return; + return -1; } if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) @@ -1168,12 +1180,12 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (damage <= 0) { // If MF6_FORCEPAIN is set, make the player enter the pain state. - if (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && - (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)) - { - goto dopain; - } - return; + if (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && + (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)) + { + goto dopain; + } + return damage; } } @@ -1232,7 +1244,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage damage = newdam; if (damage <= 0) { - return; + return damage; } } @@ -1291,7 +1303,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage } } target->Die (source, inflictor, flags); - return; + return damage; } woundstate = target->FindState(NAME_Wound, mod); @@ -1302,7 +1314,7 @@ void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage if (target->health <= woundhealth) { target->SetState (woundstate); - return; + return damage; } } @@ -1396,6 +1408,8 @@ dopain: // killough 11/98: Don't attack a friend, unless hit by that friend. if (justhit && (target->target == source || !target->target || !target->IsFriend(target->target))) target->flags |= MF_JUSTHIT; // fight back! + + return damage; } void P_PoisonMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int duration, int period, FName type) @@ -1502,6 +1516,10 @@ bool AActor::OkayToSwitchTarget (AActor *other) // // P_PoisonPlayer - Sets up all data concerning poisoning // +// poisoner is the object directly responsible for poisoning the player, +// such as a missile. source is the actor responsible for creating the +// poisoner. +// //========================================================================== bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poison) @@ -1517,7 +1535,7 @@ bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poi if (poison > 0) { player->poisoncount += poison; - player->poisoner = poisoner; + player->poisoner = source; if (poisoner == NULL) { player->poisontype = player->poisonpaintype = NAME_None; @@ -1547,6 +1565,10 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, AActor *target; AActor *inflictor; + if (player == NULL) + { + return; + } target = player->mo; inflictor = source; if (target->health <= 0) @@ -1558,11 +1580,8 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, { // target is invulnerable return; } - if (player) - { - // Take half damage in trainer mode - damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); - } + // Take half damage in trainer mode + damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); // Handle passive damage modifiers (e.g. PowerProtection) if (target->Inventory != NULL) { diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index fe77542a2..ee6af8e9f 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -326,6 +326,12 @@ FUNC(LS_Floor_RaiseAndCrush) return EV_DoFloor (DFloor::floorRaiseAndCrush, ln, arg0, SPEED(arg1), 0, arg2, 0, CRUSHTYPE(arg3)); } +FUNC(LS_Floor_RaiseAndCrushDoom) +// Floor_RaiseAndCrushDoom (tag, speed, crush, crushmode) +{ + return EV_DoFloor (DFloor::floorRaiseAndCrushDoom, ln, arg0, SPEED(arg1), 0, arg2, 0, CRUSHTYPE(arg3)); +} + FUNC(LS_Floor_RaiseByValueTimes8) // FLoor_RaiseByValueTimes8 (tag, speed, height) { @@ -3232,7 +3238,7 @@ lnSpecFunc LineSpecials[256] = /* 96 */ LS_FloorAndCeiling_RaiseByValue, /* 97 */ LS_Ceiling_LowerAndCrushDist, /* 98 */ LS_Sector_SetTranslucent, - /* 99 */ LS_NOP, + /* 99 */ LS_Floor_RaiseAndCrushDoom, /* 100 */ LS_NOP, // Scroll_Texture_Left /* 101 */ LS_NOP, // Scroll_Texture_Right /* 102 */ LS_NOP, // Scroll_Texture_Up @@ -3291,10 +3297,10 @@ lnSpecFunc LineSpecials[256] = /* 155 */ LS_NOP, /* 156 */ LS_NOP, /* 157 */ LS_NOP, // SetGlobalFogParameter // in GZDoom - /* 158 */ LS_NOP, // FS_Execute in GZDoom + /* 158 */ LS_NOP, // FS_Execute /* 159 */ LS_NOP, // Sector_SetPlaneReflection in GZDoom - /* 160 */ LS_NOP, // Sector_Set3DFloor in GZDoom and Vavoom - /* 161 */ LS_NOP, // Sector_SetContents in GZDoom and Vavoom + /* 160 */ LS_NOP, // Sector_Set3DFloor + /* 161 */ LS_NOP, // Sector_SetContents /* 162 */ LS_NOP, // Reserved Doom64 branch /* 163 */ LS_NOP, // Reserved Doom64 branch /* 164 */ LS_NOP, // Reserved Doom64 branch diff --git a/src/p_local.h b/src/p_local.h index fdf43c2da..b207125c1 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -93,7 +93,7 @@ inline int GetSafeBlockY(long long blocky) // // P_PSPR // -void P_SetupPsprites (player_t* curplayer); +void P_SetupPsprites (player_t* curplayer, bool startweaponup); void P_MovePsprites (player_t* curplayer); void P_DropWeapon (player_t* player); @@ -114,7 +114,10 @@ void P_UnPredictPlayer (); #define ONCEILINGZ FIXED_MAX #define FLOATRANDZ (FIXED_MAX-1) -APlayerPawn *P_SpawnPlayer (struct FPlayerStart *mthing, int playernum, bool tempplayer=false); +#define SPF_TEMPPLAYER 1 // spawning a short-lived dummy player +#define SPF_WEAPONFULLYUP 2 // spawn with weapon already raised + +APlayerPawn *P_SpawnPlayer (struct FPlayerStart *mthing, int playernum, int flags=0); void P_ThrustMobj (AActor *mo, angle_t angle, fixed_t move); int P_FaceMobj (AActor *source, AActor *target, angle_t *delta); @@ -125,7 +128,8 @@ enum EPuffFlags PF_HITTHING = 1, PF_MELEERANGE = 2, PF_TEMPORARY = 4, - PF_HITTHINGBLEED = 8 + PF_HITTHINGBLEED = 8, + PF_NORANDOMZ = 16 }; AActor *P_SpawnPuff (AActor *source, PClassActor *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags = 0); @@ -159,8 +163,7 @@ AActor *P_SpawnSubMissile (AActor *source, PClassActor *type, AActor *target); / // // [RH] P_THINGS // -#define MAX_SPAWNABLES (256) -extern PClassActor *SpawnableThings[MAX_SPAWNABLES]; +extern TMap SpawnableThings; bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, int newtid); bool P_Thing_Projectile (int tid, AActor *source, int type, const char * type_name, angle_t angle, @@ -172,7 +175,7 @@ int P_Thing_Damage (int tid, AActor *whofor0, int amount, FName type); void P_Thing_SetVelocity(AActor *actor, fixed_t vx, fixed_t vy, fixed_t vz, bool add, bool setbob); void P_RemoveThing(AActor * actor); bool P_Thing_Raise(AActor *thing); - +PClassActor *P_GetSpawnableType(int spawnnum); // // P_MAPUTL @@ -455,8 +458,14 @@ enum // P_AimLineAttack flags ALF_NOFRIENDS = 16, }; -AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, PClassActor *pufftype, bool ismelee = false, AActor **victim = NULL); -AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, FName pufftype, bool ismelee = false, AActor **victim = NULL); +enum // P_LineAttack flags +{ + LAF_ISMELEEATTACK = 1, + LAF_NORANDOMPUFFZ = 2 +}; + +AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, PClassActor *pufftype, int flags = 0, AActor **victim = NULL); +AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, FName pufftype, int flags = 0, AActor **victim = NULL); void P_TraceBleed (int damage, fixed_t x, fixed_t y, fixed_t z, AActor *target, angle_t angle, int pitch); void P_TraceBleed (int damage, AActor *target, angle_t angle, int pitch); void P_TraceBleed (int damage, AActor *target, AActor *missile); // missile version @@ -464,7 +473,7 @@ void P_TraceBleed (int damage, AActor *target); // random direction version bool P_HitFloor (AActor *thing); bool P_HitWater (AActor *thing, sector_t *sec, fixed_t splashx = FIXED_MIN, fixed_t splashy = FIXED_MIN, fixed_t splashz=FIXED_MIN, bool checkabove = false, bool alert = true); void P_CheckSplash(AActor *self, fixed_t distance); -void P_RailAttack (AActor *source, int damage, int offset, int color1 = 0, int color2 = 0, double maxdiff = 0, int flags = 0, PClassActor *puff = NULL, angle_t angleoffset = 0, angle_t pitchoffset = 0, fixed_t distance = 8192*FRACUNIT, int duration = 0, double sparsity = 1.0, double drift = 1.0, PClassActor *spawnclass = NULL); // [RH] Shoot a railgun +void P_RailAttack (AActor *source, int damage, int offset_xy, fixed_t offset_z = 0, int color1 = 0, int color2 = 0, double maxdiff = 0, int flags = 0, PClassActor *puff = NULL, angle_t angleoffset = 0, angle_t pitchoffset = 0, fixed_t distance = 8192*FRACUNIT, int duration = 0, double sparsity = 1.0, double drift = 1.0, PClassActor *spawnclass = NULL); // [RH] Shoot a railgun enum // P_RailAttack / A_RailAttack / A_CustomRailgun / P_DrawRailTrail flags { @@ -476,7 +485,7 @@ enum // P_RailAttack / A_RailAttack / A_CustomRailgun / P_DrawRailTrail flags }; -bool P_CheckMissileSpawn (AActor *missile); +bool P_CheckMissileSpawn (AActor *missile, fixed_t maxdist); void P_PlaySpawnSound(AActor *missile, AActor *spawner); // [RH] Position the chasecam @@ -535,7 +544,7 @@ extern FBlockNode** blocklinks; // for thing chains // P_INTER // void P_TouchSpecialThing (AActor *special, AActor *toucher); -void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags=0); +int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags=0); void P_PoisonMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int duration, int period, FName type); bool P_GiveBody (AActor *actor, int num, int max=0); bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poison); diff --git a/src/p_map.cpp b/src/p_map.cpp index 3c457a8b4..2bb990271 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -905,13 +905,17 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) // you can use a scrolling floor to move scenery items underneath a bridge. if ((tm.thing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ)) { // check if a mobj passed over/under another object - if (tm.thing->flags3 & thing->flags3 & MF3_DONTOVERLAP) - { // Some things prefer not to overlap each other, if possible - return unblocking; - } - if ((tm.thing->z >= topz) || (tm.thing->z + tm.thing->height <= thing->z)) + if (!(tm.thing->flags & MF_MISSILE) || + !(tm.thing->flags2 & MF2_RIP) || + (thing->flags5 & MF5_DONTRIP) || + ((tm.thing->flags6 & MF6_NOBOSSRIP) && (thing->flags2 & MF2_BOSS))) { - return true; + if (tm.thing->flags3 & thing->flags3 & MF3_DONTOVERLAP) + { // Some things prefer not to overlap each other, if possible + return unblocking; + } + if ((tm.thing->z >= topz) || (tm.thing->z + tm.thing->height <= thing->z)) + return true; } } @@ -966,12 +970,13 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) thing->vely += tm.thing->vely; if ((thing->velx + thing->vely) > 3*FRACUNIT) { + int newdam; damage = (tm.thing->Mass / 100) + 1; - P_DamageMobj (thing, tm.thing, tm.thing, damage, tm.thing->DamageType); - P_TraceBleed (damage, thing, tm.thing); + newdam = P_DamageMobj (thing, tm.thing, tm.thing, damage, tm.thing->DamageType); + P_TraceBleed (newdam > 0 ? newdam : damage, thing, tm.thing); damage = (thing->Mass / 100) + 1; - P_DamageMobj (tm.thing, thing, thing, damage >> 2, tm.thing->DamageType); - P_TraceBleed (damage, tm.thing, thing); + newdam = P_DamageMobj (tm.thing, thing, thing, damage >> 2, tm.thing->DamageType); + P_TraceBleed (newdam > 0 ? newdam : damage, tm.thing, thing); } return false; } @@ -1070,19 +1075,18 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) // friendliness and hate status. if (tm.thing->target->flags & MF_SHOOTABLE) { - if (!(thing->flags3 & MF3_ISMONSTER)) + // Question: Should monsters be allowed to shoot barrels in this mode? + // The old code does not. + if (thing->flags3 & MF3_ISMONSTER) { - return false; // Question: Should monsters be allowed to shoot barrels in this mode? - // The old code does not. - } - - // Monsters that are clearly hostile can always hurt each other - if (!thing->IsHostile (tm.thing->target)) - { - // The same if the shooter hates the target - if (thing->tid == 0 || tm.thing->target->TIDtoHate != thing->tid) + // Monsters that are clearly hostile can always hurt each other + if (!thing->IsHostile (tm.thing->target)) { - return false; + // The same if the shooter hates the target + if (thing->tid == 0 || tm.thing->target->TIDtoHate != thing->tid) + { + return false; + } } } } @@ -1149,10 +1153,10 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) } damage = tm.thing->GetMissileDamage (3, 2); - P_DamageMobj (thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType); + int newdam = P_DamageMobj (thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType); if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT)) { - P_TraceBleed (damage, thing, tm.thing); + P_TraceBleed (newdam > 0 ? newdam : damage, thing, tm.thing); } if (thing->flags2 & MF2_PUSHABLE && !(tm.thing->flags2 & MF2_CANNOTPUSH)) @@ -1180,7 +1184,7 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) damage = tm.thing->GetMissileDamage ((tm.thing->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1); if ((damage > 0) || (tm.thing->flags6 & MF6_FORCEPAIN)) { - P_DamageMobj (thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType); + int newdam = P_DamageMobj (thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType); if (damage > 0) { if ((tm.thing->flags5 & MF5_BLOODSPLATTER) && @@ -1194,7 +1198,7 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) } if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT)) { - P_TraceBleed (damage, thing, tm.thing); + P_TraceBleed (newdam > 0 ? newdam : damage, thing, tm.thing); } } } @@ -1830,7 +1834,15 @@ bool P_TryMove (AActor *thing, fixed_t x, fixed_t y, // If moving down, cancel vertical component of the velocity if (thing->velz < 0) { - thing->velz = 0; + // If it's a bouncer, let it bounce off its new floor, too. + if (thing->BounceFlags & BOUNCE_Floors) + { + thing->FloorBounceMissile (tm.floorsector->floorplane); + } + else + { + thing->velz = 0; + } } } } @@ -2511,10 +2523,17 @@ void FSlide::SlideMove (AActor *mo, fixed_t tryx, fixed_t tryy, int numsteps) { newx = FixedMul (tryx, bestslidefrac); newy = FixedMul (tryy, bestslidefrac); - + + // [BL] We need to abandon this function if we end up going through a teleporter + const fixed_t startvelx = mo->velx; + const fixed_t startvely = mo->vely; + // killough 3/15/98: Allow objects to drop off ledges if (!P_TryMove (mo, mo->x+newx, mo->y+newy, true)) goto stairstep; + + if (mo->velx != startvelx || mo->vely != startvely) + return; } // Now continue along the wall. @@ -2858,6 +2877,14 @@ bool FSlide::BounceWall (AActor *mo) } mo->velx = FixedMul(movelen, finecosine[deltaangle]); mo->vely = FixedMul(movelen, finesine[deltaangle]); + if (mo->BounceFlags & BOUNCE_UseBounceState) + { + FState *bouncestate = mo->FindState(NAME_Bounce, NAME_Wall); + if (bouncestate != NULL) + { + mo->SetState(bouncestate); + } + } return true; } @@ -2877,7 +2904,7 @@ extern FRandom pr_bounce; bool P_BounceActor (AActor *mo, AActor *BlockingMobj, bool ontop) { if (mo && BlockingMobj && ((mo->BounceFlags & BOUNCE_AllActors) - || ((mo->flags & MF_MISSILE) && (BlockingMobj->flags2 & MF2_REFLECTIVE)) + || ((mo->flags & MF_MISSILE) && (!(mo->flags2 & MF2_RIP) || (BlockingMobj->flags5 & MF5_DONTRIP) || ((mo->flags6 & MF6_NOBOSSRIP) && (BlockingMobj->flags2 & MF2_BOSS))) && (BlockingMobj->flags2 & MF2_REFLECTIVE)) || ((BlockingMobj->player == NULL) && (!(BlockingMobj->flags3 & MF3_ISMONSTER))) )) { @@ -2895,6 +2922,22 @@ bool P_BounceActor (AActor *mo, AActor *BlockingMobj, bool ontop) mo->velx = FixedMul (speed, finecosine[angle]); mo->vely = FixedMul (speed, finesine[angle]); mo->PlayBounceSound(true); + if (mo->BounceFlags & BOUNCE_UseBounceState) + { + FName names[] = { NAME_Bounce, NAME_Actor, NAME_Creature }; + FState *bouncestate; + int count = 2; + + if ((BlockingMobj->flags & MF_SHOOTABLE) && !(BlockingMobj->flags & MF_NOBLOOD)) + { + count = 3; + } + bouncestate = mo->FindState(count, names); + if (bouncestate != NULL) + { + mo->SetState(bouncestate); + } + } } else { @@ -3349,7 +3392,7 @@ fixed_t P_AimLineAttack (AActor *t1, angle_t angle, fixed_t distance, AActor **p // vrange of 0 degrees, because then toppitch and bottompitch will // be equal, and PTR_AimTraverse will never find anything to shoot at // if it crosses a line. - vrange = clamp (t1->player->userinfo.aimdist, ANGLE_1/2, ANGLE_1*35); + vrange = clamp (t1->player->userinfo.GetAimDist(), ANGLE_1/2, ANGLE_1*35); } } } @@ -3412,38 +3455,36 @@ fixed_t P_AimLineAttack (AActor *t1, angle_t angle, fixed_t distance, AActor **p // //========================================================================== -static bool CheckForGhost (FTraceResults &res) +static ETraceStatus CheckForGhost (FTraceResults &res, void *userdata) { if (res.HitType != TRACE_HitActor) { - return false; + return TRACE_Stop; } // check for physical attacks on a ghost if (res.Actor->flags3 & MF3_GHOST || res.Actor->flags4 & MF4_SPECTRAL) { - res.HitType = TRACE_HitNone; - return true; + return TRACE_Skip; } - return false; + return TRACE_Stop; } -static bool CheckForSpectral (FTraceResults &res) +static ETraceStatus CheckForSpectral (FTraceResults &res, void *userdata) { if (res.HitType != TRACE_HitActor) { - return false; + return TRACE_Stop; } // check for physical attacks on spectrals if (res.Actor->flags4 & MF4_SPECTRAL) { - res.HitType = TRACE_HitNone; - return true; + return TRACE_Skip; } - return false; + return TRACE_Stop; } //========================================================================== @@ -3455,7 +3496,7 @@ static bool CheckForSpectral (FTraceResults &res) //========================================================================== AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, - int pitch, int damage, FName damageType, PClassActor *pufftype, bool ismeleeattack, AActor **victim) + int pitch, int damage, FName damageType, PClassActor *pufftype, int flags, AActor **victim) { fixed_t vx, vy, vz, shootz; FTraceResults trace; @@ -3464,8 +3505,10 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, bool hitGhosts; bool killPuff = false; AActor *puff = NULL; - int flags = ismeleeattack? PF_MELEERANGE : 0; int pflag = 0; + int puffFlags = (flags & LAF_ISMELEEATTACK)? PF_MELEERANGE : 0; + if (flags & LAF_NORANDOMPUFFZ) + puffFlags |= PF_NORANDOMZ; if (victim != NULL) { @@ -3503,9 +3546,10 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, (t1->player->ReadyWeapon->flags2 & MF2_THRUGHOST)) || (puffDefaults && (puffDefaults->flags2 & MF2_THRUGHOST)); - // if the puff uses a non-standard damage type this will override default, hitscan and melee damage type. + // if the puff uses a non-standard damage type, this will override default, hitscan and melee damage type. // All other explicitly passed damage types (currenty only MDK) will be preserved. - if ((damageType == NAME_None || damageType == NAME_Melee || damageType == NAME_Hitscan) && puffDefaults->DamageType != NAME_None) + if ((damageType == NAME_None || damageType == NAME_Melee || damageType == NAME_Hitscan) && + puffDefaults != NULL && puffDefaults->DamageType != NAME_None) { damageType = puffDefaults->DamageType; } @@ -3525,9 +3569,9 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, { // Play miss sound S_Sound (t1, CHAN_WEAPON, puffDefaults->ActiveSound, 1, ATTN_NORM); } - if (puffDefaults->flags3 & MF3_ALWAYSPUFF) + if (puffDefaults != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF) { // Spawn the puff anyway - puff = P_SpawnPuff (t1, pufftype, trace.X, trace.Y, trace.Z, angle - ANG180, 2, flags); + puff = P_SpawnPuff (t1, pufftype, trace.X, trace.Y, trace.Z, angle - ANG180, 2, puffFlags); } else { @@ -3546,7 +3590,7 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, fixed_t closer = trace.Distance - 4*FRACUNIT; puff = P_SpawnPuff (t1, pufftype, t1->x + FixedMul (vx, closer), t1->y + FixedMul (vy, closer), - shootz + FixedMul (vz, closer), angle - ANG90, 0, flags); + shootz + FixedMul (vz, closer), angle - ANG90, 0, puffFlags); } // [RH] Spawn a decal @@ -3602,23 +3646,52 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, hitz = shootz + FixedMul (vz, dist); // Spawn bullet puffs or blood spots, depending on target type. - if ((puffDefaults->flags3 & MF3_PUFFONACTORS) || + if ((puffDefaults != NULL && puffDefaults->flags3 & MF3_PUFFONACTORS) || (trace.Actor->flags & MF_NOBLOOD) || (trace.Actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))) { if (!(trace.Actor->flags & MF_NOBLOOD)) - flags |= PF_HITTHINGBLEED; + puffFlags |= PF_HITTHINGBLEED; // We must pass the unreplaced puff type here - puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, flags|PF_HITTHING); + puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags|PF_HITTHING); } - if (!(puffDefaults->flags3&MF3_BLOODLESSIMPACT)) + + // Allow puffs to inflict poison damage, so that hitscans can poison, too. + if (puffDefaults != NULL && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) + { + P_PoisonMobj(trace.Actor, puff ? puff : t1, t1, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType); + } + + // [GZ] If MF6_FORCEPAIN is set, we need to call P_DamageMobj even if damage is 0! + // Note: The puff may not yet be spawned here so we must check the class defaults, not the actor. + int newdam = damage; + if (damage || (puffDefaults != NULL && puffDefaults->flags6 & MF6_FORCEPAIN)) + { + int dmgflags = DMG_INFLICTOR_IS_PUFF | pflag; + // Allow MF5_PIERCEARMOR on a weapon as well. + if (t1->player != NULL && (dmgflags & DMG_PLAYERATTACK) && t1->player->ReadyWeapon != NULL && + t1->player->ReadyWeapon->flags5 & MF5_PIERCEARMOR) + { + dmgflags |= DMG_NO_ARMOR; + } + + if (puff == NULL) + { + // Since the puff is the damage inflictor we need it here + // regardless of whether it is displayed or not. + puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags|PF_HITTHING|PF_TEMPORARY); + killPuff = true; + } + newdam = P_DamageMobj (trace.Actor, puff ? puff : t1, t1, damage, damageType, dmgflags); + } + if (!(puffDefaults != NULL && puffDefaults->flags3&MF3_BLOODLESSIMPACT)) { if (!bloodsplatter && !axeBlood && !(trace.Actor->flags & MF_NOBLOOD) && !(trace.Actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))) { - P_SpawnBlood (hitx, hity, hitz, angle - ANG180, damage, trace.Actor); + P_SpawnBlood (hitx, hity, hitz, angle - ANG180, newdam > 0 ? newdam : damage, trace.Actor); } if (damage) @@ -3639,38 +3712,10 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, } } // [RH] Stick blood to walls - P_TraceBleed (damage, trace.X, trace.Y, trace.Z, + P_TraceBleed (newdam > 0 ? newdam : damage, trace.X, trace.Y, trace.Z, trace.Actor, srcangle, srcpitch); } } - - // Allow puffs to inflict poison damage, so that hitscans can poison, too. - if (puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) - { - P_PoisonMobj(trace.Actor, puff ? puff : t1, t1, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType); - } - - // [GZ] If MF6_FORCEPAIN is set, we need to call P_DamageMobj even if damage is 0! - // Note: The puff may not yet be spawned here so we must check the class defaults, not the actor. - if (damage || (puffDefaults->flags6 & MF6_FORCEPAIN)) - { - int dmgflags = DMG_INFLICTOR_IS_PUFF | pflag; - // Allow MF5_PIERCEARMOR on a weapon as well. - if (t1->player != NULL && (dmgflags & DMG_PLAYERATTACK) && t1->player->ReadyWeapon != NULL && - t1->player->ReadyWeapon->flags5 & MF5_PIERCEARMOR) - { - dmgflags |= DMG_NO_ARMOR; - } - - if (puff == NULL) - { - // Since the puff is the damage inflictor we need it here - // regardless of whether it is displayed or not. - puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, flags|PF_HITTHING|PF_TEMPORARY); - killPuff = true; - } - P_DamageMobj (trace.Actor, puff ? puff : t1, t1, damage, damageType, dmgflags); - } if (victim != NULL) { *victim = trace.Actor; @@ -3681,7 +3726,7 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, if (puff == NULL) { // Spawn puff just to get a mass for the splash - puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, flags|PF_HITTHING|PF_TEMPORARY); + puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags|PF_HITTHING|PF_TEMPORARY); killPuff = true; } SpawnDeepSplash (t1, trace, puff, vx, vy, vz, shootz, trace.Crossed3DWater != NULL); @@ -3696,7 +3741,7 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, } AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, - int pitch, int damage, FName damageType, FName pufftype, bool ismeleeattack, AActor **victim) + int pitch, int damage, FName damageType, FName pufftype, int flags, AActor **victim) { PClassActor *type = PClass::FindActor(pufftype); if (victim != NULL) @@ -3709,7 +3754,7 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, } else { - return P_LineAttack(t1, angle, distance, pitch, damage, damageType, type, ismeleeattack, victim); + return P_LineAttack(t1, angle, distance, pitch, damage, damageType, type, flags, victim); } return NULL; } @@ -3870,28 +3915,34 @@ struct SRailHit AActor *HitActor; fixed_t Distance; }; -static TArray RailHits (16); - -static bool ProcessRailHit (FTraceResults &res) +struct RailData { + TArray RailHits; + bool StopAtOne; + bool StopAtInvul; +}; + +static ETraceStatus ProcessRailHit (FTraceResults &res, void *userdata) +{ + RailData *data = (RailData *)userdata; if (res.HitType != TRACE_HitActor) { - return false; + return TRACE_Stop; } // Invulnerable things completely block the shot - if (res.Actor->flags2 & MF2_INVULNERABLE) + if (data->StopAtInvul && res.Actor->flags2 & MF2_INVULNERABLE) { - return false; + return TRACE_Stop; } // Save this thing for damaging later, and continue the trace SRailHit newhit; newhit.HitActor = res.Actor; newhit.Distance = res.Distance - 10*FRACUNIT; // put blood in front - RailHits.Push (newhit); + data->RailHits.Push (newhit); - return true; + return data->StopAtOne ? TRACE_Stop : TRACE_Continue; } //========================================================================== @@ -3899,36 +3950,7 @@ static bool ProcessRailHit (FTraceResults &res) // // //========================================================================== - -static bool ProcessNoPierceRailHit (FTraceResults &res) -{ - if (res.HitType != TRACE_HitActor) - { - return false; - } - - // Invulnerable things completely block the shot - if (res.Actor->flags2 & MF2_INVULNERABLE) - { - return false; - } - - // Only process the first hit - SRailHit newhit; - newhit.HitActor = res.Actor; - newhit.Distance = res.Distance - 10*FRACUNIT; // put blood in front - RailHits.Push (newhit); - - return false; -} - -//========================================================================== -// -// -// -//========================================================================== - -void P_RailAttack (AActor *source, int damage, int offset, int color1, int color2, double maxdiff, int railflags, PClassActor *puffclass, angle_t angleoffset, angle_t pitchoffset, fixed_t distance, int duration, double sparsity, double drift, PClassActor *spawnclass) +void P_RailAttack (AActor *source, int damage, int offset_xy, fixed_t offset_z, int color1, int color2, double maxdiff, int railflags, PClassActor *puffclass, angle_t angleoffset, angle_t pitchoffset, fixed_t distance, int duration, double sparsity, double drift, PClassActor *spawnclass) { fixed_t vx, vy, vz; angle_t angle, pitch; @@ -3952,7 +3974,7 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color x1 = source->x; y1 = source->y; - shootz = source->z - source->floorclip + (source->height >> 1); + shootz = source->z - source->floorclip + (source->height >> 1) + offset_z; if (!(railflags & RAF_CENTERZ)) { @@ -3967,25 +3989,27 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color } angle = ((source->angle + angleoffset) - ANG90) >> ANGLETOFINESHIFT; - x1 += offset*finecosine[angle]; - y1 += offset*finesine[angle]; + x1 += offset_xy * finecosine[angle]; + y1 += offset_xy * finesine[angle]; - RailHits.Clear (); + RailData rail_data; + + rail_data.StopAtOne = !!(railflags & RAF_NOPIERCE); start.X = FIXED2FLOAT(x1); start.Y = FIXED2FLOAT(y1); start.Z = FIXED2FLOAT(shootz); int flags; - AActor *puffDefaults = puffclass == NULL ? - NULL : GetDefaultByType (puffclass->GetReplacement()); + assert(puffclass != NULL); // Because we set it to a default above + AActor *puffDefaults = GetDefaultByType (puffclass->GetReplacement()); - if (puffDefaults != NULL && puffDefaults->flags6 & MF6_NOTRIGGER) flags = 0; - else flags = TRACE_PCross|TRACE_Impact; + flags = (puffDefaults->flags6 & MF6_NOTRIGGER) ? 0 : TRACE_PCross|TRACE_Impact; + rail_data.StopAtInvul = (puffDefaults->flags3 & MF3_FOILINVUL) ? false : true; Trace (x1, y1, shootz, source->Sector, vx, vy, vz, distance, MF_SHOOTABLE, ML_BLOCKEVERYTHING, source, trace, - flags, (railflags & RAF_NOPIERCE) ? ProcessNoPierceRailHit : ProcessRailHit); + flags, ProcessRailHit, &rail_data); // Hurt anything the trace hit unsigned int i; @@ -3996,18 +4020,22 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color if (puffclass != NULL) thepuff = Spawn (puffclass, source->x, source->y, source->z, ALLOW_REPLACE); - for (i = 0; i < RailHits.Size (); i++) + for (i = 0; i < rail_data.RailHits.Size (); i++) { fixed_t x, y, z; bool spawnpuff; + bool bleed = false; + int puffflags = PF_HITTHING; + AActor *hitactor = rail_data.RailHits[i].HitActor; + fixed_t hitdist = rail_data.RailHits[i].Distance; - x = x1 + FixedMul (RailHits[i].Distance, vx); - y = y1 + FixedMul (RailHits[i].Distance, vy); - z = shootz + FixedMul (RailHits[i].Distance, vz); + x = x1 + FixedMul(hitdist, vx); + y = y1 + FixedMul(hitdist, vy); + z = shootz + FixedMul(hitdist, vz); - if ((RailHits[i].HitActor->flags & MF_NOBLOOD) || - (RailHits[i].HitActor->flags2 & (MF2_DORMANT|MF2_INVULNERABLE))) + if ((hitactor->flags & MF_NOBLOOD) || + (hitactor->flags2 & (MF2_DORMANT|MF2_INVULNERABLE))) { spawnpuff = (puffclass != NULL); } @@ -4015,18 +4043,25 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color { spawnpuff = (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF); puffflags |= PF_HITTHINGBLEED; // [XA] Allow for puffs to jump to XDeath state. - if(!(puffDefaults->flags3 & MF3_BLOODLESSIMPACT)) + if (!(puffDefaults->flags3 & MF3_BLOODLESSIMPACT)) { - P_SpawnBlood (x, y, z, (source->angle + angleoffset) - ANG180, damage, RailHits[i].HitActor); - P_TraceBleed (damage, x, y, z, RailHits[i].HitActor, source->angle, pitch); + bleed = true; } } if (spawnpuff) - P_SpawnPuff (source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags); + { + P_SpawnPuff(source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags); + } if (puffDefaults && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) - P_PoisonMobj(RailHits[i].HitActor, thepuff ? thepuff : source, source, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType); - - P_DamageMobj (RailHits[i].HitActor, thepuff? thepuff:source, source, damage, damagetype, DMG_INFLICTOR_IS_PUFF); + { + P_PoisonMobj(hitactor, thepuff ? thepuff : source, source, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType); + } + int newdam = P_DamageMobj(hitactor, thepuff? thepuff:source, source, damage, damagetype, DMG_INFLICTOR_IS_PUFF); + if (bleed) + { + P_SpawnBlood(x, y, z, (source->angle + angleoffset) - ANG180, newdam > 0 ? newdam : damage, hitactor); + P_TraceBleed(newdam > 0 ? newdam : damage, x, y, z, hitactor, source->angle, pitch); + } } // Spawn a decal or puff at the point where the trace ended. @@ -4039,24 +4074,21 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color } } - if (trace.HitType == TRACE_HitFloor && - trace.CrossedWater == NULL && - trace.Sector->heightsec == NULL) + if(thepuff != NULL) { - if (thepuff != NULL) + if (trace.HitType == TRACE_HitFloor && + trace.CrossedWater == NULL && + trace.Sector->heightsec == NULL) { thepuff->SetOrigin(trace.X, trace.Y, trace.Z); P_HitWater (thepuff, trace.Sector); } - } - if (trace.Crossed3DWater || trace.CrossedWater) - { - if (thepuff != NULL) + if (trace.Crossed3DWater || trace.CrossedWater) { SpawnDeepSplash (source, trace, thepuff, vx, vy, vz, shootz, trace.Crossed3DWater != NULL); } + thepuff->Destroy (); } - thepuff->Destroy (); // Draw the slug's trail. end.X = FIXED2FLOAT(trace.X); @@ -4462,7 +4494,7 @@ void P_RadiusAttack (AActor *bombspot, AActor *bombsource, int bombdamage, int b AActor *thing; if (flags & RADF_SOURCEISSPOT) - { // The source is actually the same as the spot, even if that wasn't what we receized. + { // The source is actually the same as the spot, even if that wasn't what we received. bombsource = bombspot; } @@ -4551,23 +4583,25 @@ void P_RadiusAttack (AActor *bombspot, AActor *bombsource, int bombdamage, int b } points *= thing->GetClass()->RDFactor / (float)FRACUNIT; - if (points > 0.f && P_CheckSight (thing, bombspot, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) + // points and bombdamage should be the same sign + if ((points * bombdamage) > 0 && P_CheckSight (thing, bombspot, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) { // OK to damage; target is in direct path double velz; double thrust; - int damage = (int)points; + int damage = abs((int)points); + int newdam = damage; if (!(flags & RADF_NODAMAGE)) - P_DamageMobj (thing, bombspot, bombsource, damage, bombmod); + newdam = P_DamageMobj (thing, bombspot, bombsource, damage, bombmod); else if (thing->player == NULL && !(flags & RADF_NOIMPACTDAMAGE)) thing->flags2 |= MF2_BLASTED; if (!(thing->flags & MF_ICECORPSE)) { if (!(flags & RADF_NODAMAGE) && !(bombspot->flags3 & MF3_BLOODLESSIMPACT)) - P_TraceBleed (damage, thing, bombspot); + P_TraceBleed (newdam > 0 ? newdam : damage, thing, bombspot); - if (!(flags & RADF_NODAMAGE) || !(bombspot->flags2 & MF2_NODMGTHRUST)) + if ((flags & RADF_NODAMAGE) || !(bombspot->flags2 & MF2_NODMGTHRUST)) { if (bombsource == NULL || !(bombsource->flags2 & MF2_NODMGTHRUST)) { @@ -4621,8 +4655,8 @@ void P_RadiusAttack (AActor *bombspot, AActor *bombsource, int bombdamage, int b damage = Scale(damage, thing->GetClass()->RDFactor, FRACUNIT); if (damage > 0) { - P_DamageMobj (thing, bombspot, bombsource, damage, bombmod); - P_TraceBleed (damage, thing, bombspot); + int newdam = P_DamageMobj (thing, bombspot, bombsource, damage, bombmod); + P_TraceBleed (newdam > 0 ? newdam : damage, thing, bombspot); } } } @@ -4824,7 +4858,7 @@ void P_DoCrunch (AActor *thing, FChangePosition *cpos) if ((cpos->crushchange > 0) && !(level.maptime & 3)) { - P_DamageMobj (thing, NULL, NULL, cpos->crushchange, NAME_Crush); + int newdam = P_DamageMobj (thing, NULL, NULL, cpos->crushchange, NAME_Crush); // spray blood in a random direction if (!(thing->flags2&(MF2_INVULNERABLE|MF2_DORMANT))) @@ -4834,7 +4868,7 @@ void P_DoCrunch (AActor *thing, FChangePosition *cpos) PalEntry bloodcolor = thing->GetBloodColor(); PClassActor *bloodcls = thing->GetBloodType(); - P_TraceBleed (cpos->crushchange, thing); + P_TraceBleed (newdam > 0 ? newdam : cpos->crushchange, thing); if (cl_bloodtype <= 1 && bloodcls != NULL) { AActor *mo; diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 981e6a4b2..f48cb145e 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -105,7 +105,6 @@ static FRandom pr_chunk ("Chunk"); static FRandom pr_checkmissilespawn ("CheckMissileSpawn"); static FRandom pr_spawnmissile ("SpawnMissile"); static FRandom pr_missiledamage ("MissileDamage"); - FRandom pr_slam ("SkullSlam"); static FRandom pr_multiclasschoice ("MultiClassChoice"); static FRandom pr_rockettrail("RocketTrail"); static FRandom pr_uniquetid("UniqueTID"); @@ -412,9 +411,7 @@ void AActor::Serialize (FArchive &arc) !(flags4 & MF4_NOSKIN) && state->sprite == GetDefaultByType (player->cls)->SpawnState->sprite) { // Give player back the skin - sprite = skins[player->userinfo.skin].sprite; - scaleX = skins[player->userinfo.skin].ScaleX; - scaleY = skins[player->userinfo.skin].ScaleY; + sprite = skins[player->userinfo.GetSkin()].sprite; } if (Speed == 0) { @@ -549,7 +546,7 @@ bool AActor::SetState (FState *newstate, bool nofunction) // spawning. if (player != NULL && skins != NULL) { - sprite = skins[player->userinfo.skin].sprite; + sprite = skins[player->userinfo.GetSkin()].sprite; } else if (newsprite != prevsprite) { @@ -917,7 +914,7 @@ void AActor::CopyFriendliness (AActor *other, bool changeTarget, bool resetHealt LastLookPlayerNumber = other->LastLookPlayerNumber; flags = (flags & ~MF_FRIENDLY) | (other->flags & MF_FRIENDLY); flags3 = (flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (other->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); - flags4 = (flags4 & ~MF4_NOHATEPLAYERS) | (other->flags4 & MF4_NOHATEPLAYERS); + flags4 = (flags4 & ~(MF4_NOHATEPLAYERS | MF4_BOSSSPAWNED)) | (other->flags4 & (MF4_NOHATEPLAYERS | MF4_BOSSSPAWNED)); FriendPlayer = other->FriendPlayer; DesignatedTeam = other->DesignatedTeam; if (changeTarget && other->target != NULL && !(other->target->flags3 & MF3_NOTARGET)) @@ -1008,7 +1005,7 @@ bool AActor::IsVisibleToPlayer() const return true; if (VisibleToTeam != 0 && teamplay && - VisibleToTeam-1 != players[consoleplayer].userinfo.team ) + (signed)(VisibleToTeam-1) != players[consoleplayer].userinfo.GetTeam() ) return false; const player_t* pPlayer = players[consoleplayer].camera->player; @@ -1095,7 +1092,7 @@ bool AActor::Grind(bool items) // ZDoom behavior differs from standard as crushed corpses cannot be raised. // The reason for the change was originally because of a problem with players, // see rh_log entry for February 21, 1999. Don't know if it is still relevant. - if (state == NULL // Only use the default crushed state if: + if (state == NULL // Only use the default crushed state if: && !(flags & MF_NOBLOOD) // 1. the monster bleeeds, && (i_compatflags & COMPATF_CORPSEGIBS) // 2. the compat setting is on, && player == NULL) // 3. and the thing isn't a player. @@ -1159,11 +1156,12 @@ bool AActor::Grind(bool items) gib->alpha = alpha; gib->height = 0; gib->radius = 0; + + PalEntry bloodcolor = GetBloodColor(); + if (bloodcolor != 0) + gib->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); } S_Sound (this, CHAN_BODY, "misc/fallingsplat", 1, ATTN_IDLE); - - PalEntry bloodcolor = GetBloodColor(); - if (bloodcolor!=0) gib->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); } if (flags & MF_ICECORPSE) { @@ -1183,13 +1181,6 @@ bool AActor::Grind(bool items) return false; // keep checking } - // crunch dropped items - if (flags & MF_DROPPED) - { - if (items) Destroy (); // Only destroy dropped items if wanted - return false; // keep checking - } - // killough 11/98: kill touchy things immediately if (flags6 & MF6_TOUCHY && (flags6 & MF6_ARMED || IsSentient())) { @@ -1487,6 +1478,22 @@ bool AActor::FloorBounceMissile (secplane_t &plane) } PlayBounceSound(true); + + // Set bounce state + if (BounceFlags & BOUNCE_UseBounceState) + { + FName names[2]; + FState *bouncestate; + + names[0] = NAME_Bounce; + names[1] = plane.c < 0 ? NAME_Ceiling : NAME_Floor; + bouncestate = FindState(2, names); + if (bouncestate != NULL) + { + SetState(bouncestate); + } + } + if (BounceFlags & BOUNCE_MBF) // Bring it to rest below a certain speed { if (abs(velz) < (fixed_t)(Mass * GetGravity() / 64)) @@ -2916,8 +2923,8 @@ bool AActor::Slam (AActor *thing) if (!(flags2 & MF2_DORMANT)) { int dam = GetMissileDamage (7, 1); - P_DamageMobj (thing, this, this, dam, NAME_Melee); - P_TraceBleed (dam, thing, this); + int newdam = P_DamageMobj (thing, this, this, dam, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : dam, thing, this); // The charging monster may have died by the target's actions here. if (health > 0) { @@ -3164,37 +3171,36 @@ void AActor::Tick () } - if (cl_rockettrails & 2) + if (effects & FX_ROCKET) { - if (effects & FX_ROCKET) + if (++smokecounter == 4) { - if (++smokecounter==4) + // add some smoke behind the rocket + smokecounter = 0; + AActor *th = Spawn("RocketSmokeTrail", x-velx, y-vely, z-velz, ALLOW_REPLACE); + if (th) { - // add some smoke behind the rocket - smokecounter = 0; - AActor * th = Spawn("RocketSmokeTrail", x-velx, y-vely, z-velz, ALLOW_REPLACE); - if (th) - { - th->tics -= pr_rockettrail()&3; - if (th->tics < 1) th->tics = 1; - } + th->tics -= pr_rockettrail()&3; + if (th->tics < 1) th->tics = 1; + if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE; } } - else if (effects & FX_GRENADE) + } + else if (effects & FX_GRENADE) + { + if (++smokecounter == 8) { - if (++smokecounter==8) + smokecounter = 0; + angle_t moveangle = R_PointToAngle2(0,0,velx,vely); + AActor * th = Spawn("GrenadeSmokeTrail", + x - FixedMul (finecosine[(moveangle)>>ANGLETOFINESHIFT], radius*2) + (pr_rockettrail()<<10), + y - FixedMul (finesine[(moveangle)>>ANGLETOFINESHIFT], radius*2) + (pr_rockettrail()<<10), + z - (height>>3) * (velz>>16) + (2*height)/3, ALLOW_REPLACE); + if (th) { - smokecounter = 0; - angle_t moveangle = R_PointToAngle2(0,0,velx,vely); - AActor * th = Spawn("GrenadeSmokeTrail", - x - FixedMul (finecosine[(moveangle)>>ANGLETOFINESHIFT], radius*2) + (pr_rockettrail()<<10), - y - FixedMul (finesine[(moveangle)>>ANGLETOFINESHIFT], radius*2) + (pr_rockettrail()<<10), - z - (height>>3) * (velz>>16) + (2*height)/3, ALLOW_REPLACE); - if (th) - { - th->tics -= pr_rockettrail()&3; - if (th->tics < 1) th->tics = 1; - } + th->tics -= pr_rockettrail()&3; + if (th->tics < 1) th->tics = 1; + if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE; } } } @@ -3614,23 +3620,35 @@ void AActor::Tick () } } + assert (state != NULL); + if (state == NULL) + { + Destroy(); + return; + } + if (ObjectFlags & OF_JustSpawned && state->GetNoDelay()) + { + // For immediately spawned objects with the NoDelay flag set for their + // Spawn state, explicitly set the current state so that it calls its + // action and chains 0-tic states. + int starttics = tics; + if (!SetState(state)) + return; // freed itself + // If the initial state had a duration of 0 tics, let the next state run + // normally. Otherwise, increment tics by 1 so that we don't double up ticks. + if (starttics > 0 && tics >= 0) + { + tics++; + } + } // cycle through states, calling action functions at transitions if (tics != -1) { - tics--; - - // you can cycle through multiple states in a tic - // [RH] Use <= 0 instead of == 0 so that spawnstates + // [RH] Use tics <= 0 instead of == 0 so that spawnstates // of 0 tics work as expected. - if (tics <= 0) + if (--tics <= 0) { - assert (state != NULL); - if (state == NULL) - { - Destroy(); - return; - } - if (!SetState (state->GetNextState())) + if (!SetState(state->GetNextState())) return; // freed itself } } @@ -3964,7 +3982,7 @@ AActor *AActor::StaticSpawn (PClassActor *type, fixed_t ix, fixed_t iy, fixed_t actor->SpawnPoint[2] = (actor->z - actor->floorz); } - actor->FloatBobPhase = rng(); // Don't make everything bob in sync + if (actor->FloatBobPhase == (BYTE)-1) actor->FloatBobPhase = rng(); // Don't make everything bob in sync (unless deliberately told to do) if (actor->flags2 & MF2_FLOORCLIP) { actor->AdjustFloorClip (); @@ -4248,7 +4266,7 @@ EXTERN_CVAR (Bool, chasedemo) extern bool demonew; -APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer) +APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) { player_t *p; APlayerPawn *mobj, *oldactor; @@ -4275,7 +4293,7 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer } else { - type = p->userinfo.PlayerClass; + type = p->userinfo.GetPlayerClassNum(); if (type < 0) { type = pr_multiclasschoice() % PlayerClasses.Size (); @@ -4349,7 +4367,7 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer { G_PlayerReborn (playernum); } - else if (oldactor != NULL && oldactor->player == p && !tempplayer) + else if (oldactor != NULL && oldactor->player == p && !(flags & SPF_TEMPPLAYER)) { // Move the voodoo doll's inventory to the new player. mobj->ObtainInventory (oldactor); @@ -4357,8 +4375,7 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer } // [GRB] Reset skin - p->userinfo.skin = R_FindSkin (skins[p->userinfo.skin].name, p->CurrentPlayerClass); - + p->userinfo.SkinNumChanged(R_FindSkin (skins[p->userinfo.GetSkin()].name, p->CurrentPlayerClass)); if (!(mobj->flags2 & MF2_DONTTRANSLATE)) { @@ -4379,9 +4396,7 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer // [RH] Set player sprite based on skin if (!(mobj->flags4 & MF4_NOSKIN)) { - mobj->sprite = skins[p->userinfo.skin].sprite; - mobj->scaleX = skins[p->userinfo.skin].ScaleX; - mobj->scaleY = skins[p->userinfo.skin].ScaleY; + mobj->sprite = skins[p->userinfo.GetSkin()].sprite; } p->DesiredFOV = p->FOV = 90.f; @@ -4424,11 +4439,9 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer p->cheats = CF_CHASECAM; // setup gun psprite - if (!tempplayer) - { - // This can also start a script so don't do it for - // the dummy player. - P_SetupPsprites (p); + if (!(flags & SPF_TEMPPLAYER)) + { // This can also start a script so don't do it for the dummy player. + P_SetupPsprites (p, !!(flags & SPF_WEAPONFULLYUP)); } if (deathmatch) @@ -4474,7 +4487,7 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, bool tempplayer } // [BC] Do script stuff - if (!tempplayer) + if (!(flags & SPF_TEMPPLAYER)) { if (state == PST_ENTER || (state == PST_LIVE && !savegamerestore)) { @@ -4660,7 +4673,7 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) AllPlayerStarts.Push(start); if (!deathmatch && !(level.flags2 & LEVEL2_RANDOMPLAYERSTARTS)) { - return P_SpawnPlayer(&start, pnum); + return P_SpawnPlayer(&start, pnum, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0); } return NULL; } @@ -4854,8 +4867,9 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) AActor *P_SpawnPuff (AActor *source, PClassActor *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags) { AActor *puff; - - z += pr_spawnpuff.Random2 () << 10; + + if (!(flags & PF_NORANDOMZ)) + z += pr_spawnpuff.Random2 () << 10; puff = Spawn (pufftype, x, y, z, ALLOW_REPLACE); if (puff == NULL) return NULL; @@ -5374,10 +5388,8 @@ void P_CheckSplash(AActor *self, fixed_t distance) // //--------------------------------------------------------------------------- -bool P_CheckMissileSpawn (AActor* th) +bool P_CheckMissileSpawn (AActor* th, fixed_t maxdist) { - int shift, count = 1; - // [RH] Don't decrement tics if they are already less than 1 if ((th->flags4 & MF4_RANDOMIZE) && th->tics > 0) { @@ -5386,76 +5398,67 @@ bool P_CheckMissileSpawn (AActor* th) th->tics = 1; } - // move a little forward so an angle can be computed if it immediately explodes - if (th->Speed >= 100*FRACUNIT) - { // Ultra-fast ripper spawning missile - shift = 3; - } - else - { // Normal missile - shift = 1; - } - - if (th->radius > 0) + if (maxdist > 0) { - while ( ((th->velx >> shift) > th->radius) || ((th->vely >> shift) > th->radius)) + // move a little forward so an angle can be computed if it immediately explodes + TVector3 advance(FIXED2DBL(th->velx), FIXED2DBL(th->vely), FIXED2DBL(th->velz)); + double maxsquared = FIXED2DBL(maxdist); + maxsquared *= maxsquared; + + // Keep halving the advance vector until we get something less than maxdist + // units away, since we still want to spawn the missile inside the shooter. + do { - // we need to take smaller steps but to produce the same end result - // we have to do more of them. - shift++; - count<<=1; + advance *= 0.5f; } + while (TVector2(advance).LengthSquared() >= maxsquared); + th->x += FLOAT2FIXED(advance.X); + th->y += FLOAT2FIXED(advance.Y); + th->z += FLOAT2FIXED(advance.Z); } FCheckPosition tm(!!(th->flags2 & MF2_RIP)); - for(int i=0; iflags & MF_MISSILE) || (th->BounceFlags & BOUNCE_MBF)); + + // killough 3/15/98: no dropoff (really = don't care for missiles) + if (!(P_TryMove (th, th->x, th->y, false, NULL, tm, true))) { - th->x += th->velx >> shift; - th->y += th->vely >> shift; - th->z += th->velz >> shift; - - // killough 8/12/98: for non-missile objects (e.g. grenades) - // - // [GZ] MBF excludes non-missile objects from the P_TryMove test - // and subsequent potential P_ExplodeMissile call. That is because - // in MBF, a projectile is not an actor with the MF_MISSILE flag - // but an actor with either or both the MF_MISSILE and MF_BOUNCES - // flags, and a grenade is identified by not having MF_MISSILE. - // Killough wanted grenades not to explode directly when spawned, - // therefore they can be fired safely even when humping a wall as - // they will then just drop on the floor at their shooter's feet. - // - // However, ZDoom does allow non-missiles to be shot as well, so - // Killough's check for non-missiles is inadequate here. So let's - // replace it by a check for non-missile and MBF bounce type. - // This should allow MBF behavior where relevant without altering - // established ZDoom behavior for crazy stuff like a cacodemon cannon. - bool MBFGrenade = (!(th->flags & MF_MISSILE) || (th->BounceFlags & BOUNCE_MBF)); - - // killough 3/15/98: no dropoff (really = don't care for missiles) - if (!(P_TryMove (th, th->x, th->y, false, NULL, tm, true))) + // [RH] Don't explode ripping missiles that spawn inside something + if (th->BlockingMobj == NULL || !(th->flags2 & MF2_RIP) || (th->BlockingMobj->flags5 & MF5_DONTRIP)) { - // [RH] Don't explode ripping missiles that spawn inside something - if (th->BlockingMobj == NULL || !(th->flags2 & MF2_RIP) || (th->BlockingMobj->flags5 & MF5_DONTRIP)) + // If this is a monster spawned by A_CustomMissile subtract it from the counter. + th->ClearCounters(); + // [RH] Don't explode missiles that spawn on top of horizon lines + if (th->BlockingLine != NULL && th->BlockingLine->special == Line_Horizon) { - // If this is a monster spawned by A_CustomMissile subtract it from the counter. - th->ClearCounters(); - // [RH] Don't explode missiles that spawn on top of horizon lines - if (th->BlockingLine != NULL && th->BlockingLine->special == Line_Horizon) - { - th->Destroy (); - } - else if (MBFGrenade && th->BlockingLine != NULL ) - { - P_BounceWall(th); - } - else - { - P_ExplodeMissile (th, NULL, th->BlockingMobj); - } - return false; + th->Destroy (); } + else if (MBFGrenade && th->BlockingLine != NULL) + { + P_BounceWall(th); + } + else + { + P_ExplodeMissile (th, NULL, th->BlockingMobj); + } + return false; } } return true; @@ -5590,7 +5593,7 @@ AActor *P_SpawnMissileXYZ (fixed_t x, fixed_t y, fixed_t z, th->SetFriendPlayer(owner->player); } - return (!checkspawn || P_CheckMissileSpawn (th)) ? th : NULL; + return (!checkspawn || P_CheckMissileSpawn (th, source->radius)) ? th : NULL; } AActor *P_OldSpawnMissile(AActor *source, AActor *owner, AActor *dest, PClassActor *type) @@ -5620,7 +5623,7 @@ AActor *P_OldSpawnMissile(AActor *source, AActor *owner, AActor *dest, PClassAct th->SetFriendPlayer(owner->player); } - P_CheckMissileSpawn(th); + P_CheckMissileSpawn(th, source->radius); return th; } @@ -5709,7 +5712,7 @@ AActor *P_SpawnMissileAngleZSpeed (AActor *source, fixed_t z, mo->SetFriendPlayer(owner->player); } - return (!checkspawn || P_CheckMissileSpawn(mo)) ? mo : NULL; + return (!checkspawn || P_CheckMissileSpawn(mo, source->radius)) ? mo : NULL; } /* @@ -5736,24 +5739,35 @@ AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, bool nofreeaim) { static const int angdiff[3] = { -1<<26, 1<<26, 0 }; - int i; angle_t an = angle; angle_t pitch; AActor *linetarget; - int vrange = nofreeaim? ANGLE_1*35 : 0; + AActor *defaultobject = GetDefaultByType(type); + int vrange = nofreeaim ? ANGLE_1*35 : 0; - if (source && source->player && source->player->ReadyWeapon && (source->player->ReadyWeapon->WeaponFlags & WIF_NOAUTOAIM)) + if (source == NULL) + { + return NULL; + } + if (source->player && source->player->ReadyWeapon && (source->player->ReadyWeapon->WeaponFlags & WIF_NOAUTOAIM)) { // Keep exactly the same angle and pitch as the player's own aim - pitch = source->pitch; linetarget = NULL; + an = angle; + pitch = source->pitch; + linetarget = NULL; } else // see which target is to be aimed at { - i = 2; + // [XA] If MaxTargetRange is defined in the spawned projectile, use this as the + // maximum range for the P_AimLineAttack call later; this allows MaxTargetRange + // to function as a "maximum tracer-acquisition range" for seeker missiles. + fixed_t linetargetrange = defaultobject->maxtargetrange > 0 ? defaultobject->maxtargetrange*64 : 16*64*FRACUNIT; + + int i = 2; do { an = angle + angdiff[i]; - pitch = P_AimLineAttack (source, an, 16*64*FRACUNIT, &linetarget, vrange); + pitch = P_AimLineAttack (source, an, linetargetrange, &linetarget, vrange); if (source->player != NULL && !nofreeaim && @@ -5763,20 +5777,18 @@ AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, break; } } while (linetarget == NULL && --i >= 0); - } - if (linetarget == NULL) - { - an = angle; - if (nofreeaim || !level.IsFreelookAllowed()) + if (linetarget == NULL) { - pitch = 0; + an = angle; + if (nofreeaim || !level.IsFreelookAllowed()) + { + pitch = 0; + } } } if (pLineTarget) *pLineTarget = linetarget; - i = GetDefaultByType (type)->flags3; - if (z != ONFLOORZ && z != ONCEILINGZ) { // Doom spawns missiles 4 units lower than hitscan attacks for players. @@ -5823,7 +5835,7 @@ AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, { MissileActor->SetFriendPlayer(source->player); } - if (P_CheckMissileSpawn (MissileActor)) + if (P_CheckMissileSpawn (MissileActor, source->radius)) { return MissileActor; } @@ -5839,11 +5851,10 @@ bool AActor::IsTeammate (AActor *other) int myTeam = DesignatedTeam; int otherTeam = other->DesignatedTeam; if (player) - myTeam = player->userinfo.team; + myTeam = player->userinfo.GetTeam(); if (other->player) - otherTeam = other->player->userinfo.team; - if (teamplay && myTeam != TEAM_NONE && - myTeam == otherTeam) + otherTeam = other->player->userinfo.GetTeam(); + if (teamplay && myTeam != TEAM_NONE && myTeam == otherTeam) { return true; } @@ -6187,26 +6198,26 @@ void PrintMiscActorInfo(AActor *query) "OptFuzzy", "Stencil", "Translucent", "Add", "Shaded", "TranslucentStencil"}; Printf("%s @ %p has the following flags:\n\tflags: %x", query->GetTag(), query, query->flags); - for (flagi = 0; flagi < 31; flagi++) + for (flagi = 0; flagi <= 31; flagi++) if (query->flags & 1<flags2); - for (flagi = 0; flagi < 31; flagi++) + for (flagi = 0; flagi <= 31; flagi++) if (query->flags2 & 1<flags3); - for (flagi = 0; flagi < 31; flagi++) + for (flagi = 0; flagi <= 31; flagi++) if (query->flags3 & 1<flags4); - for (flagi = 0; flagi < 31; flagi++) + for (flagi = 0; flagi <= 31; flagi++) if (query->flags4 & 1<flags5); - for (flagi = 0; flagi < 31; flagi++) + for (flagi = 0; flagi <= 31; flagi++) if (query->flags5 & 1<flags6); - for (flagi = 0; flagi < 31; flagi++) + for (flagi = 0; flagi <= 31; flagi++) if (query->flags6 & 1<BounceFlags, FIXED2FLOAT(query->bouncefactor), - FIXED2FLOAT(query->wallbouncefactor), query->BounceFlags); + FIXED2FLOAT(query->wallbouncefactor)); /*for (flagi = 0; flagi < 31; flagi++) if (query->BounceFlags & 1<cheats &= ~(CF_WEAPONREADY | CF_WEAPONREADYALT | CF_WEAPONBOBBING | CF_WEAPONSWITCHOK | CF_WEAPONRELOADOK | CF_WEAPONZOOMOK); + player->WeaponState &= ~(WF_WEAPONREADY | WF_WEAPONREADYALT | WF_WEAPONBOBBING | WF_WEAPONSWITCHOK | WF_WEAPONRELOADOK | WF_WEAPONZOOMOK); } psp = &player->psprites[position]; @@ -170,8 +170,11 @@ void P_BringUpWeapon (player_t *player) if (player->PendingWeapon == WP_NOCHANGE) { - player->psprites[ps_weapon].sy = WEAPONTOP; - P_SetPsprite (player, ps_weapon, player->ReadyWeapon->GetReadyState()); + if (player->ReadyWeapon != NULL) + { + player->psprites[ps_weapon].sy = WEAPONTOP; + P_SetPsprite (player, ps_weapon, player->ReadyWeapon->GetReadyState()); + } return; } @@ -352,6 +355,12 @@ void P_ZoomWeapon (player_t *player, FState *state) void P_DropWeapon (player_t *player) { + if (player == NULL) + { + return; + } + // Since the weapon is dropping, stop blocking switching. + player->WeaponState &= ~WF_DISABLESWITCH; if (player->ReadyWeapon != NULL) { P_SetPsprite (player, ps_weapon, player->ReadyWeapon->GetDownState()); @@ -397,7 +406,7 @@ void P_BobWeapon (player_t *player, pspdef_t *psp, fixed_t *x, fixed_t *y) // [RH] Smooth transitions between bobbing and not-bobbing frames. // This also fixes the bug where you can "stick" a weapon off-center by // shooting it when it's at the peak of its swing. - bobtarget = (player->cheats & CF_WEAPONBOBBING) ? player->bob : 0; + bobtarget = (player->WeaponState & WF_WEAPONBOBBING) ? player->bob : 0; if (curbob != bobtarget) { if (abs (bobtarget - curbob) <= 1*FRACUNIT) @@ -471,15 +480,43 @@ void P_BobWeapon (player_t *player, pspdef_t *psp, fixed_t *x, fixed_t *y) // //============================================================================ -void DoReadyWeaponToSwitch (AActor * self) +void DoReadyWeaponToSwitch (AActor *self, bool switchable) { // Prepare for switching action. player_t *player; if (self && (player = self->player)) - player->cheats |= CF_WEAPONSWITCHOK; + { + if (switchable) + { + player->WeaponState |= WF_WEAPONSWITCHOK | WF_REFIRESWITCHOK; + } + else + { + // WF_WEAPONSWITCHOK is automatically cleared every tic by P_SetPsprite(). + player->WeaponState &= ~WF_REFIRESWITCHOK; + } + } } -void DoReadyWeaponToFire (AActor * self, bool prim, bool alt) +void DoReadyWeaponDisableSwitch (AActor *self, INTBOOL disable) +{ + // Discard all switch attempts? + player_t *player; + if (self && (player = self->player)) + { + if (disable) + { + player->WeaponState |= WF_DISABLESWITCH; + player->WeaponState &= ~WF_REFIRESWITCHOK; + } + else + { + player->WeaponState &= ~WF_DISABLESWITCH; + } + } +} + +void DoReadyWeaponToFire (AActor *self, bool prim, bool alt) { player_t *player; AWeapon *weapon; @@ -506,41 +543,41 @@ void DoReadyWeaponToFire (AActor * self, bool prim, bool alt) } // Prepare for firing action. - player->cheats |= ((prim ? CF_WEAPONREADY : 0) | (alt ? CF_WEAPONREADYALT : 0)); + player->WeaponState |= ((prim ? WF_WEAPONREADY : 0) | (alt ? WF_WEAPONREADYALT : 0)); return; } -void DoReadyWeaponToBob (AActor * self) +void DoReadyWeaponToBob (AActor *self) { if (self && self->player && self->player->ReadyWeapon) { // Prepare for bobbing action. - self->player->cheats |= CF_WEAPONBOBBING; + self->player->WeaponState |= WF_WEAPONBOBBING; self->player->psprites[ps_weapon].sx = 0; self->player->psprites[ps_weapon].sy = WEAPONTOP; } } -void DoReadyWeaponToReload (AActor * self) +void DoReadyWeaponToReload (AActor *self) { // Prepare for reload action. player_t *player; if (self && (player = self->player)) - player->cheats |= CF_WEAPONRELOADOK; + player->WeaponState |= WF_WEAPONRELOADOK; return; } -void DoReadyWeaponToZoom (AActor * self) +void DoReadyWeaponToZoom (AActor *self) { // Prepare for reload action. player_t *player; if (self && (player = self->player)) - player->cheats |= CF_WEAPONZOOMOK; + player->WeaponState |= WF_WEAPONZOOMOK; return; } // This function replaces calls to A_WeaponReady in other codepointers. -void DoReadyWeapon(AActor * self) +void DoReadyWeapon(AActor *self) { DoReadyWeaponToBob(self); DoReadyWeaponToFire(self); @@ -552,12 +589,13 @@ void DoReadyWeapon(AActor * self) enum EWRF_Options { WRF_NoBob = 1, - WRF_NoFire = 12, WRF_NoSwitch = 2, WRF_NoPrimary = 4, WRF_NoSecondary = 8, + WRF_NoFire = WRF_NoPrimary + WRF_NoSecondary, WRF_AllowReload = 16, WRF_AllowZoom = 32, + WRF_DisableSwitch = 64, }; DEFINE_ACTION_FUNCTION_PARAMS(AInventory, A_WeaponReady) @@ -565,16 +603,13 @@ DEFINE_ACTION_FUNCTION_PARAMS(AInventory, A_WeaponReady) PARAM_ACTION_PROLOGUE; PARAM_INT_OPT(flags) { flags = 0; } - if (!(flags & WRF_NoSwitch)) - DoReadyWeaponToSwitch(self); - if ((flags & WRF_NoFire) != WRF_NoFire) - DoReadyWeaponToFire(self, !(flags & WRF_NoPrimary), !(flags & WRF_NoSecondary)); - if (!(flags & WRF_NoBob)) - DoReadyWeaponToBob(self); - if ((flags & WRF_AllowReload)) - DoReadyWeaponToReload(self); - if ((flags & WRF_AllowZoom)) - DoReadyWeaponToZoom(self); + DoReadyWeaponToSwitch(self, !(flags & WRF_NoSwitch)); + if ((flags & WRF_NoFire) != WRF_NoFire) DoReadyWeaponToFire(self, !(flags & WRF_NoPrimary), !(flags & WRF_NoSecondary)); + if (!(flags & WRF_NoBob)) DoReadyWeaponToBob(self); + if ((flags & WRF_AllowReload)) DoReadyWeaponToReload(self); + if ((flags & WRF_AllowZoom)) DoReadyWeaponToZoom(self); + + DoReadyWeaponDisableSwitch(self, flags & WRF_DisableSwitch); return 0; } @@ -596,7 +631,7 @@ void P_CheckWeaponFire (player_t *player) return; // Check for fire. Some weapons do not auto fire. - if ((player->cheats & CF_WEAPONREADY) && (player->cmd.ucmd.buttons & BT_ATTACK)) + if ((player->WeaponState & WF_WEAPONREADY) && (player->cmd.ucmd.buttons & BT_ATTACK)) { if (!player->attackdown || !(weapon->WeaponFlags & WIF_NOAUTOFIRE)) { @@ -605,7 +640,7 @@ void P_CheckWeaponFire (player_t *player) return; } } - else if ((player->cheats & CF_WEAPONREADYALT) && (player->cmd.ucmd.buttons & BT_ALTATTACK)) + else if ((player->WeaponState & WF_WEAPONREADYALT) && (player->cmd.ucmd.buttons & BT_ALTATTACK)) { if (!player->attackdown || !(weapon->WeaponFlags & WIF_NOAUTOFIRE)) { @@ -631,22 +666,23 @@ void P_CheckWeaponFire (player_t *player) void P_CheckWeaponSwitch (player_t *player) { - AWeapon *weapon; - - if (!player || !(weapon = player->ReadyWeapon)) - return; - - // Put the weapon away if the player has a pending weapon or has died. - if ((player->morphTics == 0 && player->PendingWeapon != WP_NOCHANGE) || player->health <= 0) + if (player == NULL) { - P_SetPsprite (player, ps_weapon, weapon->GetDownState()); return; } - else if (player->morphTics != 0) - { - // morphed classes cannot change weapons so don't even try again. + if ((player->WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled. + player->morphTics != 0) // Morphed classes cannot change weapons. + { // ...so throw away any pending weapon requests. player->PendingWeapon = WP_NOCHANGE; } + + // Put the weapon away if the player has a pending weapon or has died, and + // we're at a place in the state sequence where dropping the weapon is okay. + if ((player->PendingWeapon != WP_NOCHANGE || player->health <= 0) && + player->WeaponState & WF_WEAPONSWITCHOK) + { + P_DropWeapon(player); + } } //--------------------------------------------------------------------------- @@ -665,7 +701,7 @@ void P_CheckWeaponReload (player_t *player) return; // Check for reload. - if ((player->cheats & CF_WEAPONRELOADOK) && (player->cmd.ucmd.buttons & BT_RELOAD)) + if ((player->WeaponState & WF_WEAPONRELOADOK) && (player->cmd.ucmd.buttons & BT_RELOAD)) { P_ReloadWeapon (player, NULL); } @@ -687,7 +723,7 @@ void P_CheckWeaponZoom (player_t *player) return; // Check for zoom. - if ((player->cheats & CF_WEAPONZOOMOK) && (player->cmd.ucmd.buttons & BT_ZOOM)) + if ((player->WeaponState & WF_WEAPONZOOMOK) && (player->cmd.ucmd.buttons & BT_ZOOM)) { P_ZoomWeapon (player, NULL); } @@ -712,21 +748,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AInventory, A_ReFire) void A_ReFire(AActor *self, FState *state) { player_t *player = self->player; + bool pending; if (NULL == player) { return; } - if ((player->cmd.ucmd.buttons&BT_ATTACK) - && !player->ReadyWeapon->bAltFire - && player->PendingWeapon == WP_NOCHANGE && player->health) + pending = player->PendingWeapon != WP_NOCHANGE && (player->WeaponState & WF_REFIRESWITCHOK); + if ((player->cmd.ucmd.buttons & BT_ATTACK) + && !player->ReadyWeapon->bAltFire && !pending && player->health > 0) { player->refire++; P_FireWeapon (player, state); } - else if ((player->cmd.ucmd.buttons&BT_ALTATTACK) - && player->ReadyWeapon->bAltFire - && player->PendingWeapon == WP_NOCHANGE && player->health) + else if ((player->cmd.ucmd.buttons & BT_ALTATTACK) + && player->ReadyWeapon->bAltFire && !pending && player->health > 0) { player->refire++; P_FireWeaponAlt (player, state); @@ -807,17 +843,11 @@ DEFINE_ACTION_FUNCTION(AInventory, A_Lower) if (player->playerstate == PST_DEAD) { // Player is dead, so don't bring up a pending weapon psp->sy = WEAPONBOTTOM; - return 0; - } - if (player->health <= 0) - { // Player is dead, so keep the weapon off screen + + // Player is dead, so keep the weapon off screen P_SetPsprite (player, ps_weapon, NULL); return 0; } -/* if (player->PendingWeapon != WP_NOCHANGE) - { // [RH] Make sure we're actually changing weapons. - player->ReadyWeapon = player->PendingWeapon; - } */ // [RH] Clear the flash state. Only needed for Strife. P_SetPsprite (player, ps_flash, NULL); P_BringUpWeapon (player); @@ -847,7 +877,7 @@ DEFINE_ACTION_FUNCTION(AInventory, A_Raise) } if (player->PendingWeapon != WP_NOCHANGE) { - P_SetPsprite (player, ps_weapon, player->ReadyWeapon->GetDownState()); + P_DropWeapon(player); return 0; } psp = &player->psprites[ps_weapon]; @@ -1011,7 +1041,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AInventory, A_Light) if (self->player != NULL) { - self->player->extralight = clamp(light, 0, 20); + self->player->extralight = clamp(light, -20, 20); } return 0; } @@ -1024,7 +1054,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AInventory, A_Light) // //------------------------------------------------------------------------ -void P_SetupPsprites(player_t *player) +void P_SetupPsprites(player_t *player, bool startweaponup) { int i; @@ -1034,7 +1064,7 @@ void P_SetupPsprites(player_t *player) player->psprites[i].state = NULL; } // Spawn the ready weapon - player->PendingWeapon = player->ReadyWeapon; + player->PendingWeapon = !startweaponup ? player->ReadyWeapon : WP_NOCHANGE; P_BringUpWeapon (player); } @@ -1075,7 +1105,7 @@ void P_MovePsprites (player_t *player) psp->tics--; // [BC] Apply double firing speed. - if ( psp->tics && ( player->cheats & CF_DOUBLEFIRINGSPEED )) + if ( psp->tics && (player->cheats & CF_DOUBLEFIRINGSPEED)) psp->tics--; if(!psp->tics) @@ -1087,19 +1117,16 @@ void P_MovePsprites (player_t *player) } player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx; player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy; - if (player->cheats & CF_WEAPONSWITCHOK) - { - P_CheckWeaponSwitch (player); - } - if (player->cheats & (CF_WEAPONREADY | CF_WEAPONREADYALT)) + P_CheckWeaponSwitch (player); + if (player->WeaponState & (WF_WEAPONREADY | WF_WEAPONREADYALT)) { P_CheckWeaponFire (player); } - if (player->cheats & CF_WEAPONRELOADOK) + if (player->WeaponState & WF_WEAPONRELOADOK) { P_CheckWeaponReload (player); } - if (player->cheats & CF_WEAPONZOOMOK) + if (player->WeaponState & WF_WEAPONZOOMOK) { P_CheckWeaponZoom (player); } diff --git a/src/p_pspr.h b/src/p_pspr.h index 0156e2abf..a9eec48b6 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -94,7 +94,7 @@ void P_GunShot (AActor *mo, bool accurate, PClassActor *pufftype, angle_t pitch) void DoReadyWeapon(AActor *self); void DoReadyWeaponToBob(AActor *self); void DoReadyWeaponToFire(AActor *self, bool primary = true, bool secondary = true); -void DoReadyWeaponToSwitch(AActor *self); +void DoReadyWeaponToSwitch(AActor *self, bool switchable = true); DECLARE_ACTION(A_Raise) void A_ReFire(AActor *self, FState *state = NULL); diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 205e91443..a045c5098 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -91,7 +91,7 @@ void P_SerializePlayers (FArchive &arc, bool skipload) { if (playeringame[i]) { - arc.WriteString (players[i].userinfo.netname); + arc.WriteString (players[i].userinfo.GetName()); players[i].Serialize (arc); } } @@ -185,7 +185,7 @@ static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNo { for (j = 0; j < MAXPLAYERS; ++j) { - if (playerUsed[j] == 0 && stricmp(players[j].userinfo.netname, nametemp[i]) == 0) + if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0) { // Found a match, so copy our temp player to the real player Printf ("Found player %d (%s) at %d\n", i, nametemp[i], j); CopyPlayer (&players[j], &playertemp[i], nametemp[i]); @@ -206,7 +206,7 @@ static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNo { if (playerUsed[j] == 0) { - Printf ("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.netname); + Printf ("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName()); CopyPlayer (&players[j], &playertemp[i], nametemp[i]); playerUsed[j] = 1; tempPlayerUsed[i] = 1; @@ -282,7 +282,7 @@ static void CopyPlayer (player_t *dst, player_t *src, const char *name) dst->userinfo = uibackup; } // Validate the skin - dst->userinfo.skin = R_FindSkin(skins[dst->userinfo.skin].name, dst->CurrentPlayerClass); + dst->userinfo.SkinNumChanged(R_FindSkin(skins[dst->userinfo.GetSkin()].name, dst->CurrentPlayerClass)); // Make sure the player pawn points to the proper player struct. if (dst->mo != NULL) @@ -310,7 +310,7 @@ static void SpawnExtraPlayers () if (playeringame[i] && players[i].mo == NULL) { players[i].playerstate = PST_ENTER; - P_SpawnPlayer(&playerstarts[i], i); + P_SpawnPlayer(&playerstarts[i], i, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0); } } } diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index 330344679..d90466fec 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -996,10 +996,10 @@ int side_t::GetLightLevel (bool foggy, int baselight, bool noabsolute, int *pfak baselight += rel; } } - if (!(Flags & WALLF_ABSLIGHTING)) - { - baselight += this->Light; - } + } + if (!(Flags & WALLF_ABSLIGHTING) && (!foggy || (Flags & WALLF_LIGHT_FOG))) + { + baselight += this->Light; } return baselight; } diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 37a9da789..a0f0b83eb 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -142,6 +142,8 @@ int numgamesubsectors; bool hasglnodes; TArray MapThingsConverted; +TMap MapThingsUserDataIndex; // from mapthing idx -> user data idx +TArray MapThingsUserData; int sidecount; sidei_t *sidetemp; @@ -251,6 +253,7 @@ static int GetMapIndex(const char *mapname, int lastindex, const char *lumpname, MapData *P_OpenMapData(const char * mapname) { MapData * map = new MapData; + FileReader * wadReader = NULL; bool externalfile = !strnicmp(mapname, "file:", 5); if (externalfile) @@ -261,8 +264,8 @@ MapData *P_OpenMapData(const char * mapname) delete map; return NULL; } - map->file = new FileReader(mapname); - map->CloseOnDestruct = true; + map->resource = FResourceFile::OpenResourceFile(mapname, NULL, true); + wadReader = map->resource->GetReader(); } else { @@ -284,18 +287,13 @@ MapData *P_OpenMapData(const char * mapname) int lumpfile = Wads.GetLumpFile(lump_name); int nextfile = Wads.GetLumpFile(lump_name+1); - map->file = Wads.GetFileReader(lumpfile); - map->CloseOnDestruct = false; map->lumpnum = lump_name; if (lumpfile != nextfile) { // The following lump is from a different file so whatever this is, // it is not a multi-lump Doom level so let's assume it is a Build map. - map->MapLumps[0].FilePos = 0; - map->MapLumps[0].Size = Wads.LumpLength(lump_name); - map->file = Wads.ReopenLumpNum(lump_name); - map->CloseOnDestruct = true; + map->MapLumps[0].Reader = map->file = Wads.ReopenLumpNum(lump_name); if (!P_IsBuildMap(map)) { delete map; @@ -306,15 +304,14 @@ MapData *P_OpenMapData(const char * mapname) // This case can only happen if the lump is inside a real WAD file. // As such any special handling for other types of lumps is skipped. - map->MapLumps[0].FilePos = Wads.GetLumpOffset(lump_name); - map->MapLumps[0].Size = Wads.LumpLength(lump_name); + map->MapLumps[0].Reader = map->file = Wads.ReopenLumpNum(lump_name); + strncpy(map->MapLumps[0].Name, Wads.GetLumpFullName(lump_name), 8); map->Encrypted = Wads.IsEncryptedFile(lump_name); + map->InWad = true; if (map->Encrypted) { // If it's encrypted, then it's a Blood file, presumably a map. - map->file = Wads.ReopenLumpNum(lump_name); - map->CloseOnDestruct = true; - map->MapLumps[0].FilePos = 0; + map->MapLumps[0].Reader = map->file = Wads.ReopenLumpNum(lump_name); if (!P_IsBuildMap(map)) { delete map; @@ -332,22 +329,20 @@ MapData *P_OpenMapData(const char * mapname) // Since levels must be stored in WADs they can't really have full // names and for any valid level lump this always returns the short name. const char * lumpname = Wads.GetLumpFullName(lump_name + i); - index = GetMapIndex(mapname, index, lumpname, i != 1 || map->MapLumps[0].Size == 0); + index = GetMapIndex(mapname, index, lumpname, i != 1 || Wads.LumpLength(lump_name + i) == 0); if (index == ML_BEHAVIOR) map->HasBehavior = true; // The next lump is not part of this map anymore if (index < 0) break; - map->MapLumps[index].FilePos = Wads.GetLumpOffset(lump_name + i); - map->MapLumps[index].Size = Wads.LumpLength(lump_name + i); + map->MapLumps[index].Reader = Wads.ReopenLumpNum(lump_name + i); strncpy(map->MapLumps[index].Name, lumpname, 8); } } else { map->isText = true; - map->MapLumps[1].FilePos = Wads.GetLumpOffset(lump_name + 1); - map->MapLumps[1].Size = Wads.LumpLength(lump_name + 1); + map->MapLumps[1].Reader = Wads.ReopenLumpNum(lump_name + 1); for(int i = 2;; i++) { const char * lumpname = Wads.GetLumpFullName(lump_name + i); @@ -383,8 +378,7 @@ MapData *P_OpenMapData(const char * mapname) break; } else continue; - map->MapLumps[index].FilePos = Wads.GetLumpOffset(lump_name + i); - map->MapLumps[index].Size = Wads.LumpLength(lump_name + i); + map->MapLumps[index].Reader = Wads.ReopenLumpNum(lump_name + i); strncpy(map->MapLumps[index].Name, lumpname, 8); } } @@ -402,47 +396,38 @@ MapData *P_OpenMapData(const char * mapname) return NULL; } map->lumpnum = lump_wad; - map->file = Wads.ReopenLumpNum(lump_wad); - map->CloseOnDestruct = true; + map->resource = FResourceFile::OpenResourceFile(Wads.GetLumpFullName(lump_wad), Wads.ReopenLumpNum(lump_wad), true); + wadReader = map->resource->GetReader(); } } DWORD id; - map->file->Read(&id, sizeof(id)); + // Although we're using the resource system, we still want to be sure we're + // reading from a wad file. + wadReader->Seek(0, SEEK_SET); + wadReader->Read(&id, sizeof(id)); if (id == IWAD_ID || id == PWAD_ID) { char maplabel[9]=""; int index=0; - DWORD dirofs, numentries; - (*map->file) >> numentries >> dirofs; + map->MapLumps[0].Reader = map->resource->GetLump(0)->NewReader(); + strncpy(map->MapLumps[0].Name, map->resource->GetLump(0)->Name, 8); - map->file->Seek(dirofs, SEEK_SET); - (*map->file) >> map->MapLumps[0].FilePos >> map->MapLumps[0].Size; - map->file->Read(map->MapLumps[0].Name, 8); - - for(DWORD i = 1; i < numentries; i++) + for(DWORD i = 1; i < map->resource->LumpCount(); i++) { - DWORD offset, size; - char lumpname[8]; + const char* lumpname = map->resource->GetLump(i)->Name; - (*map->file) >> offset >> size; - map->file->Read(lumpname, 8); if (i == 1 && !strnicmp(lumpname, "TEXTMAP", 8)) { map->isText = true; - map->MapLumps[1].FilePos = offset; - map->MapLumps[1].Size = size; + map->MapLumps[ML_TEXTMAP].Reader = map->resource->GetLump(i)->NewReader(); + strncpy(map->MapLumps[ML_TEXTMAP].Name, lumpname, 8); for(int i = 2;; i++) { - (*map->file) >> offset >> size; - long v = map->file->Read(lumpname, 8); - if (v < 8) - { - I_Error("Invalid map definition for %s", mapname); - } - else if (!strnicmp(lumpname, "ZNODES",8)) + lumpname = map->resource->GetLump(i)->Name; + if (!strnicmp(lumpname, "ZNODES",8)) { index = ML_GLZNODES; } @@ -469,8 +454,7 @@ MapData *P_OpenMapData(const char * mapname) return map; } else continue; - map->MapLumps[index].FilePos = offset; - map->MapLumps[index].Size = size; + map->MapLumps[index].Reader = map->resource->GetLump(i)->NewReader(); strncpy(map->MapLumps[index].Name, lumpname, 8); } } @@ -489,15 +473,14 @@ MapData *P_OpenMapData(const char * mapname) maplabel[8]=0; } - map->MapLumps[index].FilePos = offset; - map->MapLumps[index].Size = size; + map->MapLumps[index].Reader = map->resource->GetLump(i)->NewReader(); strncpy(map->MapLumps[index].Name, lumpname, 8); } } else { // This is a Build map and not subject to WAD consistency checks. - map->MapLumps[0].Size = map->file->GetLength(); + //map->MapLumps[0].Size = wadReader->GetLength(); if (!P_IsBuildMap(map)) { delete map; @@ -532,29 +515,29 @@ void MapData::GetChecksum(BYTE cksum[16]) { if (isText) { - file->Seek(MapLumps[ML_TEXTMAP].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_TEXTMAP].Size); + Seek(ML_TEXTMAP); + md5.Update(file, Size(ML_TEXTMAP)); } else { - if (MapLumps[ML_LABEL].Size != 0) + if (Size(ML_LABEL) != 0) { - file->Seek(MapLumps[ML_LABEL].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_LABEL].Size); + Seek(ML_LABEL); + md5.Update(file, Size(ML_LABEL)); } - file->Seek(MapLumps[ML_THINGS].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_THINGS].Size); - file->Seek(MapLumps[ML_LINEDEFS].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_LINEDEFS].Size); - file->Seek(MapLumps[ML_SIDEDEFS].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_SIDEDEFS].Size); - file->Seek(MapLumps[ML_SECTORS].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_SECTORS].Size); + Seek(ML_THINGS); + md5.Update(file, Size(ML_THINGS)); + Seek(ML_LINEDEFS); + md5.Update(file, Size(ML_LINEDEFS)); + Seek(ML_SIDEDEFS); + md5.Update(file, Size(ML_SIDEDEFS)); + Seek(ML_SECTORS); + md5.Update(file, Size(ML_SECTORS)); } if (HasBehavior) { - file->Seek(MapLumps[ML_BEHAVIOR].FilePos, SEEK_SET); - md5.Update(file, MapLumps[ML_BEHAVIOR].Size); + Seek(ML_BEHAVIOR); + md5.Update(file, Size(ML_BEHAVIOR)); } } md5.Final(cksum); @@ -834,7 +817,7 @@ void P_LoadVertexes (MapData * map) // Determine number of vertices: // total lump length / vertex record length. - numvertexes = map->MapLumps[ML_VERTEXES].Size / sizeof(mapvertex_t); + numvertexes = map->Size(ML_VERTEXES) / sizeof(mapvertex_t); numvertexdatas = 0; if (numvertexes == 0) @@ -914,7 +897,7 @@ void P_LoadGLZSegs (FileReaderBase &data, int type) BYTE side; data >> v1 >> partner; - if (type == 2) + if (type >= 2) { data >> line; } @@ -1067,13 +1050,20 @@ void LoadZNodes(FileReaderBase &data, int glnodes) for (i = 0; i < numNodes; ++i) { - SWORD x, y, dx, dy; + if (glnodes < 3) + { + SWORD x, y, dx, dy; - data >> x >> y >> dx >> dy; - nodes[i].x = x << FRACBITS; - nodes[i].y = y << FRACBITS; - nodes[i].dx = dx << FRACBITS; - nodes[i].dy = dy << FRACBITS; + data >> x >> y >> dx >> dy; + nodes[i].x = x << FRACBITS; + nodes[i].y = y << FRACBITS; + nodes[i].dx = dx << FRACBITS; + nodes[i].dy = dy << FRACBITS; + } + else + { + data >> nodes[i].x >> nodes[i].y >> nodes[i].dx >> nodes[i].dy; + } for (int j = 0; j < 2; ++j) { for (int k = 0; k < 4; ++k) @@ -1122,6 +1112,11 @@ void P_LoadZNodes (FileReader &dalump, DWORD id) compressed = true; break; + case MAKE_ID('Z','G','L','3'): + type = 3; + compressed = true; + break; + case MAKE_ID('X','N','O','D'): type = 0; compressed = false; @@ -1137,6 +1132,11 @@ void P_LoadZNodes (FileReader &dalump, DWORD id) compressed = false; break; + case MAKE_ID('X','G','L','3'): + type = 3; + compressed = false; + break; + default: return; } @@ -1199,7 +1199,7 @@ void P_LoadSegs (MapData * map) int dis; // phares 10/4/98 int dx,dy; // phares 10/4/98 int vnum1,vnum2; // phares 10/4/98 - int lumplen = map->MapLumps[ML_SEGS].Size; + int lumplen = map->Size(ML_SEGS); memset (vertchanged,0,numvertexes); // phares 10/4/98 @@ -1382,7 +1382,7 @@ void P_LoadSubsectors (MapData * map) int i; DWORD maxseg = map->Size(ML_SEGS) / sizeof(segtype); - numsubsectors = map->MapLumps[ML_SSECTORS].Size / sizeof(subsectortype); + numsubsectors = map->Size(ML_SSECTORS) / sizeof(subsectortype); if (numsubsectors == 0 || maxseg == 0 ) { @@ -1641,7 +1641,7 @@ void P_LoadNodes (MapData * map) //=========================================================================== CVAR(Bool, dumpspawnedthings, false, 0) -void SpawnMapThing(int index, FMapThing *mt, int position) +AActor *SpawnMapThing(int index, FMapThing *mt, int position) { AActor *spawned = P_SpawnMapThing(mt, position); if (dumpspawnedthings) @@ -1651,6 +1651,39 @@ void SpawnMapThing(int index, FMapThing *mt, int position) spawned? spawned->GetClass()->TypeName.GetChars() : "(none)"); } T_AddSpawnedThing(spawned); + return spawned; +} + +//=========================================================================== +// +// SetMapThingUserData +// +//=========================================================================== + +static void SetMapThingUserData(AActor *actor, unsigned udi) +{ + if (actor == NULL) + { + return; + } + while (MapThingsUserData[udi].Property != NAME_None) + { + FName varname = MapThingsUserData[udi].Property; + int value = MapThingsUserData[udi].Value; + PSymbolVariable *var = dyn_cast(actor->GetClass()->Symbols.FindSymbol(varname, true)); + + udi++; + + if (var == NULL || var->bUserVar || var->ValueType.Type != VAL_Int) + { + DPrintf("%s is not a user variable in class %s\n", varname.GetChars(), + actor->GetClass()->TypeName.GetChars()); + } + else + { // Set the value of the specified user variable. + *(int *)(reinterpret_cast(actor) + var->offset) = value; + } + } } //=========================================================================== @@ -1689,7 +1722,7 @@ void P_LoadThings (MapData * map) for (int i=0 ; i < numthings; i++, mt++) { // [RH] At this point, monsters unique to Doom II were weeded out - // if the IWAD wasn't for Doom II. R_SpawnMapThing() can now + // if the IWAD wasn't for Doom II. P_SpawnMapThing() can now // handle these and more cases better, so we just pass it // everything and let it decide what to do with them. @@ -1746,7 +1779,7 @@ void P_LoadThings (MapData * map) void P_LoadThings2 (MapData * map) { - int lumplen = map->MapLumps[ML_THINGS].Size; + int lumplen = map->Size(ML_THINGS); int numthings = lumplen / sizeof(mapthinghexen_t); char *mtp; @@ -1784,7 +1817,12 @@ void P_SpawnThings (int position) for (int i=0; i < numthings; i++) { - SpawnMapThing (i, &MapThingsConverted[i], position); + AActor *actor = SpawnMapThing (i, &MapThingsConverted[i], position); + unsigned *udi = MapThingsUserDataIndex.CheckKey((unsigned)i); + if (udi != NULL) + { + SetMapThingUserData(actor, *udi); + } } for(int i=0; iMapLumps[0].Size > 0) + if (map->Size(0) > 0) { - BYTE *mapdata = new BYTE[map->MapLumps[0].Size]; + BYTE *mapdata = new BYTE[map->Size(0)]; map->Seek(0); - map->file->Read(mapdata, map->MapLumps[0].Size); + map->file->Read(mapdata, map->Size(0)); times[0].Clock(); - buildmap = P_LoadBuildMap (mapdata, map->MapLumps[0].Size, &buildthings, &numbuildthings); + buildmap = P_LoadBuildMap (mapdata, map->Size(0), &buildthings, &numbuildthings); times[0].Unclock(); delete[] mapdata; } @@ -3612,6 +3652,10 @@ void P_SetupLevel (char *lumpname, int position) level.maptype = MAPTYPE_UDMF; } CheckCompatibility(map); + if (ib_compatflags & BCOMPATF_REBUILDNODES) + { + ForceNodeBuild = true; + } T_LoadScripts(map); if (!map->HasBehavior || map->isText) @@ -3702,26 +3746,28 @@ void P_SetupLevel (char *lumpname, int position) { // Check for compressed nodes first, then uncompressed nodes FWadLump test; - DWORD id = MAKE_ID('X','x','X','x'), idcheck = 0, idcheck2 = 0, idcheck3 = 0, idcheck4 = 0; + DWORD id = MAKE_ID('X','x','X','x'), idcheck = 0, idcheck2 = 0, idcheck3 = 0, idcheck4 = 0, idcheck5 = 0, idcheck6 = 0; - if (map->MapLumps[ML_ZNODES].Size != 0) + if (map->Size(ML_ZNODES) != 0) { // Test normal nodes first map->Seek(ML_ZNODES); idcheck = MAKE_ID('Z','N','O','D'); idcheck2 = MAKE_ID('X','N','O','D'); } - else if (map->MapLumps[ML_GLZNODES].Size != 0) + else if (map->Size(ML_GLZNODES) != 0) { map->Seek(ML_GLZNODES); idcheck = MAKE_ID('Z','G','L','N'); idcheck2 = MAKE_ID('Z','G','L','2'); - idcheck3 = MAKE_ID('X','G','L','N'); - idcheck4 = MAKE_ID('X','G','L','2'); + idcheck3 = MAKE_ID('Z','G','L','3'); + idcheck4 = MAKE_ID('X','G','L','N'); + idcheck5 = MAKE_ID('X','G','L','2'); + idcheck6 = MAKE_ID('X','G','L','3'); } map->file->Read (&id, 4); - if (id != 0 && (id == idcheck || id == idcheck2 || id == idcheck3 || id == idcheck4)) + if (id != 0 && (id == idcheck || id == idcheck2 || id == idcheck3 || id == idcheck4 || id == idcheck5 || id == idcheck6)) { try { @@ -3989,7 +4035,7 @@ void P_SetupLevel (char *lumpname, int position) { players[i].mo = NULL; FPlayerStart *mthing = G_PickPlayerStart(i); - P_SpawnPlayer(mthing, i); + P_SpawnPlayer(mthing, i, (level.flags2 & LEVEL2_PRERAISEWEAPON) ? SPF_WEAPONFULLYUP : 0); } } } @@ -4069,6 +4115,8 @@ void P_SetupLevel (char *lumpname, int position) } } MapThingsConverted.Clear(); + MapThingsUserDataIndex.Clear(); + MapThingsUserData.Clear(); if (glsegextras != NULL) { diff --git a/src/p_setup.h b/src/p_setup.h index 4bb0435e0..6e0a90a1d 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -23,42 +23,52 @@ #ifndef __P_SETUP__ #define __P_SETUP__ -#include "w_wad.h" +#include "resourcefiles/resourcefile.h" #include "doomdata.h" struct MapData { - wadlump_t MapLumps[ML_MAX]; + struct MapLump + { + char Name[8]; + FileReader *Reader; + } MapLumps[ML_MAX]; bool HasBehavior; - bool CloseOnDestruct; bool Encrypted; bool isText; + bool InWad; int lumpnum; FileReader * file; + FResourceFile * resource; MapData() { memset(MapLumps, 0, sizeof(MapLumps)); file = NULL; + resource = NULL; lumpnum = -1; HasBehavior = false; - CloseOnDestruct = true; Encrypted = false; isText = false; + InWad = false; } ~MapData() { - if (CloseOnDestruct && file != NULL) delete file; - file = NULL; + for (unsigned int i = 0;i < ML_MAX;++i) + delete MapLumps[i].Reader; + + delete resource; + resource = NULL; } void Seek(unsigned int lumpindex) { if (lumpindexSeek(MapLumps[lumpindex].FilePos, SEEK_SET); + file = MapLumps[lumpindex].Reader; + file->Seek(0, SEEK_SET); } } @@ -66,17 +76,17 @@ struct MapData { if (lumpindexSeek(MapLumps[lumpindex].FilePos, SEEK_SET); + if (size == -1) size = MapLumps[lumpindex].Reader->GetLength(); + Seek(lumpindex); file->Read(buffer, size); } } DWORD Size(unsigned int lumpindex) { - if (lumpindexGetLength(); } return 0; } @@ -149,4 +159,14 @@ struct FMissingCount }; typedef TMap FMissingTextureTracker; +// Record of user data for UDMF maps +struct FMapThingUserData +{ + FName Property; + int Value; +}; +extern TMap MapThingsUserDataIndex; // from mapthing idx -> user data idx +extern TArray MapThingsUserData; + + #endif diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 5da0c7f76..2ec77fe2a 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -177,7 +177,7 @@ bool CheckIfExitIsGood (AActor *self, level_info_t *info) } if (deathmatch) { - Printf ("%s exited the level.\n", self->player->userinfo.netname); + Printf ("%s exited the level.\n", self->player->userinfo.GetName()); } return true; } @@ -1837,24 +1837,28 @@ static void P_SpawnScrollers(void) break; case Scroll_Texture_Left: + l->special = special; // Restore the special, for compat_useblocking's benefit. s = int(lines[i].sidedef[0] - sides); new DScroller (DScroller::sc_side, l->args[0] * (FRACUNIT/64), 0, -1, s, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Right: + l->special = special; s = int(lines[i].sidedef[0] - sides); new DScroller (DScroller::sc_side, l->args[0] * (-FRACUNIT/64), 0, -1, s, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Up: + l->special = special; s = int(lines[i].sidedef[0] - sides); new DScroller (DScroller::sc_side, 0, l->args[0] * (FRACUNIT/64), -1, s, accel, SCROLLTYPE(l->args[1])); break; case Scroll_Texture_Down: + l->special = special; s = int(lines[i].sidedef[0] - sides); new DScroller (DScroller::sc_side, 0, l->args[0] * (-FRACUNIT/64), -1, s, accel, SCROLLTYPE(l->args[1])); diff --git a/src/p_spec.h b/src/p_spec.h index 52d7a62a1..1e45c44ad 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -697,6 +697,7 @@ public: floorRaiseToHighest, floorRaiseToNearest, floorRaiseAndCrush, + floorRaiseAndCrushDoom, floorCrushStop, floorLowerInstant, floorRaiseInstant, diff --git a/src/p_states.cpp b/src/p_states.cpp index ac0696347..4c12dbd66 100644 --- a/src/p_states.cpp +++ b/src/p_states.cpp @@ -342,8 +342,8 @@ FStateDefine *FStateDefinitions::FindStateLabelInList(TArray & lis if (create) { FStateDefine def; - def.Label=name; - def.State=NULL; + def.Label = name; + def.State = NULL; def.DefineFlags = SDF_NEXT; return &list[list.Push(def)]; } @@ -359,11 +359,10 @@ FStateDefine *FStateDefinitions::FindStateLabelInList(TArray & lis FStateDefine *FStateDefinitions::FindStateAddress(const char *name) { - FStateDefine *statedef=NULL; - + FStateDefine *statedef = NULL; TArray &namelist = MakeStateNameList(name); - TArray *statelist = &StateLabels; + for(unsigned i = 0; i < namelist.Size(); i++) { statedef = FindStateLabelInList(*statelist, namelist[i], true); @@ -387,7 +386,7 @@ void FStateDefinitions::SetStateLabel(const char *statename, FState *state, BYTE //========================================================================== // -// Adds a new state to the curremt list +// Adds a new state to the current list // //========================================================================== @@ -401,6 +400,24 @@ void FStateDefinitions::AddStateLabel(const char *statename) lastlabel = index; } +//========================================================================== +// +// Returns the index a state label points to. May only be called before +// installing states. +// +//========================================================================== + +int FStateDefinitions::GetStateLabelIndex (FName statename) +{ + FStateDefine *std = FindStateLabelInList(StateLabels, statename, false); + if (std == NULL) + { + return -1; + } + assert((size_t)std->State <= StateArray.Size() + 1); + return (int)((ptrdiff_t)std->State - 1); +} + //========================================================================== // // Finds the state associated with the given name @@ -888,6 +905,9 @@ int FStateDefinitions::AddStates(FState *state, const char *framechars) state->SameFrame = noframe; StateArray.Push(*state); ++count; + + // NODELAY flag is not carried past the first state + state->NoDelay = false; } laststate = &StateArray[StateArray.Size() - 1]; laststatebeforelabel = laststate; diff --git a/src/p_teleport.cpp b/src/p_teleport.cpp index 0b812d2c6..6c0d07a43 100644 --- a/src/p_teleport.cpp +++ b/src/p_teleport.cpp @@ -76,12 +76,7 @@ void ATeleportFog::PostBeginPlay () void P_SpawnTeleportFog(fixed_t x, fixed_t y, fixed_t z, int spawnid) { - PClassActor *fog = NULL; - - if (spawnid > 0 && spawnid < MAX_SPAWNABLES && SpawnableThings[spawnid] != NULL) - { - fog = SpawnableThings[spawnid]; - } + PClassActor *fog = P_GetSpawnableType(spawnid); if (fog == NULL) { @@ -189,14 +184,16 @@ bool P_Teleport (AActor *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, if (sourceFog) { fixed_t fogDelta = thing->flags & MF_MISSILE ? 0 : TELEFOGHEIGHT; - Spawn (oldx, oldy, oldz + fogDelta, ALLOW_REPLACE); + AActor *fog = Spawn (oldx, oldy, oldz + fogDelta, ALLOW_REPLACE); + fog->target = thing; } if (useFog) { fixed_t fogDelta = thing->flags & MF_MISSILE ? 0 : TELEFOGHEIGHT; an = angle >> ANGLETOFINESHIFT; - Spawn (x + 20*finecosine[an], + AActor *fog = Spawn (x + 20*finecosine[an], y + 20*finesine[an], thing->z + fogDelta, ALLOW_REPLACE); + fog->target = thing; if (thing->player) { // [RH] Zoom player's field of vision diff --git a/src/p_terrain.cpp b/src/p_terrain.cpp index c3df86b27..fdd98c96a 100644 --- a/src/p_terrain.cpp +++ b/src/p_terrain.cpp @@ -631,7 +631,8 @@ static void ParseFloor (FScanner &sc) int terrain; sc.MustGetString (); - picnum = TexMan.CheckForTexture (sc.String, FTexture::TEX_Flat); + picnum = TexMan.CheckForTexture (sc.String, FTexture::TEX_Flat, + FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny); if (!picnum.Exists()) { Printf ("Unknown flat %s\n", sc.String); @@ -650,7 +651,7 @@ static void ParseFloor (FScanner &sc) //========================================================================== // -// ParseFloor +// ParseDefault // //========================================================================== diff --git a/src/p_things.cpp b/src/p_things.cpp index 41ba20ff5..5d769238b 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -46,8 +46,8 @@ #include "templates.h" #include "g_level.h" -// List of spawnable things for the Thing_Spawn and Thing_Projectile specials. -PClassActor *SpawnableThings[MAX_SPAWNABLES]; +// Set of spawnable things for the Thing_Spawn and Thing_Projectile specials. +TMap SpawnableThings; static FRandom pr_leadtarget ("LeadTarget"); @@ -58,10 +58,9 @@ bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, AActor *spot, *mobj; FActorIterator iterator (tid); - if (type >= MAX_SPAWNABLES) - return false; + kind = P_GetSpawnableType(type); - if ( (kind = SpawnableThings[type]) == NULL) + if (kind == NULL) return false; // Handle decorate replacements. @@ -182,18 +181,16 @@ bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_nam if (type_name == NULL) { - if (type >= MAX_SPAWNABLES) - return false; - - if ((kind = SpawnableThings[type]) == NULL) - return false; + kind = P_GetSpawnableType(type); } else { - if ((kind = PClass::FindActor(type_name)) == NULL) - return false; + kind = PClass::FindActor(type_name); + } + if (kind == NULL) + { + return false; } - // Handle decorate replacements. kind = kind->GetReplacement(); @@ -339,7 +336,7 @@ nolead: mobj->angle = R_PointToAngle2 (mobj->x, mobj->y, targ->x, targ->y); } if (mobj->flags & MF_MISSILE) { - if (P_CheckMissileSpawn (mobj)) + if (P_CheckMissileSpawn (mobj, spot->radius)) { rtn = true; } @@ -487,17 +484,53 @@ void P_Thing_SetVelocity(AActor *actor, fixed_t vx, fixed_t vy, fixed_t vz, bool } } +PClassActor *P_GetSpawnableType(int spawnnum) +{ + if (spawnnum < 0) + { // A named arg from a UDMF map + FName spawnname = FName(ENamedName(-spawnnum)); + if (spawnname.IsValidName()) + { + return PClass::FindActor(spawnname); + } + } + else + { // A numbered arg from a Hexen or UDMF map + PClassActor **type = SpawnableThings.CheckKey(spawnnum); + if (type != NULL) + { + return *type; + } + } + return NULL; +} + +typedef TMap::Pair SpawnablePair; + +static int STACK_ARGS SpawnableSort(const void *a, const void *b) +{ + return (*((SpawnablePair **)a))->Key - (*((SpawnablePair **)b))->Key; +} CCMD (dumpspawnables) { - int i; + TMapIterator it(SpawnableThings); + SpawnablePair *pair, **allpairs; + int i = 0; - for (i = 0; i < MAX_SPAWNABLES; i++) + // Sort into numerical order, since their arrangement in the map can + // be in an unspecified order. + allpairs = new TMap::Pair *[SpawnableThings.CountUsed()]; + while (it.NextPair(pair)) { - if (SpawnableThings[i] != NULL) - { - Printf ("%d %s\n", i, SpawnableThings[i]->TypeName.GetChars()); - } + allpairs[i++] = pair; } + qsort(allpairs, i, sizeof(*allpairs), SpawnableSort); + for (int j = 0; j < i; ++j) + { + pair = allpairs[j]; + Printf ("%d %s\n", pair->Key, pair->Value->TypeName.GetChars()); + } + delete[] allpairs; } diff --git a/src/p_tick.cpp b/src/p_tick.cpp index 26c4c2ae1..07e153ffa 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -117,13 +117,13 @@ void P_Ticker (void) { P_ThinkParticles (); // [RH] make the particles think } - StatusBar->Tick (); // [RH] moved this here for (i = 0; iTick (); // [RH] moved this here level.Tick (); // [RH] let the level tick DThinker::RunThinkers (); diff --git a/src/p_trace.cpp b/src/p_trace.cpp index 93c347d9f..d1b9f63ac 100644 --- a/src/p_trace.cpp +++ b/src/p_trace.cpp @@ -48,7 +48,8 @@ struct FTraceInfo sector_t *CurSector; fixed_t MaxDist; fixed_t EnterDist; - bool (*TraceCallback)(FTraceResults &res); + ETraceStatus (*TraceCallback)(FTraceResults &res, void *data); + void *TraceCallbackData; DWORD TraceFlags; int inshootthrough; @@ -71,7 +72,7 @@ bool Trace (fixed_t x, fixed_t y, fixed_t z, sector_t *sector, fixed_t vx, fixed_t vy, fixed_t vz, fixed_t maxDist, DWORD actorMask, DWORD wallMask, AActor *ignore, FTraceResults &res, - DWORD flags, bool (*callback)(FTraceResults &res)) + DWORD flags, ETraceStatus (*callback)(FTraceResults &res, void *), void *callbackdata) { int ptflags; FTraceInfo inf; @@ -91,6 +92,7 @@ bool Trace (fixed_t x, fixed_t y, fixed_t z, sector_t *sector, inf.MaxDist = maxDist; inf.EnterDist = 0; inf.TraceCallback = callback; + inf.TraceCallbackData = callbackdata; inf.TraceFlags = flags; inf.Results = &res; inf.inshootthrough = true; @@ -506,7 +508,13 @@ cont: if (TraceCallback != NULL) { - if (!TraceCallback (*Results)) return false; + switch (TraceCallback(*Results, TraceCallbackData)) + { + case TRACE_Stop: return false; + case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; + case TRACE_Skip: Results->HitType = TRACE_HitNone; break; + default: break; + } } else { @@ -515,8 +523,7 @@ cont: } // Encountered an actor - if (!(in->d.thing->flags & ActorMask) || - in->d.thing == IgnoreThis) + if (!(in->d.thing->flags & ActorMask) || in->d.thing == IgnoreThis) { continue; } @@ -583,13 +590,19 @@ cont: // the trace hit a 3D-floor before the thing. // Calculate an intersection and abort. Results->Sector = §ors[CurSector->sectornum]; - if (!CheckSectorPlane(CurSector, Results->HitType==TRACE_HitFloor)) + if (!CheckSectorPlane(CurSector, Results->HitType == TRACE_HitFloor)) { - Results->HitType=TRACE_HitNone; + Results->HitType = TRACE_HitNone; } if (TraceCallback != NULL) { - return TraceCallback (*Results); + switch (TraceCallback(*Results, TraceCallbackData)) + { + case TRACE_Continue: return true; + case TRACE_Stop: return false; + case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; + case TRACE_Skip: Results->HitType = TRACE_HitNone; return true; + } } else { @@ -608,7 +621,13 @@ cont1: if (TraceCallback != NULL) { - if (!TraceCallback (*Results)) return false; + switch (TraceCallback(*Results, TraceCallbackData)) + { + case TRACE_Stop: return false; + case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; + case TRACE_Skip: Results->HitType = TRACE_HitNone; break; + default: break; + } } else { diff --git a/src/p_trace.h b/src/p_trace.h index 8ce905da3..403b1f9cf 100644 --- a/src/p_trace.h +++ b/src/p_trace.h @@ -85,10 +85,20 @@ enum TRACE_Impact = 4, // Trigger SPAC_IMPACT lines }; +// return values from callback +enum ETraceStatus +{ + TRACE_Stop, // stop the trace, returning this hit + TRACE_Continue, // continue the trace, returning this hit if there are none further along + TRACE_Skip, // continue the trace; do not return this hit + TRACE_Abort, // stop the trace, returning no hits +}; + bool Trace (fixed_t x, fixed_t y, fixed_t z, sector_t *sector, fixed_t vx, fixed_t vy, fixed_t vz, fixed_t maxDist, DWORD ActorMask, DWORD WallMask, AActor *ignore, FTraceResults &res, - DWORD traceFlags=0, bool (*callback)(FTraceResults &res)=NULL); + DWORD traceFlags=0, + ETraceStatus (*callback)(FTraceResults &res, void *)=NULL, void *callbackdata=NULL); #endif //__P_TRACE_H__ diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index ce05903e0..c1d18fd4c 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -45,6 +45,7 @@ #include "p_udmf.h" #include "r_state.h" #include "r_data/colormaps.h" +#include "w_wad.h" //=========================================================================== // @@ -97,6 +98,11 @@ static char HexenSectorSpecialOk[256]={ 1,1,1,1,1, }; +static inline bool P_IsThingSpecial(int specnum) +{ + return (specnum >= Thing_Projectile && specnum <= Thing_SpawnNoFog) || + specnum == Thing_SpawnFacing || Thing_ProjectileIntercept || Thing_ProjectileAimed; +} enum { @@ -466,7 +472,7 @@ public: void ParseThing(FMapThing *th) { - FString arg0str; + FString arg0str, arg1str; memset(th, 0, sizeof(*th)); sc.MustGetToken('{'); @@ -523,6 +529,11 @@ public: arg0str = CheckString(key); break; + case NAME_Arg1Str: + CHECK_N(Zd); + arg1str = CheckString(key); + break; + case NAME_Skill1: case NAME_Skill2: case NAME_Skill3: @@ -616,17 +627,24 @@ public: break; default: - if (!strnicmp("user_", key.GetChars(), 5)) - { - // Custom user key - handle later + if (0 == strnicmp("user_", key.GetChars(), 5)) + { // Custom user key - Sets an actor's user variable directly + FMapThingUserData ud; + ud.Property = key; + ud.Value = CheckInt(key); + MapThingsUserData.Push(ud); } break; } } - if (arg0str.IsNotEmpty() && P_IsACSSpecial(th->special)) + if (arg0str.IsNotEmpty() && (P_IsACSSpecial(th->special) || th->special == 0)) { th->args[0] = -FName(arg0str); } + if (arg1str.IsNotEmpty() && (P_IsThingSpecial(th->special) || th->special == 0)) + { + th->args[1] = -FName(arg1str); + } // Thing specials are only valid in namespaces with Hexen-type specials // and in ZDoomTranslated - which will use the translator on them. if (namespc == NAME_ZDoomTranslated) @@ -662,7 +680,7 @@ public: { bool passuse = false; bool strifetrans = false; - FString arg0str; + FString arg0str, arg1str; memset(ld, 0, sizeof(*ld)); ld->Alpha = FRACUNIT; @@ -723,6 +741,11 @@ public: arg0str = CheckString(key); continue; + case NAME_Arg1Str: + CHECK_N(Zd); + arg1str = CheckString(key); + continue; + case NAME_Blocking: Flag(ld->flags, ML_BLOCKING, key); continue; @@ -937,10 +960,14 @@ public: ld->sidedef[0] = (side_t*)(intptr_t)(1); Printf("Line %d has no first side.\n", index); } - if (arg0str.IsNotEmpty() && P_IsACSSpecial(ld->special)) + if (arg0str.IsNotEmpty() && (P_IsACSSpecial(ld->special) || ld->special == 0)) { ld->args[0] = -FName(arg0str); } + if (arg1str.IsNotEmpty() && (P_IsThingSpecial(ld->special) || ld->special == 0)) + { + ld->args[1] = -FName(arg1str); + } } //=========================================================================== @@ -1052,6 +1079,10 @@ public: Flag(sd->Flags, WALLF_ABSLIGHTING, key); continue; + case NAME_lightfog: + Flag(sd->Flags, WALLF_LIGHT_FOG, key); + continue; + case NAME_nofakecontrast: Flag(sd->Flags, WALLF_NOFAKECONTRAST, key); continue; @@ -1511,6 +1542,7 @@ public: isTranslated = false; break; case NAME_ZDoomTranslated: + level.flags2 |= LEVEL2_DUMMYSWITCHES; namespace_bits = Zdt; break; case NAME_Vavoom: @@ -1575,8 +1607,18 @@ public: if (sc.Compare("thing")) { FMapThing th; + unsigned userdatastart = MapThingsUserData.Size(); ParseThing(&th); MapThingsConverted.Push(th); + if (userdatastart < MapThingsUserData.Size()) + { // User data added + MapThingsUserDataIndex[MapThingsConverted.Size()-1] = userdatastart; + // Mark end of the user data for this map thing + FMapThingUserData ud; + ud.Property = NAME_None; + ud.Value = 0; + MapThingsUserData.Push(ud); + } } else if (sc.Compare("linedef")) { diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp index c6465f554..372a8d0a6 100644 --- a/src/p_usdf.cpp +++ b/src/p_usdf.cpp @@ -42,6 +42,7 @@ #include "cmdlib.h" #include "actor.h" #include "a_pickups.h" +#include "w_wad.h" #define Zd 1 #define St 2 diff --git a/src/p_user.cpp b/src/p_user.cpp index 27ecfe60e..6e9787d8f 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -243,6 +243,7 @@ player_t::player_t() lastkilltime(0), multicount(0), spreecount(0), + WeaponState(0), ReadyWeapon(0), PendingWeapon(0), cheats(0), @@ -621,7 +622,7 @@ void APlayerPawn::BeginPlay () void APlayerPawn::Tick() { - if (player != NULL && player->mo == this && player->morphTics == 0 && player->playerstate != PST_DEAD) + if (player != NULL && player->mo == this && player->CanCrouch() && player->playerstate != PST_DEAD) { height = FixedMul(GetDefault()->height, player->crouchfactor); } @@ -848,6 +849,12 @@ AWeapon *APlayerPawn::BestWeapon(PClassAmmo *ammotype) !weap->CheckAmmo (AWeapon::PrimaryFire, false)) continue; + // Don't select if if there isn't enough ammo as determined by the weapon's author. + if (weap->MinSelAmmo1 > 0 && (weap->Ammo1 == NULL || weap->Ammo1->Amount < weap->MinSelAmmo1)) + continue; + if (weap->MinSelAmmo2 > 0 && (weap->Ammo2 == NULL || weap->Ammo2->Amount < weap->MinSelAmmo2)) + continue; + // This weapon is usable! bestOrder = weap->SelectionOrder; bestMatch = weap; @@ -874,7 +881,7 @@ AWeapon *APlayerPawn::PickNewWeapon(PClassAmmo *ammotype) player->PendingWeapon = best; if (player->ReadyWeapon != NULL) { - P_SetPsprite (player, ps_weapon, player->ReadyWeapon->GetDownState()); + P_DropWeapon(player); } else if (player->PendingWeapon != WP_NOCHANGE) { @@ -895,7 +902,7 @@ AWeapon *APlayerPawn::PickNewWeapon(PClassAmmo *ammotype) void APlayerPawn::CheckWeaponSwitch(PClassAmmo *ammotype) { - if (!player->userinfo.neverswitch && + if (!player->userinfo.GetNeverSwitch() && player->PendingWeapon == WP_NOCHANGE && (player->ReadyWeapon == NULL || (player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON))) @@ -1067,10 +1074,10 @@ const char *APlayerPawn::GetSoundClass() const { if (player != NULL && (player->mo == NULL || !(player->mo->flags4 &MF4_NOSKIN)) && - (unsigned int)player->userinfo.skin >= PlayerClasses.Size () && - (size_t)player->userinfo.skin < numskins) + (unsigned int)player->userinfo.GetSkin() >= PlayerClasses.Size () && + (size_t)player->userinfo.GetSkin() < numskins) { - return skins[player->userinfo.skin].name; + return skins[player->userinfo.GetSkin()].name; } // [GRB] @@ -1570,75 +1577,50 @@ DEFINE_ACTION_FUNCTION(AActor, A_CheckPlayerDone) // // P_CheckPlayerSprites // -// Here's the place where crouching sprites are handled -// This must be called each frame before rendering +// Here's the place where crouching sprites are handled. +// R_ProjectSprite() calls this for any players. // //=========================================================================== -void P_CheckPlayerSprites() +void P_CheckPlayerSprite(AActor *actor, int &spritenum, fixed_t &scalex, fixed_t &scaley) { - for (int i = 0; i < MAXPLAYERS; i++) + player_t *player = actor->player; + int crouchspriteno; + + if (player->userinfo.GetSkin() != 0 && !(actor->flags4 & MF4_NOSKIN)) { - player_t * player = &players[i]; - APlayerPawn * mo = player->mo; + // Convert from default scale to skin scale. + fixed_t defscaleY = actor->GetDefault()->scaleY; + fixed_t defscaleX = actor->GetDefault()->scaleX; + scaley = Scale(scaley, skins[player->userinfo.GetSkin()].ScaleY, defscaleY); + scalex = Scale(scalex, skins[player->userinfo.GetSkin()].ScaleX, defscaleX); + } - if (playeringame[i] && mo != NULL) + // Set the crouch sprite? + if (player->crouchfactor < FRACUNIT*3/4) + { + if (spritenum == actor->SpawnState->sprite || spritenum == player->mo->crouchsprite) { - int crouchspriteno; - fixed_t defscaleY = mo->GetDefault()->scaleY; - fixed_t defscaleX = mo->GetDefault()->scaleX; - - if (player->userinfo.skin != 0 && !(player->mo->flags4 & MF4_NOSKIN)) - { - defscaleY = skins[player->userinfo.skin].ScaleY; - defscaleX = skins[player->userinfo.skin].ScaleX; - } - - // Set the crouch sprite - if (player->crouchfactor < FRACUNIT*3/4) - { - if (mo->sprite == mo->SpawnState->sprite || mo->sprite == mo->crouchsprite) - { - crouchspriteno = mo->crouchsprite; - } - else if (!(player->mo->flags4 & MF4_NOSKIN) && - (mo->sprite == skins[player->userinfo.skin].sprite || - mo->sprite == skins[player->userinfo.skin].crouchsprite)) - { - crouchspriteno = skins[player->userinfo.skin].crouchsprite; - } - else - { - // no sprite -> squash the existing one - crouchspriteno = -1; - } + crouchspriteno = player->mo->crouchsprite; + } + else if (!(actor->flags4 & MF4_NOSKIN) && + (spritenum == skins[player->userinfo.GetSkin()].sprite || + spritenum == skins[player->userinfo.GetSkin()].crouchsprite)) + { + crouchspriteno = skins[player->userinfo.GetSkin()].crouchsprite; + } + else + { // no sprite -> squash the existing one + crouchspriteno = -1; + } - if (crouchspriteno > 0) - { - mo->sprite = crouchspriteno; - mo->scaleY = defscaleY; - } - else if (player->playerstate != PST_DEAD) - { - mo->scaleY = player->crouchfactor < FRACUNIT*3/4 ? defscaleY/2 : defscaleY; - } - } - else // Set the normal sprite - { - if (mo->sprite != 0) - { - if (mo->sprite == mo->crouchsprite) - { - mo->sprite = mo->SpawnState->sprite; - } - else if (mo->sprite != 0 && mo->sprite == skins[player->userinfo.skin].crouchsprite) - { - mo->sprite = skins[player->userinfo.skin].sprite; - } - } - mo->scaleY = defscaleY; - } - mo->scaleX = defscaleX; + if (crouchspriteno > 0) + { + spritenum = crouchspriteno; + } + else if (player->playerstate != PST_DEAD && player->crouchfactor < FRACUNIT*3/4) + { + scaley /= 2; } } } @@ -1690,8 +1672,16 @@ void P_ForwardThrust (player_t *player, angle_t angle, fixed_t move) // reduced at a regular rate, even on ice (where the player coasts). // -void P_Bob (player_t *player, angle_t angle, fixed_t move) +void P_Bob (player_t *player, angle_t angle, fixed_t move, bool forward) { + if (forward + && (player->mo->waterlevel || (player->mo->flags & MF_NOGRAVITY)) + && player->mo->pitch != 0) + { + angle_t pitch = (angle_t)player->mo->pitch >> ANGLETOFINESHIFT; + move = FixedMul (move, finecosine[pitch]); + } + angle >>= ANGLETOFINESHIFT; player->velx += FixedMul(move, finecosine[angle]); @@ -1741,7 +1731,7 @@ void P_CalcHeight (player_t *player) } else { - player->bob = FixedMul (player->bob, player->userinfo.MoveBob); + player->bob = FixedMul (player->bob, player->userinfo.GetMoveBob()); if (player->bob > MAXBOB) player->bob = MAXBOB; @@ -1765,7 +1755,7 @@ void P_CalcHeight (player_t *player) if (player->health > 0) { angle = DivScale13 (level.time, 120*TICRATE/35) & FINEMASK; - bob = FixedMul (player->userinfo.StillBob, finesine[angle]); + bob = FixedMul (player->userinfo.GetStillBob(), finesine[angle]); } else { @@ -1884,8 +1874,8 @@ void P_MovePlayer (player_t *player) fm = FixedMul (fm, player->mo->Speed); sm = FixedMul (sm, player->mo->Speed); - // When crouching speed and bobbing have to be reduced - if (player->morphTics==0 && player->crouchfactor != FRACUNIT) + // When crouching, speed and bobbing have to be reduced + if (player->CanCrouch() && player->crouchfactor != FRACUNIT) { fm = FixedMul(fm, player->crouchfactor); sm = FixedMul(sm, player->crouchfactor); @@ -1897,12 +1887,12 @@ void P_MovePlayer (player_t *player) if (forwardmove) { - P_Bob (player, mo->angle, (cmd->ucmd.forwardmove * bobfactor) >> 8); + P_Bob (player, mo->angle, (cmd->ucmd.forwardmove * bobfactor) >> 8, true); P_ForwardThrust (player, mo->angle, forwardmove); } if (sidemove) { - P_Bob (player, mo->angle-ANG90, (cmd->ucmd.sidemove * bobfactor) >> 8); + P_Bob (player, mo->angle-ANG90, (cmd->ucmd.sidemove * bobfactor) >> 8, false); P_SideThrust (player, mo->angle, sidemove); } @@ -2206,7 +2196,7 @@ void P_PlayerThink (player_t *player) player->ReadyWeapon != NULL && // No adjustment if no weapon. player->ReadyWeapon->FOVScale != 0) // No adjustment if the adjustment is zero. { - // A negative scale is used top prevent G_AddViewAngle/G_AddViewPitch + // A negative scale is used to prevent G_AddViewAngle/G_AddViewPitch // from scaling with the FOV scale. desired *= fabs(player->ReadyWeapon->FOVScale); } @@ -2301,7 +2291,7 @@ void P_PlayerThink (player_t *player) { player->cmd.ucmd.buttons &= ~BT_CROUCH; } - if (player->morphTics == 0 && player->health > 0 && level.IsCrouchingAllowed()) + if (player->CanCrouch() && player->health > 0 && level.IsCrouchingAllowed()) { if (!totallyfrozen) { @@ -2309,11 +2299,11 @@ void P_PlayerThink (player_t *player) if (crouchdir == 0) { - crouchdir = (player->cmd.ucmd.buttons & BT_CROUCH)? -1 : 1; + crouchdir = (player->cmd.ucmd.buttons & BT_CROUCH) ? -1 : 1; } else if (player->cmd.ucmd.buttons & BT_CROUCH) { - player->crouching=0; + player->crouching = 0; } if (crouchdir == 1 && player->crouchfactor < FRACUNIT && player->mo->z + player->mo->height < player->mo->ceilingz) @@ -2435,10 +2425,9 @@ void P_PlayerThink (player_t *player) // Jumping while crouching will force an un-crouch but not jump player->crouching = 1; } - else - if (player->mo->waterlevel >= 2) + else if (player->mo->waterlevel >= 2) { - player->mo->velz = 4*FRACUNIT; + player->mo->velz = FixedMul(4*FRACUNIT, player->mo->Speed); } else if (player->mo->flags & MF_NOGRAVITY) { @@ -2476,7 +2465,7 @@ void P_PlayerThink (player_t *player) } if (player->mo->waterlevel >= 2 || (player->mo->flags2 & MF2_FLY) || (player->cheats & CF_NOCLIP2)) { - player->mo->velz = cmd->ucmd.upmove << 9; + player->mo->velz = FixedMul(player->mo->Speed, cmd->ucmd.upmove << 9); if (player->mo->waterlevel < 2 && !(player->mo->flags & MF_NOGRAVITY)) { player->mo->flags2 |= MF2_FLY; @@ -2680,9 +2669,14 @@ void P_UnPredictPlayer () if (player->cheats & CF_PREDICTING) { AActor *act = player->mo; + AActor *savedcamera = player->camera; *player = PredictionPlayerBackup; + // Restore the camera instead of using the backup's copy, because spynext/prev + // could cause it to change during prediction. + player->camera = savedcamera; + act->UnlinkFromWorld (); memcpy (&act->x, PredictionActorBackup, sizeof(AActor)-((BYTE *)&act->x-(BYTE *)act)); @@ -2782,6 +2776,16 @@ void player_t::Serialize (FArchive &arc) mo->stamina = oldstamina; } } + if (SaveVersion < 4041) + { + // Move weapon state flags from cheats and into WeaponState. + WeaponState = ((cheats >> 14) & 1) | ((cheats & (0x37 << 24)) >> (24 - 1)); + cheats &= ~((1 << 14) | (0x37 << 24)); + } + else + { + arc << WeaponState; + } arc << LogText << ConversationNPC << ConversationPC diff --git a/src/po_man.cpp b/src/po_man.cpp index 6e301377e..289e06016 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -382,24 +382,24 @@ void DRotatePoly::Tick () FPolyObj *poly = PO_GetPolyobj (m_PolyObj); if (poly == NULL) return; + // Don't let non-perpetual polyobjs overshoot their targets. + if (m_Dist != -1 && (unsigned int)m_Dist < (unsigned int)abs(m_Speed)) + { + m_Speed = m_Speed < 0 ? -m_Dist : m_Dist; + } + if (poly->RotatePolyobj (m_Speed)) { - unsigned int absSpeed = abs (m_Speed); - if (m_Dist == -1) { // perpetual polyobj return; } - m_Dist -= absSpeed; + m_Dist -= abs(m_Speed); if (m_Dist == 0) { SN_StopSequence (poly); Destroy (); } - else if ((unsigned int)m_Dist < absSpeed) - { - m_Speed = m_Dist * (m_Speed < 0 ? -1 : 1); - } } } @@ -446,7 +446,7 @@ bool EV_RotatePoly (line_t *line, int polyNum, int speed, int byteAngle, { pe->m_Dist = ANGLE_MAX-1; } - pe->m_Speed = (speed*direction*(ANGLE_90/64))>>3; + pe->m_Speed = speed*direction*(ANGLE_90/(64<<3)); SN_StartSequence (poly, poly->seqType, SEQ_DOOR, 0); direction = -direction; // Reverse the direction } @@ -907,8 +907,8 @@ void FPolyObj::ThrustMobj (AActor *actor, side_t *side) { if (bHurtOnTouch || !P_CheckMove (actor, actor->x + thrustX, actor->y + thrustY)) { - P_DamageMobj (actor, NULL, NULL, crush, NAME_Crush); - P_TraceBleed (crush, actor); + int newdam = P_DamageMobj (actor, NULL, NULL, crush, NAME_Crush); + P_TraceBleed (newdam > 0 ? newdam : crush, actor); } } if (level.flags2 & LEVEL2_POLYGRIND) actor->Grind(false); // crush corpses that get caught in a polyobject's way @@ -986,6 +986,7 @@ void FPolyObj::CalcCenter() bool FPolyObj::MovePolyobj (int x, int y, bool force) { + FBoundingBox oldbounds = Bounds; UnLinkPolyobj (); DoMovePolyobj (x, y); @@ -1013,6 +1014,7 @@ bool FPolyObj::MovePolyobj (int x, int y, bool force) CenterSpot.y += y; LinkPolyobj (); ClearSubsectorLinks(); + RecalcActorFloorCeil(Bounds | oldbounds); return true; } @@ -1065,6 +1067,7 @@ bool FPolyObj::RotatePolyobj (angle_t angle) { int an; bool blocked; + FBoundingBox oldbounds = Bounds; an = (this->angle+angle)>>ANGLETOFINESHIFT; @@ -1103,6 +1106,7 @@ bool FPolyObj::RotatePolyobj (angle_t angle) this->angle += angle; LinkPolyobj(); ClearSubsectorLinks(); + RecalcActorFloorCeil(Bounds | oldbounds); return true; } @@ -1338,6 +1342,26 @@ void FPolyObj::LinkPolyobj () } } +//=========================================================================== +// +// FPolyObj :: RecalcActorFloorCeil +// +// For each actor within the bounding box, recalculate its floorz, ceilingz, +// and related values. +// +//=========================================================================== + +void FPolyObj::RecalcActorFloorCeil(FBoundingBox bounds) const +{ + FBlockThingsIterator it(bounds); + AActor *actor; + + while ((actor = it.Next()) != NULL) + { + P_FindFloorCeiling(actor); + } +} + //=========================================================================== // // PO_ClosestPoint diff --git a/src/po_man.h b/src/po_man.h index 5603095d5..35dc2af9c 100644 --- a/src/po_man.h +++ b/src/po_man.h @@ -76,6 +76,7 @@ struct FPolyObj bool RotatePolyobj (angle_t angle); void ClosestPoint(fixed_t fx, fixed_t fy, fixed_t &ox, fixed_t &oy, side_t **side) const; void LinkPolyobj (); + void RecalcActorFloorCeil(FBoundingBox bounds) const; void CreateSubsectorLinks(); void ClearSubsectorLinks(); void CalcCenter(); diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index 163b0e296..31dfe9907 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -346,7 +346,7 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, { sector_t *heightsec = viewsector->heightsec; bool underwater = r_fakingunderwater || - (heightsec && viewz <= heightsec->floorplane.ZatPoint (viewx, viewy)); + (heightsec && heightsec->floorplane.PointOnSide(viewx, viewy, viewz) <= 0); bool doorunderwater = false; int diffTex = (s->MoreFlags & SECF_CLIPFAKEPLANES); @@ -405,9 +405,7 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, } } -// fixed_t refflorz = s->floorplane.ZatPoint (viewx, viewy); fixed_t refceilz = s->ceilingplane.ZatPoint (viewx, viewy); -// fixed_t orgflorz = sec->floorplane.ZatPoint (viewx, viewy); fixed_t orgceilz = sec->ceilingplane.ZatPoint (viewx, viewy); #if 1 @@ -482,7 +480,7 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, } FakeSide = FAKED_BelowFloor; } - else if (heightsec && viewz >= heightsec->ceilingplane.ZatPoint (viewx, viewy) && + else if (heightsec && heightsec->ceilingplane.PointOnSide(viewx, viewy, viewz) <= 0 && orgceilz > refceilz && !(s->MoreFlags & SECF_FAKEFLOORONLY)) { // Above-ceiling hack tempsec->ceilingplane = s->ceilingplane; @@ -1017,6 +1015,7 @@ void R_Subsector (subsector_t *sub) int ceilinglightlevel; // killough 4/11/98 bool outersubsector; int fll, cll, position; + ASkyViewpoint *skybox; // kg3D - fake floor stuff visplane_t *backupfp; @@ -1072,16 +1071,22 @@ void R_Subsector (subsector_t *sub) { light = P_GetPlaneLight(frontsector, &frontsector->ceilingplane, false); basecolormap = light->extra_colormap; - ceilinglightlevel = *light->p_lightlevel; + // If this is the real ceiling, don't discard plane lighting R_FakeFlat() + // accounted for. + if (light->p_lightlevel != &frontsector->lightlevel) + { + ceilinglightlevel = *light->p_lightlevel; + } } else { basecolormap = frontsector->ColorMap; } - ceilingplane = frontsector->ceilingplane.ZatPoint (viewx, viewy) > viewz || + skybox = frontsector->CeilingSkyBox != NULL ? frontsector->CeilingSkyBox : level.DefaultSkybox; + ceilingplane = frontsector->ceilingplane.PointOnSide(viewx, viewy, viewz) > 0 || frontsector->GetTexture(sector_t::ceiling) == skyflatnum || - (frontsector->CeilingSkyBox != NULL && frontsector->CeilingSkyBox->bAlways) || + (skybox != NULL && skybox->bAlways) || (frontsector->heightsec && !(frontsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) && frontsector->heightsec->GetTexture(sector_t::floor) == skyflatnum) ? @@ -1096,14 +1101,19 @@ void R_Subsector (subsector_t *sub) frontsector->GetYScale(sector_t::ceiling), frontsector->GetAngle(sector_t::ceiling), frontsector->sky, - frontsector->CeilingSkyBox + skybox ) : NULL; if (fixedlightlev < 0 && frontsector->e && frontsector->e->XFloor.lightlist.Size()) { light = P_GetPlaneLight(frontsector, &frontsector->floorplane, false); basecolormap = light->extra_colormap; - floorlightlevel = *light->p_lightlevel; + // If this is the real floor, don't discard plane lighting R_FakeFlat() + // accounted for. + if (light->p_lightlevel != &frontsector->lightlevel) + { + floorlightlevel = *light->p_lightlevel; + } } else { @@ -1113,9 +1123,10 @@ void R_Subsector (subsector_t *sub) // killough 3/7/98: Add (x,y) offsets to flats, add deep water check // killough 3/16/98: add floorlightlevel // killough 10/98: add support for skies transferred from sidedefs - floorplane = frontsector->floorplane.ZatPoint (viewx, viewy) < viewz || // killough 3/7/98 + skybox = frontsector->FloorSkyBox != NULL ? frontsector->FloorSkyBox : level.DefaultSkybox; + floorplane = frontsector->floorplane.PointOnSide(viewx, viewy, viewz) > 0 || // killough 3/7/98 frontsector->GetTexture(sector_t::floor) == skyflatnum || - (frontsector->FloorSkyBox != NULL && frontsector->FloorSkyBox->bAlways) || + (skybox != NULL && skybox->bAlways) || (frontsector->heightsec && !(frontsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) && frontsector->heightsec->GetTexture(sector_t::ceiling) == skyflatnum) ? @@ -1130,7 +1141,7 @@ void R_Subsector (subsector_t *sub) frontsector->GetYScale(sector_t::floor), frontsector->GetAngle(sector_t::floor), frontsector->sky, - frontsector->FloorSkyBox + skybox ) : NULL; // kg3D - fake planes rendering diff --git a/src/r_data/r_translate.cpp b/src/r_data/r_translate.cpp index 230689ad9..d11388e6e 100644 --- a/src/r_data/r_translate.cpp +++ b/src/r_data/r_translate.cpp @@ -1101,7 +1101,7 @@ void R_BuildPlayerTranslation (int player) D_GetPlayerColor (player, &h, &s, &v, &colorset); R_CreatePlayerTranslation (h, s, v, colorset, - &skins[players[player].userinfo.skin], + &skins[players[player].userinfo.GetSkin()], translationtables[TRANSLATION_Players][player], translationtables[TRANSLATION_PlayersExtra][player]); } diff --git a/src/r_data/voxels.cpp b/src/r_data/voxels.cpp index eca4c89c4..d5ad1fa89 100644 --- a/src/r_data/voxels.cpp +++ b/src/r_data/voxels.cpp @@ -71,13 +71,14 @@ TDeletingArray VoxelDefs; struct VoxelOptions { VoxelOptions() - : DroppedSpin(0), PlacedSpin(0), Scale(FRACUNIT), AngleOffset(ANGLE_90) + : DroppedSpin(0), PlacedSpin(0), Scale(FRACUNIT), AngleOffset(ANGLE_90), OverridePalette(false) {} int DroppedSpin; int PlacedSpin; fixed_t Scale; angle_t AngleOffset; + bool OverridePalette; }; //========================================================================== @@ -296,6 +297,14 @@ FVoxel *R_LoadKVX(int lumpnum) } voxel->NumMips = mip; + // Fix pivot data for submips, since some tools seem to like to just center these. + for (i = 1; i < mip; ++i) + { + voxel->Mips[i].PivotX = voxel->Mips[0].PivotX >> i; + voxel->Mips[i].PivotY = voxel->Mips[0].PivotY >> i; + voxel->Mips[i].PivotZ = voxel->Mips[0].PivotZ >> i; + } + for (i = 0; i < mip; ++i) { if (!CopyVoxelSlabs((kvxslab_t *)voxel->Mips[i].SlabData, slabs[i], voxel->Mips[i].OffsetX[voxel->Mips[i].SizeX])) @@ -400,6 +409,20 @@ void FVoxel::Remap() { RemapVoxelSlabs((kvxslab_t *)Mips[i].SlabData, Mips[i].OffsetX[Mips[i].SizeX], remap); } + RemovePalette(); + } +} + +//========================================================================== +// +// Delete the voxel's built-in palette +// +//========================================================================== + +void FVoxel::RemovePalette() +{ + if (Palette != NULL) + { delete [] Palette; Palette = NULL; } @@ -510,6 +533,10 @@ static void VOX_ReadOptions(FScanner &sc, VoxelOptions &opts) } opts.AngleOffset = ANGLE_90 + angle_t(sc.Float * ANGLE_180 / 180.0); } + else if (sc.Compare("overridepalette")) + { + opts.OverridePalette = true; + } else { sc.ScriptMessage("Unknown voxel option '%s'\n", sc.String); @@ -595,6 +622,10 @@ void R_InitVoxels() sc.SetCMode(false); if (voxeldata != NULL && vsprites.Size() != 0) { + if (opts.OverridePalette) + { + voxeldata->RemovePalette(); + } FVoxelDef *def = new FVoxelDef; def->Voxel = voxeldata; diff --git a/src/r_data/voxels.h b/src/r_data/voxels.h index 90b57e0af..9dce7820b 100644 --- a/src/r_data/voxels.h +++ b/src/r_data/voxels.h @@ -42,6 +42,7 @@ struct FVoxel FVoxel(); ~FVoxel(); void Remap(); + void RemovePalette(); }; struct FVoxelDef diff --git a/src/r_defs.h b/src/r_defs.h index 5f8272b3d..c2f3e5b34 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -212,10 +212,16 @@ struct secplane_t fixed_t a, b, c, d, ic; + // Returns < 0 : behind; == 0 : on; > 0 : in front + int PointOnSide (fixed_t x, fixed_t y, fixed_t z) const + { + return TMulScale16(a,x, b,y, c,z) + d; + } + // Returns the value of z at (0,0) This is used by the 3D floor code which does not handle slopes fixed_t Zat0 () const { - return ic < 0? d:-d; + return ic < 0 ? d : -d; } // Returns the value of z at (x,y) @@ -744,6 +750,7 @@ enum WALLF_CLIP_MIDTEX = 16, // Like the line counterpart, but only for this side. WALLF_WRAP_MIDTEX = 32, // Like the line counterpart, but only for this side. WALLF_POLYOBJ = 64, // This wall belongs to a polyobject. + WALLF_LIGHT_FOG = 128, // This wall's Light is used even in fog. }; struct side_t @@ -903,7 +910,7 @@ struct line_t slopetype_t slopetype; // To aid move clipping. sector_t *frontsector, *backsector; int validcount; // if == validcount, already checked - int locknumber; // [Dusk] lock number for special + int locknumber; // [Dusk] lock number for special }; // phares 3/14/98 diff --git a/src/r_main.cpp b/src/r_main.cpp index 80edd18d6..f4ba8c6e5 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -762,7 +762,7 @@ void R_RenderActorView (AActor *actor, bool dontmaplines) MaskedCycles.Reset(); WallScanCycles.Reset(); - fakeActive = 0; // kg3D - reset fake floor idicator + fakeActive = 0; // kg3D - reset fake floor indicator R_3D_ResetClip(); // reset clips (floor/ceiling) R_SetupBuffer (); diff --git a/src/r_plane.cpp b/src/r_plane.cpp index 0e8237cb6..8bf544455 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -492,13 +492,39 @@ void R_ClearPlanes (bool fullclear) { int i, max; - // kg3D - we can't just clear planes if there are fake planes - if(!fullclear && fakeActive) return; - - max = fullclear ? MAXVISPLANES : MAXVISPLANES-1; - for (i = 0; i <= max; i++) // new code -- killough - for (*freehead = visplanes[i], visplanes[i] = NULL; *freehead; ) - freehead = &(*freehead)->next; + // Don't clear fake planes if not doing a full clear. + if (!fullclear) + { + for (i = 0; i <= MAXVISPLANES-1; i++) // new code -- killough + { + for (visplane_t **probe = &visplanes[i]; *probe != NULL; ) + { + if ((*probe)->sky < 0) + { // fake: move past it + probe = &(*probe)->next; + } + else + { // not fake: move to freelist + visplane_t *vis = *probe; + *freehead = vis; + *probe = vis->next; + vis->next = NULL; + freehead = &vis->next; + } + } + } + } + else + { + max = fullclear ? MAXVISPLANES : MAXVISPLANES-1; + for (i = 0; i <= MAXVISPLANES; i++) // new code -- killough + { + for (*freehead = visplanes[i], visplanes[i] = NULL; *freehead; ) + { + freehead = &(*freehead)->next; + } + } + } if (fullclear) { @@ -597,7 +623,7 @@ visplane_t *R_FindPlane (const secplane_t &height, FTextureID picnum, int lightl skybox = NULL; alpha = FRACUNIT; } - + // New visplane algorithm uses hash table -- killough hash = isskybox ? MAXVISPLANES : visplane_hash (picnum.GetIndex(), lightlevel, height); @@ -1002,15 +1028,15 @@ static void R_DrawSkyStriped (visplane_t *pl) CVAR (Bool, tilt, false, 0); //CVAR (Int, pa, 0, 0) -void R_DrawPlanes () +int R_DrawPlanes () { visplane_t *pl; int i; - int vpcount; + int vpcount = 0; ds_color = 3; - for (i = vpcount = 0; i < MAXVISPLANES; i++) + for (i = 0; i < MAXVISPLANES; i++) { for (pl = visplanes[i]; pl; pl = pl->next) { @@ -1024,6 +1050,7 @@ void R_DrawPlanes () } } } + return vpcount; } // kg3D - draw all visplanes with "height" @@ -1329,13 +1356,12 @@ void R_DrawSkyBoxes () for (*freehead = visplanes[MAXVISPLANES], visplanes[MAXVISPLANES] = NULL; *freehead; ) freehead = &(*freehead)->next; - } ADD_STAT(skyboxes) { FString out; - out.Format (out, "%d skybox planes", numskyboxes); + out.Format ("%d skybox planes", numskyboxes); return out; } @@ -1671,7 +1697,7 @@ void R_DrawTiltedPlane (visplane_t *pl, fixed_t alpha, bool additive, bool maske plane_sz[0] = -plane_sz[0]; } - planelightfloat = (r_TiltVisibility * lxscale * lyscale) / (float)(abs(pl->height.ZatPoint (viewx, viewy) - viewz)); + planelightfloat = (r_TiltVisibility * lxscale * lyscale) / (fabs(pl->height.ZatPoint(FIXED2DBL(viewx), FIXED2DBL(viewy)) - FIXED2DBL(viewz))) / 65536.0; if (pl->height.c > 0) planelightfloat = -planelightfloat; diff --git a/src/r_plane.h b/src/r_plane.h index 6bcb36a19..7ad711bcc 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -87,7 +87,7 @@ void R_InitPlanes (); void R_DeinitPlanes (); void R_ClearPlanes (bool fullclear); -void R_DrawPlanes (); +int R_DrawPlanes (); void R_DrawSkyBoxes (); void R_DrawSkyPlane (visplane_t *pl); void R_DrawNormalPlane (visplane_t *pl, fixed_t alpha, bool additive, bool masked); diff --git a/src/r_polymost.cpp b/src/r_polymost.cpp index 92cec1a6e..e7963faa2 100644 --- a/src/r_polymost.cpp +++ b/src/r_polymost.cpp @@ -1430,7 +1430,7 @@ void RP_Subsector (subsector_t *sub) frontsector->ceiling_yscale, frontsector->ceiling_angle + frontsector->base_ceiling_angle, frontsector->sky, - frontsector->CeilingSkyBox + frontsector->CeilingSkyBox, ) : NULL;*/ basecolormap = frontsector->ColorMap; diff --git a/src/r_segs.cpp b/src/r_segs.cpp index bfeab506a..d050d8c3b 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -378,12 +378,12 @@ void R_RenderMaskedSegRange (drawseg_t *ds, int x1, int x2) goto clearfog; } - if ((fake3D & FAKE3D_CLIPBOTTOM) && textop <= sclipBottom - viewz) + if ((fake3D & FAKE3D_CLIPBOTTOM) && textop < sclipBottom - viewz) { notrelevant = true; goto clearfog; } - if ((fake3D & FAKE3D_CLIPTOP) && textop - texheight >= sclipTop - viewz) + if ((fake3D & FAKE3D_CLIPTOP) && textop - texheight > sclipTop - viewz) { notrelevant = true; goto clearfog; @@ -1295,37 +1295,67 @@ static void call_wallscan(int x1, int x2, short *uwal, short *dwal, fixed_t *swa void wallscan_np2(int x1, int x2, short *uwal, short *dwal, fixed_t *swal, fixed_t *lwal, fixed_t yrepeat, fixed_t top, fixed_t bot, bool mask) { - short *up = uwal; - - if (r_np2) + if (!r_np2) + { + call_wallscan(x1, x2, uwal, dwal, swal, lwal, yrepeat, mask); + } + else { short most1[MAXWIDTH], most2[MAXWIDTH], most3[MAXWIDTH]; - short *down; + short *up, *down; fixed_t texheight = rw_pic->GetHeight() << FRACBITS; fixed_t scaledtexheight = FixedDiv(texheight, yrepeat); - fixed_t partition = top - (top - FixedDiv(dc_texturemid, yrepeat) - viewz) % scaledtexheight; + fixed_t partition; - down = most1; - - dc_texturemid = FixedMul(partition - viewz, yrepeat) + texheight; - while (partition > bot) - { - int j = OWallMost(most3, partition - viewz); - if (j != 3) + if (yrepeat >= 0) + { // normal orientation: draw strips from top to bottom + partition = top - (top - FixedDiv(dc_texturemid, yrepeat) - viewz) % scaledtexheight; + up = uwal; + down = most1; + dc_texturemid = FixedMul(partition - viewz, yrepeat) + texheight; + while (partition > bot) { - for (int j = x1; j <= x2; ++j) + int j = OWallMost(most3, partition - viewz); + if (j != 3) { - down[j] = clamp (most3[j], up[j], dwal[j]); + for (int j = x1; j <= x2; ++j) + { + down[j] = clamp(most3[j], up[j], dwal[j]); + } + call_wallscan(x1, x2, up, down, swal, lwal, yrepeat, mask); + up = down; + down = (down == most1) ? most2 : most1; } - call_wallscan(x1, x2, up, down, swal, lwal, yrepeat, mask); - up = down; - down = (down == most1) ? most2 : most1; - } - partition -= scaledtexheight; - dc_texturemid -= texheight; - } + partition -= scaledtexheight; + dc_texturemid -= texheight; + } + call_wallscan(x1, x2, up, dwal, swal, lwal, yrepeat, mask); + } + else + { // upside down: draw strips from bottom to top + partition = bot - (bot - FixedDiv(dc_texturemid, yrepeat) - viewz) % scaledtexheight; + up = most1; + down = dwal; + dc_texturemid = FixedMul(partition - viewz, yrepeat) + texheight; + while (partition < top) + { + int j = OWallMost(most3, partition - viewz); + if (j != 12) + { + for (int j = x1; j <= x2; ++j) + { + up[j] = clamp(most3[j], uwal[j], down[j]); + } + call_wallscan(x1, x2, up, down, swal, lwal, yrepeat, mask); + down = up; + up = (up == most1) ? most2 : most1; + } + partition -= scaledtexheight; + dc_texturemid -= texheight; + } + call_wallscan(x1, x2, uwal, down, swal, lwal, yrepeat, mask); + } } - call_wallscan(x1, x2, up, dwal, swal, lwal, yrepeat, mask); } static void wallscan_np2_ds(drawseg_t *ds, int x1, int x2, short *uwal, short *dwal, fixed_t *swal, fixed_t *lwal, fixed_t yrepeat) @@ -1820,6 +1850,10 @@ void R_RenderSegLoop () { rw_offset = rw_offset_mid; } + if (xscale < 0) + { + rw_offset = -rw_offset; + } if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) { wallscan_np2(x1, x2-1, walltop, wallbottom, swall, lwall, yscale, MAX(rw_frontcz1, rw_frontcz2), MIN(rw_frontfz1, rw_frontfz2), false); @@ -1859,6 +1893,10 @@ void R_RenderSegLoop () { rw_offset = rw_offset_top; } + if (xscale < 0) + { + rw_offset = -rw_offset; + } if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) { wallscan_np2(x1, x2-1, walltop, wallupper, swall, lwall, yscale, MAX(rw_frontcz1, rw_frontcz2), MIN(rw_backcz1, rw_backcz2), false); @@ -1901,6 +1939,10 @@ void R_RenderSegLoop () { rw_offset = rw_offset_bottom; } + if (xscale < 0) + { + rw_offset = -rw_offset; + } if (rw_pic->GetHeight() != 1 << rw_pic->HeightBits) { wallscan_np2(x1, x2-1, walllower, wallbottom, swall, lwall, yscale, MAX(rw_backfz1, rw_backfz2), MIN(rw_frontfz1, rw_frontfz2), false); @@ -1951,16 +1993,31 @@ void R_NewWall (bool needlights) rw_midtexturescalex = sidedef->GetTextureXScale(side_t::mid); rw_midtexturescaley = sidedef->GetTextureYScale(side_t::mid); yrepeat = FixedMul(midtexture->yScale, rw_midtexturescaley); - if (linedef->flags & ML_DONTPEGBOTTOM) - { // bottom of texture at bottom - rw_midtexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::floor) - viewz, yrepeat) + (midtexture->GetHeight() << FRACBITS); + if (yrepeat >= 0) + { // normal orientation + if (linedef->flags & ML_DONTPEGBOTTOM) + { // bottom of texture at bottom + rw_midtexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::floor) - viewz, yrepeat) + (midtexture->GetHeight() << FRACBITS); + } + else + { // top of texture at top + rw_midtexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat); + if (rowoffset < 0 && midtexture != NULL) + { + rowoffset += midtexture->GetHeight() << FRACBITS; + } + } } else - { // top of texture at top - rw_midtexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat); - if (rowoffset < 0 && midtexture != NULL) - { - rowoffset += midtexture->GetHeight() << FRACBITS; + { // upside down + rowoffset = -rowoffset; + if (linedef->flags & ML_DONTPEGBOTTOM) + { // top of texture at bottom + rw_midtexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::floor) - viewz, yrepeat); + } + else + { // bottom of texture at top + rw_midtexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat) + (midtexture->GetHeight() << FRACBITS); } } if (midtexture->bWorldPanning) @@ -2096,17 +2153,32 @@ void R_NewWall (bool needlights) rw_toptexturescalex = sidedef->GetTextureXScale(side_t::top); rw_toptexturescaley = sidedef->GetTextureYScale(side_t::top); yrepeat = FixedMul(toptexture->yScale, rw_toptexturescaley); - if (linedef->flags & ML_DONTPEGTOP) - { // top of texture at top - rw_toptexturemid = MulScale16 (frontsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat); - if (rowoffset < 0 && toptexture != NULL) - { - rowoffset += toptexture->GetHeight() << FRACBITS; + if (yrepeat >= 0) + { // normal orientation + if (linedef->flags & ML_DONTPEGTOP) + { // top of texture at top + rw_toptexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat); + if (rowoffset < 0 && toptexture != NULL) + { + rowoffset += toptexture->GetHeight() << FRACBITS; + } + } + else + { // bottom of texture at bottom + rw_toptexturemid = MulScale16(backsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat) + (toptexture->GetHeight() << FRACBITS); } } else - { // bottom of texture at bottom - rw_toptexturemid = MulScale16(backsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat) + (toptexture->GetHeight() << FRACBITS); + { // upside down + rowoffset = -rowoffset; + if (linedef->flags & ML_DONTPEGTOP) + { // bottom of texture at top + rw_toptexturemid = MulScale16(frontsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat) + (toptexture->GetHeight() << FRACBITS); + } + else + { // top of texture at bottom + rw_toptexturemid = MulScale16(backsector->GetPlaneTexZ(sector_t::ceiling) - viewz, yrepeat); + } } if (toptexture->bWorldPanning) { @@ -2126,25 +2198,40 @@ void R_NewWall (bool needlights) rw_bottomtexturescalex = sidedef->GetTextureXScale(side_t::bottom); rw_bottomtexturescaley = sidedef->GetTextureYScale(side_t::bottom); yrepeat = FixedMul(bottomtexture->yScale, rw_bottomtexturescaley); - if (linedef->flags & ML_DONTPEGBOTTOM) - { // bottom of texture at bottom - rw_bottomtexturemid = rw_frontlowertop; + if (yrepeat >= 0) + { // normal orientation + if (linedef->flags & ML_DONTPEGBOTTOM) + { // bottom of texture at bottom + rw_bottomtexturemid = MulScale16(rw_frontlowertop - viewz, yrepeat); + } + else + { // top of texture at top + rw_bottomtexturemid = MulScale16(backsector->GetPlaneTexZ(sector_t::floor) - viewz, yrepeat); + if (rowoffset < 0 && bottomtexture != NULL) + { + rowoffset += bottomtexture->GetHeight() << FRACBITS; + } + } } else - { // top of texture at top - rw_bottomtexturemid = backsector->GetPlaneTexZ(sector_t::floor); - if (rowoffset < 0 && bottomtexture != NULL) - { - rowoffset += bottomtexture->GetHeight() << FRACBITS; + { // upside down + rowoffset = -rowoffset; + if (linedef->flags & ML_DONTPEGBOTTOM) + { // top of texture at bottom + rw_bottomtexturemid = MulScale16(rw_frontlowertop - viewz, yrepeat); + } + else + { // bottom of texture at top + rw_bottomtexturemid = MulScale16(backsector->GetPlaneTexZ(sector_t::floor) - viewz, yrepeat) + (bottomtexture->GetHeight() << FRACBITS); } } if (bottomtexture->bWorldPanning) { - rw_bottomtexturemid = MulScale16(rw_bottomtexturemid - viewz + rowoffset, yrepeat); + rw_bottomtexturemid += MulScale16(rowoffset, yrepeat); } else { - rw_bottomtexturemid = MulScale16(rw_bottomtexturemid - viewz, yrepeat) + rowoffset; + rw_bottomtexturemid += rowoffset; } } } @@ -2155,11 +2242,22 @@ void R_NewWall (bool needlights) // killough 3/7/98: add deep water check if (frontsector->GetHeightSec() == NULL) { - if (frontsector->floorplane.ZatPoint (viewx, viewy) >= viewz) // above view plane + int planeside; + + planeside = frontsector->floorplane.PointOnSide(viewx, viewy, viewz); + if (frontsector->floorplane.c < 0) // 3D floors have the floor backwards + planeside = -planeside; + if (planeside <= 0) // above view plane markfloor = false; - if (frontsector->ceilingplane.ZatPoint (viewx, viewy) <= viewz && - frontsector->GetTexture(sector_t::ceiling) != skyflatnum) // below view plane - markceiling = false; + + if (frontsector->GetTexture(sector_t::ceiling) != skyflatnum) + { + planeside = frontsector->ceilingplane.PointOnSide(viewx, viewy, viewz); + if (frontsector->ceilingplane.c > 0) // 3D floors have the ceiling backwards + planeside = -planeside; + if (planeside <= 0) // below view plane + markceiling = false; + } } FTexture *midtex = TexMan(sidedef->GetTexture(side_t::mid), true); @@ -2307,13 +2405,13 @@ void R_StoreWallRange (int start, int stop) ds_p->silhouette = 0; if (rw_frontfz1 > rw_backfz1 || rw_frontfz2 > rw_backfz2 || - backsector->floorplane.ZatPoint (viewx, viewy) > viewz) + backsector->floorplane.PointOnSide(viewx, viewy, viewz) < 0) { ds_p->silhouette = SIL_BOTTOM; } if (rw_frontcz1 < rw_backcz1 || rw_frontcz2 < rw_backcz2 || - backsector->ceilingplane.ZatPoint (viewx, viewy) < viewz) + backsector->ceilingplane.PointOnSide(viewx, viewy, viewz) < 0) { ds_p->silhouette |= SIL_TOP; } @@ -2427,7 +2525,20 @@ void R_StoreWallRange (int start, int stop) } ds_p->light = rw_light; ds_p->lightstep = rw_lightstep; - ds_p->shade = wallshade; + + // Masked midtextures should get the light level from the sector they reference, + // not from the current subsector, which is what the current wallshade value + // comes from. We make an exeption for polyobjects, however, since their "home" + // sector should be whichever one they move into. + if (curline->sidedef->Flags & WALLF_POLYOBJ) + { + ds_p->shade = wallshade; + } + else + { + ds_p->shade = LIGHT2SHADE(curline->sidedef->GetLightLevel(foggy, curline->frontsector->lightlevel) + + r_actualextralight); + } if (ds_p->bFogBoundary || ds_p->maskedtexturecol != -1) { diff --git a/src/r_sky.cpp b/src/r_sky.cpp index d93ee59f2..993456a66 100644 --- a/src/r_sky.cpp +++ b/src/r_sky.cpp @@ -69,8 +69,8 @@ void R_InitSkyMap () int skyheight; FTexture *skytex1, *skytex2; - skytex1 = TexMan[sky1texture]; - skytex2 = TexMan[sky2texture]; + skytex1 = TexMan(sky1texture, true); + skytex2 = TexMan(sky2texture, true); if (skytex1 == NULL) return; @@ -107,9 +107,8 @@ void R_InitSkyMap () } else if (skyheight > 200) { - skytexturemid = (200 - skyheight) << FRACBITS; + skytexturemid = FixedMul((200 - skyheight) << FRACBITS, skytex1->yScale); } - skytexturemid = FixedMul(skytexturemid, skytex1->yScale); if (viewwidth != 0 && viewheight != 0) { diff --git a/src/r_things.cpp b/src/r_things.cpp index 74c32ba0f..9b52beb22 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -140,6 +140,7 @@ vissprite_t **firstvissprite; vissprite_t **vissprite_p; vissprite_t **lastvissprite; int newvissprite; +bool DrewAVoxel; static vissprite_t **spritesorter; static int spritesortersize = 0; @@ -188,6 +189,7 @@ void R_DeinitSprites() void R_ClearSprites (void) { vissprite_p = firstvissprite; + DrewAVoxel = false; } @@ -431,13 +433,18 @@ void R_DrawVisVoxel(vissprite_t *spr, int minslabz, int maxslabz, short *cliptop { R_CheckOffscreenBuffer(RenderTarget->GetWidth(), RenderTarget->GetHeight(), !!(flags & DVF_SPANSONLY)); } + if (spr->bInMirror) + { + flags |= DVF_MIRRORED; + } // Render the voxel, either directly to the screen or offscreen. - R_DrawVoxel(spr->gx, spr->gy, spr->gz, spr->angle, spr->xscale, spr->yscale, spr->voxel, spr->Style.colormap, cliptop, clipbot, + R_DrawVoxel(spr->vx, spr->vy, spr->vz, spr->vang, spr->gx, spr->gy, spr->gz, spr->angle, + spr->xscale, spr->yscale, spr->voxel, spr->Style.colormap, cliptop, clipbot, minslabz, maxslabz, flags); // Blend the voxel, if that's what we need to do. - if (flags != 0) + if ((flags & ~DVF_MIRRORED) != 0) { for (int x = 0; x < viewwidth; ++x) { @@ -530,6 +537,14 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor tex = NULL; voxel = NULL; + int spritenum = thing->sprite; + fixed_t spritescaleX = thing->scaleX; + fixed_t spritescaleY = thing->scaleY; + if (thing->player != NULL) + { + P_CheckPlayerSprite(thing, spritenum, spritescaleX, spritescaleY); + } + if (thing->picnum.isValid()) { picnum = thing->picnum; @@ -564,13 +579,13 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor { // decide which texture to use for the sprite #ifdef RANGECHECK - if ((unsigned)thing->sprite >= (unsigned)sprites.Size ()) + if (spritenum >= (signed)sprites.Size () || spritenum < 0) { - DPrintf ("R_ProjectSprite: invalid sprite number %i\n", thing->sprite); + DPrintf ("R_ProjectSprite: invalid sprite number %u\n", spritenum); return; } #endif - spritedef_t *sprdef = &sprites[thing->sprite]; + spritedef_t *sprdef = &sprites[spritenum]; if (thing->frame >= sprdef->numframes) { // If there are no frames at all for this sprite, don't draw it. @@ -631,13 +646,13 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor // [RH] Added scaling int scaled_to = tex->GetScaledTopOffset(); int scaled_bo = scaled_to - tex->GetScaledHeight(); - gzt = fz + thing->scaleY * scaled_to; - gzb = fz + thing->scaleY * scaled_bo; + gzt = fz + spritescaleY * scaled_to; + gzb = fz + spritescaleY * scaled_bo; } else { - xscale = FixedMul(thing->scaleX, voxel->Scale); - yscale = FixedMul(thing->scaleY, voxel->Scale); + xscale = FixedMul(spritescaleX, voxel->Scale); + yscale = FixedMul(spritescaleY, voxel->Scale); gzt = fz + MulScale8(yscale, voxel->Voxel->Mips[0].PivotZ) - thing->floorclip; gzb = fz + MulScale8(yscale, voxel->Voxel->Mips[0].PivotZ - (voxel->Voxel->Mips[0].SizeZ << 8)); if (gzt <= gzb) @@ -689,7 +704,7 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor } // calculate edges of the shape - const fixed_t thingxscalemul = DivScale16(thing->scaleX, tex->xScale); + const fixed_t thingxscalemul = DivScale16(spritescaleX, tex->xScale); tx -= (flip ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul; x1 = centerx + MulScale32 (tx, xscale); @@ -705,11 +720,11 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor if ((x2 < WindowLeft || x2 <= x1)) return; - xscale = FixedDiv(FixedMul(thing->scaleX, xscale), tex->xScale); + xscale = FixedDiv(FixedMul(spritescaleX, xscale), tex->xScale); iscale = (tex->GetWidth() << FRACBITS) / (x2 - x1); x2--; - fixed_t yscale = SafeDivScale16(thing->scaleY, tex->yScale); + fixed_t yscale = SafeDivScale16(spritescaleY, tex->yScale); // store information in a vissprite vis = R_NewVisSprite(); @@ -759,10 +774,10 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor vis->angle -= angle_t(ang * (4294967296.f / 360)); } - // These are irrelevant for voxels. - vis->texturemid = 0x1CEDBEEF; - vis->startfrac = 0x1CEDBEEF; - vis->xiscale = 0x1CEDBEEF; + vis->vx = viewx; + vis->vy = viewy; + vis->vz = viewz; + vis->vang = viewangle; } // killough 3/27/98: save sector for special clipping later @@ -776,6 +791,8 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor vis->gz = fz; vis->gzb = gzb; // [RH] use gzb, not thing->z vis->gzt = gzt; // killough 3/27/98 + vis->deltax = fx - viewx; + vis->deltay = fy - viewy; vis->renderflags = thing->renderflags; if(thing->flags5 & MF5_BRIGHT) vis->renderflags |= RF_FULLBRIGHT; // kg3D vis->Style.RenderStyle = thing->RenderStyle; @@ -786,11 +803,13 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor vis->fakefloor = fakefloor; vis->fakeceiling = fakeceiling; vis->ColormapNum = 0; + vis->bInMirror = MirrorFlags & RF_XFLIP; if (voxel != NULL) { vis->voxel = voxel->Voxel; vis->bIsVoxel = true; + DrewAVoxel = true; } else { @@ -1111,7 +1130,7 @@ void R_DrawPSprite (pspdef_t* psp, int pspnum, AActor *owner, fixed_t sx, fixed_ } // If the main colormap has fixed lights, and this sprite is being drawn with that // colormap, disable acceleration so that the lights can remain fixed. - if (!noaccel && + if (!noaccel && realfixedcolormap == NULL && NormalLightHasFixedLights && mybasecolormap == &NormalLight && vis->pic->UseBasePalette()) { @@ -1323,11 +1342,22 @@ void R_DrawRemainingPlayerSprites() // gain compared to the old function. // // Sort vissprites by depth, far to near + +// This is the standard version, which does a simple test based on depth. static bool sv_compare(vissprite_t *a, vissprite_t *b) { return a->idepth > b->idepth; } +// This is an alternate version, for when one or more voxel is in view. +// It does a 2D distance test based on whichever one is furthest from +// the viewpoint. +static bool sv_compare2d(vissprite_t *a, vissprite_t *b) +{ + return TVector2(a->deltax, a->deltay).LengthSquared() < + TVector2(b->deltax, b->deltay).LengthSquared(); +} + #if 0 static drawseg_t **drawsegsorter; static int drawsegsortersize = 0; @@ -1896,6 +1926,16 @@ void R_DrawSprite (vissprite_t *spr) return; } } + // Add everything outside the left and right edges to the clipping array + // for R_DrawVisVoxel(). + if (x1 > 0) + { + clearbufshort(cliptop, x1, viewheight); + } + if (x2 < viewwidth - 1) + { + clearbufshort(cliptop + x2 + 1, viewwidth - x2 - 1, viewheight); + } int minvoxely = spr->gzt <= hzt ? 0 : (spr->gzt - hzt) / spr->yscale; int maxvoxely = spr->gzb > hzb ? INT_MAX : (spr->gzt - hzb) / spr->yscale; R_DrawVisVoxel(spr, minvoxely, maxvoxely, cliptop, clipbot); @@ -1948,7 +1988,8 @@ void R_DrawHeightPlanes(fixed_t height); // kg3D - fake planes void R_DrawMasked (void) { - R_SortVisSprites (sv_compare, firstvissprite - vissprites); + DrewAVoxel = true; // TESTME + R_SortVisSprites (DrewAVoxel ? sv_compare2d : sv_compare, firstvissprite - vissprites); if (height_top == NULL) { // kg3D - no visible 3D floors, normal rendering @@ -2227,7 +2268,8 @@ void R_DrawParticle (vissprite_t *vis) extern fixed_t baseyaspectmul; -void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t dasprang, +void R_DrawVoxel(fixed_t globalposx, fixed_t globalposy, fixed_t globalposz, angle_t viewang, + fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t dasprang, fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj, lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags) { @@ -2243,12 +2285,14 @@ void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t daspran const int nytooclose = centerxwide * 2100, nytoofar = 32768*32768 - 1048576; const int xdimenscale = Scale(centerxwide, yaspectmul, 160); - const fixed_t globalposx = viewx >> 12; - const fixed_t globalposy = -viewy >> 12; - const fixed_t globalposz = -viewz >> 8; const double centerxwide_f = centerxwide; const double centerxwidebig_f = centerxwide_f * 65536*65536*8; + // Convert to Build's coordinate system. + globalposx = globalposx >> 12; + globalposy = -globalposy >> 12; + globalposz = -globalposz >> 8; + dasprx = dasprx >> 12; daspry = -daspry >> 12; dasprz = -dasprz >> 8; @@ -2258,20 +2302,19 @@ void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t daspran daxscale = daxscale / (0xC000 >> 6); dayscale = dayscale / (0xC000 >> 6); - cosang = viewcos >> 2; - sinang = -viewsin >> 2; + cosang = finecosine[viewang >> ANGLETOFINESHIFT] >> 2; + sinang = -finesine[viewang >> ANGLETOFINESHIFT] >> 2; sprcosang = finecosine[dasprang >> ANGLETOFINESHIFT] >> 2; sprsinang = -finesine[dasprang >> ANGLETOFINESHIFT] >> 2; R_SetupDrawSlab(colormap); // Select mip level - i = abs(DMulScale8(dasprx - globalposx, viewcos, daspry - globalposy, -viewsin)); + i = abs(DMulScale6(dasprx - globalposx, cosang, daspry - globalposy, sinang)); i = DivScale6(i, MIN(daxscale, dayscale)); j = FocalLengthX >> 3; - for (k = 0; k < voxobj->NumMips; ++k) + for (k = 0; i >= j && k < voxobj->NumMips; ++k) { - if (i < j) { break; } i >>= 1; } if (k >= voxobj->NumMips) k = voxobj->NumMips - 1; @@ -2403,6 +2446,13 @@ void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t daspran if (rx > viewwidth) rx = viewwidth; if (rx <= lx) continue; + if (flags & DVF_MIRRORED) + { + int t = viewwidth - lx; + lx = viewwidth - rx; + rx = t; + } + fixed_t l1 = xs_RoundToInt(centerxwidebig_f / (ny - yoff)); fixed_t l2 = xs_RoundToInt(centerxwidebig_f / (ny + yoff)); for (; voxptr < voxend; voxptr = (kvxslab_t *)((BYTE *)voxptr + voxptr->zleng + 3)) diff --git a/src/r_things.h b/src/r_things.h index 92428b639..2219eee20 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -35,25 +35,37 @@ struct vissprite_t fixed_t gx, gy, gz; // origin in world coordinates angle_t angle; fixed_t gzb, gzt; // global bottom / top for silhouette clipping - fixed_t startfrac; // horizontal position of x1 fixed_t xscale, yscale; - fixed_t xiscale; // negative if flipped fixed_t depth; fixed_t idepth; // 1/z - fixed_t texturemid; + fixed_t deltax, deltay; DWORD FillColor; + fixed_t floorclip; + union + { + // Used by regular sprites + struct + { + FTexture *pic; + fixed_t texturemid; + fixed_t startfrac; // horizontal position of x1 + fixed_t xiscale; // negative if flipped + }; + // Used by voxels + struct + { + struct FVoxel *voxel; + fixed_t vx, vy, vz; // view origin + angle_t vang; // view angle + }; + }; sector_t *heightsec; // killough 3/27/98: height sector for underwater/fake ceiling sector_t *sector; // [RH] sector this sprite is in F3DFloor *fakefloor; F3DFloor *fakeceiling; - fixed_t floorclip; - union - { - FTexture *pic; - struct FVoxel *voxel; - }; BYTE bIsVoxel:1; // [RH] Use voxel instead of pic BYTE bSplitSprite:1; // [RH] Sprite was split by a drawseg + BYTE bInMirror:1; // [RH] Sprite is "inside" a mirror BYTE FakeFlatStat; // [RH] which side of fake/floor ceiling sprite is on BYTE ColormapNum; // Which colormap is rendered (needed for shaded drawer) short renderflags; @@ -102,9 +114,10 @@ void R_DrawRemainingPlayerSprites (); void R_CheckOffscreenBuffer(int width, int height, bool spansonly); -enum { DVF_OFFSCREEN = 1, DVF_SPANSONLY = 2 }; +enum { DVF_OFFSCREEN = 1, DVF_SPANSONLY = 2, DVF_MIRRORED = 4 }; -void R_DrawVoxel(fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t dasprang, +void R_DrawVoxel(fixed_t viewx, fixed_t viewy, fixed_t viewz, angle_t viewangle, + fixed_t dasprx, fixed_t daspry, fixed_t dasprz, angle_t dasprang, fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj, lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags); diff --git a/src/r_utility.cpp b/src/r_utility.cpp index c91da86a5..ffc10380a 100644 --- a/src/r_utility.cpp +++ b/src/r_utility.cpp @@ -460,7 +460,7 @@ void R_SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight) void R_ExecuteSetViewSize () { setsizeneeded = false; - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); R_SetWindow (setblocks, SCREENWIDTH, SCREENHEIGHT, ST_Y); @@ -849,15 +849,17 @@ void R_SetupFrame (AActor *actor) TArray &lightlist = viewsector->e->XFloor.lightlist; if (lightlist.Size() > 0) { - for(unsigned int i=0;ifloorplane.ZatPoint(viewx, viewy); - - if (lightbottom < viewz) + secplane_t *plane; + int viewside; + plane = (i < lightlist.Size()-1) ? &lightlist[i+1].plane : &viewsector->floorplane; + viewside = plane->PointOnSide(viewx, viewy, viewz); + // Reverse the direction of the test if the plane was downward facing. + // We want to know if the view is above it, whatever its orientation may be. + if (plane->c < 0) + viewside = -viewside; + if (viewside > 0) { // 3d floor 'fog' is rendered as a blending value PalEntry blendv = lightlist[i].blend; @@ -874,9 +876,9 @@ void R_SetupFrame (AActor *actor) const sector_t *s = viewsector->GetHeightSec(); if (s != NULL) { - newblend = viewz < s->floorplane.ZatPoint (viewx, viewy) + newblend = s->floorplane.PointOnSide(viewx, viewy, viewz) < 0 ? s->bottommap - : viewz > s->ceilingplane.ZatPoint (viewx, viewy) + : s->ceilingplane.PointOnSide(viewx, viewy, viewz) < 0 ? s->topmap : s->midmap; if (APART(newblend) == 0 && newblend >= numfakecmaps) diff --git a/src/resourcefiles/file_7z.cpp b/src/resourcefiles/file_7z.cpp index 2943d00aa..2f955705e 100644 --- a/src/resourcefiles/file_7z.cpp +++ b/src/resourcefiles/file_7z.cpp @@ -271,7 +271,6 @@ bool F7ZFile::Open(bool quiet) for (DWORD i = 0; i < NumLumps; ++i) { CSzFileItem *file = &Archive->DB.db.Files[i]; - char name[256]; // skip Directories if (file->IsDir) @@ -280,10 +279,9 @@ bool F7ZFile::Open(bool quiet) continue; } - strncpy(name, file->Name, countof(name)-1); - name[countof(name)-1] = 0; + FString name = file->Name; FixPathSeperator(name); - strlwr(name); + name.ToLower(); lump_p->LumpNameSetup(name); lump_p->LumpSize = int(file->Size); diff --git a/src/resourcefiles/file_directory.cpp b/src/resourcefiles/file_directory.cpp index 420b7dde3..e9076ae9b 100644 --- a/src/resourcefiles/file_directory.cpp +++ b/src/resourcefiles/file_directory.cpp @@ -118,7 +118,6 @@ FDirectory::FDirectory(const char * directory) dirname = directory; dirname.ReplaceChars('\\', '/'); if (dirname[dirname.Len()-1] != '/') dirname += '/'; - free((void*)directory); Filename = copystring(dirname); } @@ -336,7 +335,6 @@ FileReader *FDirectoryLump::NewReader() { FString fullpath = Owner->Filename; fullpath += FullName; - printf("%s\n", fullpath.GetChars()); return new FileReader(fullpath); } catch (CRecoverableError &) diff --git a/src/resourcefiles/file_wad.cpp b/src/resourcefiles/file_wad.cpp index a0f04269c..ea8cac15b 100644 --- a/src/resourcefiles/file_wad.cpp +++ b/src/resourcefiles/file_wad.cpp @@ -35,24 +35,267 @@ #include "resourcefile.h" #include "cmdlib.h" +#include "templates.h" #include "v_text.h" #include "w_wad.h" +// Console Doom LZSS wrapper. +class FileReaderLZSS : public FileReaderBase +{ +private: + enum { BUFF_SIZE = 4096, WINDOW_SIZE = 4096, INTERNAL_BUFFER_SIZE = 128 }; + + FileReader &File; + bool SawEOF; + BYTE InBuff[BUFF_SIZE]; + + enum StreamState + { + STREAM_EMPTY, + STREAM_BITS, + STREAM_FLUSH, + STREAM_FINAL + }; + struct + { + StreamState State; + + BYTE *In; + unsigned int AvailIn; + unsigned int InternalOut; + + BYTE CFlags, Bits; + + BYTE Window[WINDOW_SIZE+INTERNAL_BUFFER_SIZE]; + const BYTE *WindowData; + BYTE *InternalBuffer; + } Stream; + + void FillBuffer() + { + if(Stream.AvailIn) + memmove(InBuff, Stream.In, Stream.AvailIn); + + long numread = File.Read(InBuff+Stream.AvailIn, BUFF_SIZE-Stream.AvailIn); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.In = InBuff; + Stream.AvailIn = numread+Stream.AvailIn; + } + + // Reads a flag byte. + void PrepareBlocks() + { + assert(Stream.InternalBuffer == Stream.WindowData); + Stream.CFlags = *Stream.In++; + --Stream.AvailIn; + Stream.Bits = 0xFF; + Stream.State = STREAM_BITS; + } + + // Reads the next chunk in the block. Returns true if successful and + // returns false if it ran out of input data. + bool UncompressBlock() + { + if(Stream.CFlags & 1) + { + // Check to see if we have enough input + if(Stream.AvailIn < 2) + return false; + Stream.AvailIn -= 2; + + WORD pos = BigShort(*(WORD*)Stream.In); + BYTE len = (pos & 0xF)+1; + pos >>= 4; + Stream.In += 2; + if(len == 1) + { + // We've reached the end of the stream. + Stream.State = STREAM_FINAL; + return true; + } + + const BYTE* copyStart = Stream.InternalBuffer-pos-1; + + // Complete overlap: Single byte repeated + if(pos == 0) + memset(Stream.InternalBuffer, *copyStart, len); + // No overlap: One copy + else if(pos >= len) + memcpy(Stream.InternalBuffer, copyStart, len); + else + { + // Partial overlap: Copy in 2 or 3 chunks. + do + { + unsigned int copy = MIN(len, pos+1); + memcpy(Stream.InternalBuffer, copyStart, copy); + Stream.InternalBuffer += copy; + Stream.InternalOut += copy; + len -= copy; + pos += copy; // Increase our position since we can copy twice as much the next round. + } + while(len); + } + + Stream.InternalOut += len; + Stream.InternalBuffer += len; + } + else + { + // Uncompressed byte. + *Stream.InternalBuffer++ = *Stream.In++; + --Stream.AvailIn; + ++Stream.InternalOut; + } + + Stream.CFlags >>= 1; + Stream.Bits >>= 1; + + // If we're done with this block, flush the output + if(Stream.Bits == 0) + Stream.State = STREAM_FLUSH; + + return true; + } + +public: + FileReaderLZSS(FileReader &file) : File(file), SawEOF(false) + { + Stream.State = STREAM_EMPTY; + Stream.WindowData = Stream.InternalBuffer = Stream.Window+WINDOW_SIZE; + Stream.InternalOut = 0; + Stream.AvailIn = 0; + + FillBuffer(); + } + + ~FileReaderLZSS() + { + } + + long Read(void *buffer, long len) + { + + BYTE *Out = (BYTE*)buffer; + long AvailOut = len; + + do + { + while(Stream.AvailIn) + { + if(Stream.State == STREAM_EMPTY) + PrepareBlocks(); + else if(Stream.State == STREAM_BITS && !UncompressBlock()) + break; + else + break; + } + + unsigned int copy = MIN(Stream.InternalOut, AvailOut); + if(copy > 0) + { + memcpy(Out, Stream.WindowData, copy); + Out += copy; + AvailOut -= copy; + + // Slide our window + memmove(Stream.Window, Stream.Window+copy, WINDOW_SIZE+INTERNAL_BUFFER_SIZE-copy); + Stream.InternalBuffer -= copy; + Stream.InternalOut -= copy; + } + + if(Stream.State == STREAM_FINAL) + break; + + if(Stream.InternalOut == 0 && Stream.State == STREAM_FLUSH) + Stream.State = STREAM_EMPTY; + + if(Stream.AvailIn < 2) + FillBuffer(); + } + while(AvailOut && Stream.State != STREAM_FINAL); + + assert(AvailOut == 0); + return (long)(Out - (BYTE*)buffer); + } +}; + +//========================================================================== +// +// Wad Lump (with console doom LZSS support) +// +//========================================================================== + +class FWadFileLump : public FResourceLump +{ +public: + bool Compressed; + int Position; + + int GetFileOffset() { return Position; } + FileReader *GetReader() + { + if(!Compressed) + { + Owner->Reader->Seek(Position, SEEK_SET); + return Owner->Reader; + } + return NULL; + } + int FillCache() + { + if(!Compressed) + { + const char * buffer = Owner->Reader->GetBuffer(); + + if (buffer != NULL) + { + // This is an in-memory file so the cache can point directly to the file's data. + Cache = const_cast(buffer) + Position; + RefCount = -1; + return -1; + } + } + + Owner->Reader->Seek(Position, SEEK_SET); + Cache = new char[LumpSize]; + + if(Compressed) + { + FileReaderLZSS lzss(*Owner->Reader); + lzss.Read(Cache, LumpSize); + } + else + Owner->Reader->Read(Cache, LumpSize); + + RefCount = 1; + return 1; + } +}; + //========================================================================== // // Wad file // //========================================================================== -class FWadFile : public FUncompressedFile +class FWadFile : public FResourceFile { + FWadFileLump *Lumps; + bool IsMarker(int lump, const char *marker); void SetNamespace(const char *startmarker, const char *endmarker, namespace_t space, bool flathack=false); void SkinHack (); public: FWadFile(const char * filename, FileReader *file); + ~FWadFile(); void FindStrifeTeaserVoices (); + FResourceLump *GetLump(int lump) { return &Lumps[lump]; } bool Open(bool quiet); }; @@ -65,11 +308,16 @@ public: // //========================================================================== -FWadFile::FWadFile(const char *filename, FileReader *file) : FUncompressedFile(filename, file) +FWadFile::FWadFile(const char *filename, FileReader *file) : FResourceFile(filename, file) { Lumps = NULL; } +FWadFile::~FWadFile() +{ + delete[] Lumps; +} + //========================================================================== // // Open it @@ -79,33 +327,51 @@ FWadFile::FWadFile(const char *filename, FileReader *file) : FUncompressedFile(f bool FWadFile::Open(bool quiet) { wadinfo_t header; + DWORD InfoTableOfs; + bool isBigEndian = false; // Little endian is assumed until proven otherwise + const long wadSize = Reader->GetLength(); Reader->Read(&header, sizeof(header)); NumLumps = LittleLong(header.NumLumps); - header.InfoTableOfs = LittleLong(header.InfoTableOfs); - + InfoTableOfs = LittleLong(header.InfoTableOfs); + + // Check to see if the little endian interpretation is valid + // This should be sufficient to detect big endian wads. + if (InfoTableOfs + NumLumps*sizeof(wadlump_t) > (unsigned)wadSize) + { + NumLumps = BigLong(header.NumLumps); + InfoTableOfs = BigLong(header.InfoTableOfs); + isBigEndian = true; + } + wadlump_t *fileinfo = new wadlump_t[NumLumps]; - Reader->Seek (header.InfoTableOfs, SEEK_SET); + Reader->Seek (InfoTableOfs, SEEK_SET); Reader->Read (fileinfo, NumLumps * sizeof(wadlump_t)); - Lumps = new FUncompressedLump[NumLumps]; - - if (!quiet) Printf(", %d lumps\n", NumLumps); + Lumps = new FWadFileLump[NumLumps]; for(DWORD i = 0; i < NumLumps; i++) { uppercopy (Lumps[i].Name, fileinfo[i].Name); Lumps[i].Name[8] = 0; + Lumps[i].Compressed = (Lumps[i].Name[0] & 0x80) == 0x80; + Lumps[i].Name[0] &= ~0x80; + Lumps[i].Owner = this; - Lumps[i].Position = LittleLong(fileinfo[i].FilePos); - Lumps[i].LumpSize = LittleLong(fileinfo[i].Size); + Lumps[i].Position = isBigEndian ? BigLong(fileinfo[i].FilePos) : LittleLong(fileinfo[i].FilePos); + Lumps[i].LumpSize = isBigEndian ? BigLong(fileinfo[i].Size) : LittleLong(fileinfo[i].Size); Lumps[i].Namespace = ns_global; Lumps[i].Flags = 0; Lumps[i].FullName = NULL; } - if (!quiet) // don't bother with namespaces here. We won't need them. + delete[] fileinfo; + + if (!quiet) { + Printf(", %d lumps\n", NumLumps); + + // don't bother with namespaces here. We won't need them. SetNamespace("S_START", "S_END", ns_sprites); SetNamespace("F_START", "F_END", ns_flats, true); SetNamespace("C_START", "C_END", ns_colormaps); @@ -116,7 +382,6 @@ bool FWadFile::Open(bool quiet) SetNamespace("VX_START", "VX_END", ns_voxels); SkinHack(); } - delete [] fileinfo; return true; } diff --git a/src/resourcefiles/file_zip.cpp b/src/resourcefiles/file_zip.cpp index e33938144..7e59bd35a 100644 --- a/src/resourcefiles/file_zip.cpp +++ b/src/resourcefiles/file_zip.cpp @@ -210,11 +210,8 @@ bool FZipFile::Open(bool quiet) { FZipCentralDirectoryInfo *zip_fh = (FZipCentralDirectoryInfo *)dirptr; - char name[256]; - int len = LittleShort(zip_fh->NameLength); - strncpy(name, dirptr + sizeof(FZipCentralDirectoryInfo), MIN(len, 255)); - name[len] = 0; + FString name(dirptr + sizeof(FZipCentralDirectoryInfo), len); dirptr += sizeof(FZipCentralDirectoryInfo) + LittleShort(zip_fh->NameLength) + LittleShort(zip_fh->ExtraLength) + @@ -236,7 +233,7 @@ bool FZipFile::Open(bool quiet) zip_fh->Method != METHOD_IMPLODE && zip_fh->Method != METHOD_SHRINK) { - if (!quiet) Printf("\n%s: '%s' uses an unsupported compression algorithm (#%d).\n", Filename, name, zip_fh->Method); + if (!quiet) Printf("\n%s: '%s' uses an unsupported compression algorithm (#%d).\n", Filename, name.GetChars(), zip_fh->Method); skipped++; continue; } @@ -244,13 +241,13 @@ bool FZipFile::Open(bool quiet) zip_fh->Flags = LittleShort(zip_fh->Flags); if (zip_fh->Flags & ZF_ENCRYPTED) { - if (!quiet) Printf("\n%s: '%s' is encrypted. Encryption is not supported.\n", Filename, name); + if (!quiet) Printf("\n%s: '%s' is encrypted. Encryption is not supported.\n", Filename, name.GetChars()); skipped++; continue; } FixPathSeperator(name); - strlwr(name); + name.ToLower(); lump_p->LumpNameSetup(name); lump_p->LumpSize = LittleLong(zip_fh->UncompressedSize); diff --git a/src/resourcefiles/resourcefile.cpp b/src/resourcefiles/resourcefile.cpp index 866605d14..8a4f07fd7 100644 --- a/src/resourcefiles/resourcefile.cpp +++ b/src/resourcefiles/resourcefile.cpp @@ -96,15 +96,10 @@ FResourceLump::~FResourceLump() void FResourceLump::LumpNameSetup(const char *iname) { - char base[256]; const char *lname = strrchr(iname,'/'); lname = (lname == NULL) ? iname : lname + 1; - strcpy(base, lname); - char *dot = strrchr(base, '.'); - if (dot != NULL) - { - *dot = 0; - } + FString base = lname; + base = base.Left(base.LastIndexOf('.')); uppercopy(Name, base); Name[8] = 0; FullName = copystring(iname); diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index f4c343fac..c2d567f29 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -519,6 +519,7 @@ int S_AddSoundLump (const char *logicalname, int lump) newsfx.bUsed = false; newsfx.bSingular = false; newsfx.bTentative = false; + newsfx.bPlayerSilent = false; newsfx.link = sfxinfo_t::NO_LINK; newsfx.Rolloff.RolloffType = ROLLOFF_Doom; newsfx.Rolloff.MinDistance = 0; @@ -1110,10 +1111,14 @@ static void S_AddSNDINFO (int lump) case SI_PlayerSound: { // $playersound FString pclass; - int gender, refid; + int gender, refid, sfxnum; S_ParsePlayerSoundCommon (sc, pclass, gender, refid); - S_AddPlayerSound (pclass, gender, refid, sc.String); + sfxnum = S_AddPlayerSound (pclass, gender, refid, sc.String); + if (0 == stricmp(sc.String, "dsempty")) + { + S_sfx[sfxnum].bPlayerSilent = true; + } } break; @@ -1665,7 +1670,9 @@ static int S_LookupPlayerSound (int classidx, int gender, FSoundID refid) // If we're not done parsing SNDINFO yet, assume that the target sound is valid if (PlayerClassesIsSorted && (sndnum == 0 || - ((S_sfx[sndnum].lumpnum == -1 || S_sfx[sndnum].lumpnum == sfx_empty) && S_sfx[sndnum].link == sfxinfo_t::NO_LINK))) + ((S_sfx[sndnum].lumpnum == -1 || S_sfx[sndnum].lumpnum == sfx_empty) && + S_sfx[sndnum].link == sfxinfo_t::NO_LINK && + !S_sfx[sndnum].bPlayerSilent))) { // This sound is unavailable. if (ingender != 0) { // Try "male" @@ -1790,7 +1797,7 @@ int S_FindSkinnedSound (AActor *actor, FSoundID refid) if (actor != NULL && actor->IsKindOf(RUNTIME_CLASS(APlayerPawn))) { pclass = static_cast(actor)->GetSoundClass (); - if (actor->player != NULL) gender = actor->player->userinfo.gender; + if (actor->player != NULL) gender = actor->player->userinfo.GetGender(); } else { diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 005f01df2..35fcc44e2 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -268,7 +268,7 @@ void S_NoiseDebug (void) } chan = (FSoundChan *)((size_t)chan->PrevChan - myoffsetof(FSoundChan, NextChan)); } - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); } static FString LastLocalSndInfo; @@ -446,8 +446,9 @@ void S_Start () // start new music for the level MusicPaused = false; - // [RH] This is a lot simpler now. - if (!savegamerestore) + // Don't start the music if loading a savegame, because the music is stored there. + // Don't start the music if revisiting a level in a hub for the same reason. + if (!savegamerestore && (level.info == NULL || level.info->snapshot == NULL || !level.info->isValid())) { if (level.cdtrack == 0 || !S_ChangeCDMusic (level.cdtrack, level.cdid)) S_ChangeMusic (level.Music, level.musicorder); @@ -1591,6 +1592,29 @@ void S_RelinkSound (AActor *from, AActor *to) } } + +//========================================================================== +// +// S_ChangeSoundVolume +// +//========================================================================== + +bool S_ChangeSoundVolume(AActor *actor, int channel, float volume) +{ + for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan) + { + if (chan->SourceType == SOURCE_Actor && + chan->Actor == actor && + (chan->EntChannel == channel || (i_compatflags & COMPATF_MAGICSILENCE))) + { + GSnd->ChannelVolume(chan, volume); + chan->Volume = volume; + return true; + } + } + return false; +} + //========================================================================== // // S_GetSoundPlayingInfo @@ -2135,7 +2159,6 @@ void S_StopChannel(FSoundChan *chan) } } - //========================================================================== // // (FArchive &) << (FSoundID &) diff --git a/src/s_sound.h b/src/s_sound.h index 8b0e4ddea..346e51ce1 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -57,6 +57,7 @@ struct sfxinfo_t WORD bUsed:1; WORD bSingular:1; WORD bTentative:1; + WORD bPlayerSilent:1; // This player sound is intentionally silent. int LoopStart; // -1 means no specific loop defined @@ -302,6 +303,9 @@ bool S_GetSoundPlayingInfo (const FPolyObj *poly, int sound_id); bool S_IsActorPlayingSomething (AActor *actor, int channel, int sound_id); +// Change a playing sound's volume +bool S_ChangeSoundVolume(AActor *actor, int channel, float volume); + // Moves all sounds from one mobj to another void S_RelinkSound (AActor *from, AActor *to); diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 437887d87..02ba61125 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -1,4 +1,4 @@ -#define YYCTYPE char +#define YYCTYPE unsigned char #define YYCURSOR cursor #define YYLIMIT limit #define YYMARKER marker diff --git a/src/sdl/crashcatcher.c b/src/sdl/crashcatcher.c index 1dfa43af8..37511f63a 100644 --- a/src/sdl/crashcatcher.c +++ b/src/sdl/crashcatcher.c @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef __linux__ #include diff --git a/src/sdl/hardware.cpp b/src/sdl/hardware.cpp index ef78b4c31..352fe8558 100644 --- a/src/sdl/hardware.cpp +++ b/src/sdl/hardware.cpp @@ -33,6 +33,8 @@ */ #include +#include +#include #include "hardware.h" #include "i_video.h" @@ -175,10 +177,97 @@ void I_ClosestResolution (int *width, int *height, int bits) return; } } -} +} + +//========================================================================== +// +// SetFPSLimit +// +// Initializes an event timer to fire at a rate of /sec. The video +// update will wait for this timer to trigger before updating. +// +// Pass 0 as the limit for unlimited. +// Pass a negative value for the limit to use the value of vid_maxfps. +// +//========================================================================== + +EXTERN_CVAR(Int, vid_maxfps); +EXTERN_CVAR(Bool, cl_capfps); + +#ifndef __APPLE__ +Semaphore FPSLimitSemaphore; + +static void FPSLimitNotify(sigval val) +{ + SEMAPHORE_SIGNAL(FPSLimitSemaphore) +} void I_SetFPSLimit(int limit) { + static sigevent FPSLimitEvent; + static timer_t FPSLimitTimer; + static bool FPSLimitTimerEnabled = false; + static bool EventSetup = false; + if(!EventSetup) + { + EventSetup = true; + FPSLimitEvent.sigev_notify = SIGEV_THREAD; + FPSLimitEvent.sigev_signo = 0; + FPSLimitEvent.sigev_value.sival_int = 0; + FPSLimitEvent.sigev_notify_function = FPSLimitNotify; + FPSLimitEvent.sigev_notify_attributes = NULL; + + SEMAPHORE_INIT(FPSLimitSemaphore, 0, 0) + } + + if (limit < 0) + { + limit = vid_maxfps; + } + // Kill any leftover timer. + if (FPSLimitTimerEnabled) + { + timer_delete(FPSLimitTimer); + FPSLimitTimerEnabled = false; + } + if (limit == 0) + { // no limit + DPrintf("FPS timer disabled\n"); + } + else + { + FPSLimitTimerEnabled = true; + if(timer_create(CLOCK_REALTIME, &FPSLimitEvent, &FPSLimitTimer) == -1) + Printf("Failed to create FPS limitter event\n"); + itimerspec period = { {0, 0}, {0, 0} }; + period.it_value.tv_nsec = period.it_interval.tv_nsec = 1000000000 / limit; + if(timer_settime(FPSLimitTimer, 0, &period, NULL) == -1) + Printf("Failed to set FPS limitter timer\n"); + DPrintf("FPS timer set to %u ms\n", (unsigned int) period.it_interval.tv_nsec / 1000000); + } +} +#else +// So Apple doesn't support POSIX timers and I can't find a good substitute short of +// having Objective-C Cocoa events or something like that. +void I_SetFPSLimit(int limit) +{ +} +#endif + +CUSTOM_CVAR (Int, vid_maxfps, 200, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (vid_maxfps < TICRATE && vid_maxfps != 0) + { + vid_maxfps = TICRATE; + } + else if (vid_maxfps > 1000) + { + vid_maxfps = 1000; + } + else if (cl_capfps == 0) + { + I_SetFPSLimit(vid_maxfps); + } } extern int NewWidth, NewHeight, NewBits, DisplayBits; diff --git a/src/sdl/hardware.h b/src/sdl/hardware.h index 651d3ee2b..6544f1498 100644 --- a/src/sdl/hardware.h +++ b/src/sdl/hardware.h @@ -37,6 +37,35 @@ #include "i_video.h" #include "v_video.h" +// Semaphores +#ifdef __APPLE__ +#include +#include +#include +typedef semaphore_t Semaphore; +#define SEMAPHORE_WAIT(sem) \ + while(semaphore_wait(sem) != KERN_SUCCESS){} +#define SEMAPHORE_SIGNAL(sem) \ + semaphore_signal(sem); +#define SEMAPHORE_INIT(sem, shared, value) \ + semaphore_create(mach_task_self(), &sem, shared, value); +#else +#include +typedef sem_t Semaphore; +#define SEMAPHORE_WAIT(sem) \ + do { \ + while(sem_wait(&sem) != 0); \ + int semValue; \ + sem_getvalue(&sem, &semValue); \ + if(semValue < 1) \ + break; \ + } while(true); +#define SEMAPHORE_SIGNAL(sem) \ + sem_post(&sem); +#define SEMAPHORE_INIT(sem, shared, value) \ + sem_init(&sem, shared, value); +#endif + class IVideo { public: @@ -59,6 +88,7 @@ void I_InitGraphics (); void I_ShutdownGraphics (); void I_CreateRenderer(); +extern Semaphore FPSLimitSemaphore; void I_SetFPSLimit(int limit); extern IVideo *Video; diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index f7ca515d3..afabd0d21 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -38,14 +38,6 @@ #include #endif -#ifdef __APPLE__ -#include -#include -#include -#else -#include -#endif - #include "doomerrors.h" #include @@ -144,11 +136,7 @@ static DWORD BaseTime; static int TicFrozen; // Signal based timer. -#ifdef __APPLE__ -static semaphore_t timerWait; -#else -static sem_t timerWait; -#endif +static Semaphore timerWait; static int tics; static DWORD sig_start, sig_next; @@ -221,12 +209,7 @@ int I_WaitForTicSignaled (int prevtic) while(tics <= prevtic) { -#ifdef __APPLE__ - while(semaphore_wait(timerWait) != KERN_SUCCESS) - ; -#else - while(sem_wait(&timerWait) != 0); -#endif + SEMAPHORE_WAIT(timerWait) } return tics; @@ -276,11 +259,7 @@ void I_HandleAlarm (int sig) tics++; sig_start = SDL_GetTicks(); sig_next = Scale((Scale (sig_start, TICRATE, 1000) + 1), 1000, TICRATE); -#ifdef __APPLE__ - semaphore_signal(timerWait); -#else - sem_post(&timerWait); -#endif + SEMAPHORE_SIGNAL(timerWait) } // @@ -290,12 +269,15 @@ void I_HandleAlarm (int sig) // void I_SelectTimer() { -#ifdef __APPLE__ - semaphore_create(mach_task_self(), &timerWait, 0, 0); -#else - sem_init(&timerWait, 0, 0); -#endif + SEMAPHORE_INIT(timerWait, 0, 0) +#ifndef __sun signal(SIGALRM, I_HandleAlarm); +#else + struct sigaction alrmaction; + sigaction(SIGALRM, NULL, &alrmaction); + alrmaction.sa_handler = I_HandleAlarm; + sigaction(SIGALRM, &alrmaction, NULL); +#endif struct itimerval itv; itv.it_interval.tv_sec = itv.it_value.tv_sec = 0; diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index 26723c583..2f11b8bbd 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -80,6 +80,8 @@ extern SDL_Rect cursorBlit; extern bool GUICapture; EXTERN_CVAR (Float, Gamma) +EXTERN_CVAR (Int, vid_maxfps) +EXTERN_CVAR (Bool, cl_capfps) // PUBLIC DATA DEFINITIONS ------------------------------------------------- @@ -374,6 +376,13 @@ void SDLFB::Update () DrawRateStuff (); +#ifndef __APPLE__ + if(vid_maxfps && !cl_capfps) + { + SEMAPHORE_WAIT(FPSLimitSemaphore) + } +#endif + Buffer = NULL; LockCount = 0; UpdatePending = false; diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index 5abb67e76..133fc2406 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -2053,6 +2053,20 @@ void FMODSoundRenderer::StopChannel(FISoundChannel *chan) } } +//========================================================================== +// +// FMODSoundRenderer :: ChannelVolume +// +//========================================================================== + +void FMODSoundRenderer::ChannelVolume(FISoundChannel *chan, float volume) +{ + if (chan != NULL && chan->SysChannel != NULL) + { + ((FMOD::Channel *)chan->SysChannel)->setVolume(volume); + } +} + //========================================================================== // // FMODSoundRenderer :: GetPosition diff --git a/src/sound/fmodsound.h b/src/sound/fmodsound.h index 99d627e5f..93b70ce87 100644 --- a/src/sound/fmodsound.h +++ b/src/sound/fmodsound.h @@ -33,6 +33,9 @@ public: // Stops a sound channel. void StopChannel (FISoundChannel *chan); + // Changes a channel's volume. + void ChannelVolume (FISoundChannel *chan, float volume); + // Marks a channel's start time without actually playing it. void MarkStartTime (FISoundChannel *chan); diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 576597dc2..3c1134c63 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -373,6 +373,8 @@ protected: int (STACK_ARGS *fluid_settings_setint)(fluid_settings_t *, const char *, int); int (STACK_ARGS *fluid_settings_getstr)(fluid_settings_t *, const char *, char **); int (STACK_ARGS *fluid_settings_getint)(fluid_settings_t *, const char *, int *); + void (STACK_ARGS *fluid_synth_set_reverb_on)(fluid_synth_t *, int); + void (STACK_ARGS *fluid_synth_set_chorus_on)(fluid_synth_t *, int); int (STACK_ARGS *fluid_synth_set_interp_method)(fluid_synth_t *, int, int); int (STACK_ARGS *fluid_synth_set_polyphony)(fluid_synth_t *, int); int (STACK_ARGS *fluid_synth_get_polyphony)(fluid_synth_t *); diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp index 0fe68a25c..c95b0221f 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -150,6 +150,9 @@ public: void StopChannel(FISoundChannel *chan) { } + void ChannelVolume(FISoundChannel *, float) + { + } // Streaming sounds. SoundStream *CreateStream (SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index c905678ad..adce8796b 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -110,6 +110,9 @@ public: // Stops a sound channel. virtual void StopChannel (FISoundChannel *chan) = 0; + // Changes a channel's volume. + virtual void ChannelVolume (FISoundChannel *chan, float volume) = 0; + // Marks a channel's start time without actually playing it. virtual void MarkStartTime (FISoundChannel *chan) = 0; diff --git a/src/sound/music_dumb.cpp b/src/sound/music_dumb.cpp index d0423e256..8405228a3 100644 --- a/src/sound/music_dumb.cpp +++ b/src/sound/music_dumb.cpp @@ -986,6 +986,15 @@ MusInfo *MOD_OpenSong(FILE *file, BYTE *musiccache, int size) duh = dumb_read_asy_quick(f); } } + else if (size >= 8 && + dstart[0] == MAKE_ID('O','K','T','A') && + dstart[1] == MAKE_ID('S','O','N','G')) + { + if ((f = dumb_read_allfile(&filestate, start, file, musiccache, headsize, size))) + { + duh = dumb_read_okt_quick(f); + } + } if ( ! duh ) { diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index 7914aa256..2e86cb2ee 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -50,9 +50,11 @@ #ifdef _WIN32 #ifndef _M_X64 -#define FLUIDSYNTHLIB "fluidsynth.dll" +#define FLUIDSYNTHLIB1 "fluidsynth.dll" +#define FLUIDSYNTHLIB2 "libfluidsynth.dll" #else -#define FLUIDSYNTHLIB "fluidsynth64.dll" +#define FLUIDSYNTHLIB1 "fluidsynth64.dll" +#define FLUIDSYNTHLIB2 "libfluidsynth64.dll" #endif #else #include @@ -105,13 +107,13 @@ CUSTOM_CVAR(Float, fluid_gain, 0.5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CUSTOM_CVAR(Bool, fluid_reverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (currSong != NULL) - currSong->FluidSettingStr("synth.reverb.active", self ? "yes" : "no"); + currSong->FluidSettingInt("synth.reverb.active", self); } CUSTOM_CVAR(Bool, fluid_chorus, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (currSong != NULL) - currSong->FluidSettingStr("synth.chorus.active", self ? "yes" : "no"); + currSong->FluidSettingInt("synth.chorus.active", self); } CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -156,7 +158,7 @@ CUSTOM_CVAR(Int, fluid_threads, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) self = 256; } -CUSTOM_CVAR(Float, fluid_reverb_roomsize, FLUID_REVERB_DEFAULT_ROOMSIZE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_reverb_roomsize, 0.61f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -166,7 +168,7 @@ CUSTOM_CVAR(Float, fluid_reverb_roomsize, FLUID_REVERB_DEFAULT_ROOMSIZE, CVAR_AR currSong->FluidSettingInt("z.reverb-changed", 0); } -CUSTOM_CVAR(Float, fluid_reverb_damping, FLUID_REVERB_DEFAULT_DAMP, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_reverb_damping, 0.23f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -176,7 +178,7 @@ CUSTOM_CVAR(Float, fluid_reverb_damping, FLUID_REVERB_DEFAULT_DAMP, CVAR_ARCHIVE currSong->FluidSettingInt("z.reverb-changed", 0); } -CUSTOM_CVAR(Float, fluid_reverb_width, FLUID_REVERB_DEFAULT_WIDTH, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_reverb_width, 0.76f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -186,7 +188,7 @@ CUSTOM_CVAR(Float, fluid_reverb_width, FLUID_REVERB_DEFAULT_WIDTH, CVAR_ARCHIVE| currSong->FluidSettingInt("z.reverb-changed", 0); } -CUSTOM_CVAR(Float, fluid_reverb_level, FLUID_REVERB_DEFAULT_LEVEL, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_reverb_level, 0.57f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -196,7 +198,7 @@ CUSTOM_CVAR(Float, fluid_reverb_level, FLUID_REVERB_DEFAULT_LEVEL, CVAR_ARCHIVE| currSong->FluidSettingInt("z.reverb-changed", 0); } -CUSTOM_CVAR(Int, fluid_chorus_voices, FLUID_CHORUS_DEFAULT_N, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, fluid_chorus_voices, 3, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -206,7 +208,7 @@ CUSTOM_CVAR(Int, fluid_chorus_voices, FLUID_CHORUS_DEFAULT_N, CVAR_ARCHIVE|CVAR_ currSong->FluidSettingInt("z.chorus-changed", 0); } -CUSTOM_CVAR(Float, fluid_chorus_level, FLUID_CHORUS_DEFAULT_LEVEL, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_chorus_level, 1.2f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -216,7 +218,7 @@ CUSTOM_CVAR(Float, fluid_chorus_level, FLUID_CHORUS_DEFAULT_LEVEL, CVAR_ARCHIVE| currSong->FluidSettingInt("z.chorus-changed", 0); } -CUSTOM_CVAR(Float, fluid_chorus_speed, FLUID_CHORUS_DEFAULT_SPEED, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_chorus_speed, 0.3f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0.29f) self = 0.29f; @@ -227,7 +229,7 @@ CUSTOM_CVAR(Float, fluid_chorus_speed, FLUID_CHORUS_DEFAULT_SPEED, CVAR_ARCHIVE| } // depth is in ms and actual maximum depends on the sample rate -CUSTOM_CVAR(Float, fluid_chorus_depth, FLUID_CHORUS_DEFAULT_DEPTH, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Float, fluid_chorus_depth, 8, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0) self = 0; @@ -276,8 +278,8 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice() } fluid_settings_setnum(FluidSettings, "synth.sample-rate", SampleRate); fluid_settings_setnum(FluidSettings, "synth.gain", fluid_gain); - fluid_settings_setstr(FluidSettings, "synth.reverb.active", fluid_reverb ? "yes" : "no"); - fluid_settings_setstr(FluidSettings, "synth.chorus.active", fluid_chorus ? "yes" : "no"); + fluid_settings_setint(FluidSettings, "synth.reverb.active", fluid_reverb); + fluid_settings_setint(FluidSettings, "synth.chorus.active", fluid_chorus); fluid_settings_setint(FluidSettings, "synth.polyphony", fluid_voices); fluid_settings_setint(FluidSettings, "synth.cpu-cores", fluid_threads); FluidSynth = new_fluid_synth(FluidSettings); @@ -540,9 +542,18 @@ void FluidSynthMIDIDevice::FluidSettingInt(const char *setting, int value) fluid_synth_set_chorus(FluidSynth, fluid_chorus_voices, fluid_chorus_level, fluid_chorus_speed, fluid_chorus_depth, fluid_chorus_type); } - else if (FLUID_OK != fluid_settings_setint(FluidSettings, setting, value)) + else if (0 == fluid_settings_setint(FluidSettings, setting, value)) { - Printf("Faild to set %s to %d.\n", setting, value); + Printf("Failed to set %s to %d.\n", setting, value); + } + // fluid_settings_setint succeeded; update these settings in the running synth, too + else if (strcmp(setting, "synth.reverb.active") == 0) + { + fluid_synth_set_reverb_on(FluidSynth, value); + } + else if (strcmp(setting, "synth.chorus.active") == 0) + { + fluid_synth_set_chorus_on(FluidSynth, value); } } @@ -558,7 +569,7 @@ void FluidSynthMIDIDevice::FluidSettingNum(const char *setting, double value) { if (FluidSettings != NULL) { - if (!fluid_settings_setnum(FluidSettings, setting, value)) + if (0 == fluid_settings_setnum(FluidSettings, setting, value)) { Printf("Failed to set %s to %g.\n", setting, value); } @@ -577,7 +588,7 @@ void FluidSynthMIDIDevice::FluidSettingStr(const char *setting, const char *valu { if (FluidSettings != NULL) { - if (!fluid_settings_setstr(FluidSettings, setting, value)) + if (0 == fluid_settings_setstr(FluidSettings, setting, value)) { Printf("Failed to set %s to %s.\n", setting, value); } @@ -646,6 +657,8 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() { (void **)&fluid_settings_setint, "fluid_settings_setint" }, { (void **)&fluid_settings_getstr, "fluid_settings_getstr" }, { (void **)&fluid_settings_getint, "fluid_settings_getint" }, + { (void **)&fluid_synth_set_reverb_on, "fluid_synth_set_reverb_on" }, + { (void **)&fluid_synth_set_chorus_on, "fluid_synth_set_chorus_on" }, { (void **)&fluid_synth_set_interp_method, "fluid_synth_set_interp_method" }, { (void **)&fluid_synth_set_polyphony, "fluid_synth_set_polyphony" }, { (void **)&fluid_synth_get_polyphony, "fluid_synth_get_polyphony" }, @@ -665,16 +678,21 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() { (void **)&fluid_synth_sysex, "fluid_synth_sysex" }, }; int fail = 0; + const char *libname; #ifdef _WIN32 - FluidSynthDLL = LoadLibrary(FLUIDSYNTHLIB); + FluidSynthDLL = LoadLibrary((libname = FLUIDSYNTHLIB1)); if (FluidSynthDLL == NULL) { - Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB "\n"); - return false; + FluidSynthDLL = LoadLibrary((libname = FLUIDSYNTHLIB2)); + if (FluidSynthDLL == NULL) + { + Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB1 " or " FLUIDSYNTHLIB2 "\n"); + return false; + } } #else - FluidSynthSO = dlopen(FLUIDSYNTHLIB, RTLD_LAZY); + FluidSynthSO = dlopen((libname = FLUIDSYNTHLIB), RTLD_LAZY); if (FluidSynthSO == NULL) { Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB ": %s\n", dlerror()); @@ -691,7 +709,7 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() #endif if (proc == NULL) { - Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, FLUIDSYNTHLIB); + Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, libname); fail++; } *imports[i].FuncPointer = (void *)proc; diff --git a/src/sound/music_mus_opl.cpp b/src/sound/music_mus_opl.cpp index 8f5d430ba..156094e1b 100644 --- a/src/sound/music_mus_opl.cpp +++ b/src/sound/music_mus_opl.cpp @@ -1,15 +1,27 @@ #include "i_musicinterns.h" +#include "oplsynth/muslib.h" +#include "oplsynth/opl.h" static bool OPL_Active; -CUSTOM_CVAR (Bool, opl_onechip, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR (Int, opl_numchips, 2, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { - if (OPL_Active && currSong != NULL) + if (*self <= 0) + { + self = 1; + } + else if (*self > MAXOPL2CHIPS) + { + self = MAXOPL2CHIPS; + } + else if (OPL_Active && currSong != NULL) { static_cast(currSong)->ResetChips (); } } +CVAR(Int, opl_core, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + OPLMUSSong::OPLMUSSong (FILE *file, BYTE *musiccache, int len) { int samples = int(OPL_SAMPLE_RATE / 14); @@ -17,7 +29,7 @@ OPLMUSSong::OPLMUSSong (FILE *file, BYTE *musiccache, int len) Music = new OPLmusicFile (file, musiccache, len); m_Stream = GSnd->CreateStream (FillStream, samples*4, - SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this); + (opl_core == 0 ? SoundStream::Mono : 0) | SoundStream::Float, int(OPL_SAMPLE_RATE), this); if (m_Stream == NULL) { Printf (PRINT_BOLD, "Could not create music stream.\n"); diff --git a/src/sound/music_softsynth_mididevice.cpp b/src/sound/music_softsynth_mididevice.cpp index 5cb21687a..56d77e735 100644 --- a/src/sound/music_softsynth_mididevice.cpp +++ b/src/sound/music_softsynth_mididevice.cpp @@ -96,7 +96,12 @@ SoftSynthMIDIDevice::~SoftSynthMIDIDevice() int SoftSynthMIDIDevice::OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) { - Stream = GSnd->CreateStream(FillStream, (SampleRate / chunks) * 4, SoundStream::Float | flags, SampleRate, this); + int chunksize = (SampleRate / chunks) * 4; + if (!(flags & SoundStream::Mono)) + { + chunksize *= 2; + } + Stream = GSnd->CreateStream(FillStream, chunksize, SoundStream::Float | flags, SampleRate, this); if (Stream == NULL) { return 2; diff --git a/src/st_stuff.h b/src/st_stuff.h index bd42c9eda..aaa407585 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -29,7 +29,6 @@ struct event_t; extern int ST_X; extern int ST_Y; -extern int SB_state; bool ST_Responder(event_t* ev); // [RH] Base blending values (for e.g. underwater) diff --git a/src/stats.cpp b/src/stats.cpp index 76fe36c26..61b04f87f 100644 --- a/src/stats.cpp +++ b/src/stats.cpp @@ -94,7 +94,7 @@ void FStat::ToggleStat (const char *name) void FStat::ToggleStat () { m_Active = !m_Active; - SB_state = StatusBar == NULL ? 0 : screen->GetPageCount (); + ST_SetNeedRefresh(); } void FStat::PrintStat () @@ -124,7 +124,7 @@ void FStat::PrintStat () } if (count) { - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); } } diff --git a/src/tarray.h b/src/tarray.h index a4a6efe15..3e66aa748 100644 --- a/src/tarray.h +++ b/src/tarray.h @@ -516,6 +516,37 @@ public: return *this; } + //======================================================================= + // + // TransferFrom + // + // Moves the contents from one TMap to another, leaving the TMap moved + // from empty. + // + //======================================================================= + + void TransferFrom(TMap &o) + { + // Clear all our nodes. + NumUsed = 0; + ClearNodeVector(); + + // Copy all of o's nodes. + Nodes = o.Nodes; + LastFree = o.LastFree; + Size = o.Size; + NumUsed = o.NumUsed; + + // Tell o it doesn't have any nodes. + o.Nodes = NULL; + o.Size = 0; + o.LastFree = NULL; + o.NumUsed = 0; + + // Leave o functional with one empty node. + o.SetNodeVector(1); + } + //======================================================================= // // Clear diff --git a/src/textures/multipatchtexture.cpp b/src/textures/multipatchtexture.cpp index 5d5f61062..3083735a9 100644 --- a/src/textures/multipatchtexture.cpp +++ b/src/textures/multipatchtexture.cpp @@ -558,7 +558,7 @@ int FMultiPatchTexture::CopyTrueColorPixels(FBitmap *bmp, int x, int y, int rota return Parts[0].Texture->CopyTrueColorPixels(bmp, x, y, rotate, inf); } - if (rotate != 0 || (inf != NULL && inf->op != OP_OVERWRITE && inf->op != OP_COPY)) + if (rotate != 0 || (inf != NULL && ((inf->op != OP_OVERWRITE && inf->op != OP_COPY) || inf->blend != BLEND_NONE))) { // We are doing some sort of fancy stuff to the destination bitmap, so composite to // a temporary bitmap, and copy that. FBitmap tbmp; diff --git a/src/textures/texturemanager.cpp b/src/textures/texturemanager.cpp index 1abbab29f..0302ca543 100644 --- a/src/textures/texturemanager.cpp +++ b/src/textures/texturemanager.cpp @@ -51,11 +51,16 @@ #include "farchive.h" #include "v_video.h" #include "r_renderer.h" +#include "r_sky.h" #include "textures/textures.h" FTextureManager TexMan; -CVAR(Bool, vid_nopalsubstitutions, false, CVAR_ARCHIVE) +CUSTOM_CVAR(Bool, vid_nopalsubstitutions, false, CVAR_ARCHIVE) +{ + // This is in case the sky texture has been substituted. + R_InitSkyMap (); +} //========================================================================== // @@ -171,7 +176,8 @@ FTextureID FTextureManager::CheckForTexture (const char *name, int usetype, BITF { // All NULL textures should actually return 0 if (tex->UseType == FTexture::TEX_FirstDefined && !(flags & TEXMAN_ReturnFirst)) return 0; - return FTextureID(tex->UseType==FTexture::TEX_Null? 0 : i); + if (tex->UseType == FTexture::TEX_SkinGraphic && !(flags & TEXMAN_AllowSkins)) return 0; + return FTextureID(tex->UseType==FTexture::TEX_Null ? 0 : i); } else if ((flags & TEXMAN_Overridable) && tex->UseType == FTexture::TEX_Override) { @@ -826,6 +832,7 @@ void FTextureManager::AddTexturesForWad(int wadnum) for (int i= firsttx; i <= lasttx; i++) { + bool skin = false; char name[9]; Wads.GetLumpName(name, i); name[8]=0; @@ -864,11 +871,17 @@ void FTextureManager::AddTexturesForWad(int wadnum) // Don't bother looking this lump if something later overrides it. if (Wads.CheckNumForName(name, ns_graphics) != i) continue; } + else if (ns >= ns_firstskin) + { + // Don't bother looking this lump if something later overrides it. + if (Wads.CheckNumForName(name, ns) != i) continue; + skin = true; + } else continue; // Try to create a texture from this lump and add it. // Unfortunately we have to look at everything that comes through here... - FTexture *out = FTexture::CreateTexture(i, FTexture::TEX_MiscPatch); + FTexture *out = FTexture::CreateTexture(i, skin ? FTexture::TEX_SkinGraphic : FTexture::TEX_MiscPatch); if (out != NULL) { @@ -917,7 +930,7 @@ void FTextureManager::SortTexturesByType(int start, int end) static int texturetypes[] = { FTexture::TEX_Sprite, FTexture::TEX_Null, FTexture::TEX_FirstDefined, FTexture::TEX_WallPatch, FTexture::TEX_Wall, FTexture::TEX_Flat, - FTexture::TEX_Override, FTexture::TEX_MiscPatch + FTexture::TEX_Override, FTexture::TEX_MiscPatch, FTexture::TEX_SkinGraphic }; for(unsigned int i=0;itarget, self, self, damage, NAME_Melee); - P_TraceBleed (damage, self->target, self); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } else if (domissile && MissileType != NULL) { @@ -366,7 +368,7 @@ static void DoAttack (AActor *self, bool domelee, bool domissile, { missile->tracer=self->target; } - P_CheckMissileSpawn(missile); + P_CheckMissileSpawn(missile, self->radius); } } } @@ -919,8 +921,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) bool sourcenothrust = false; - if (force <= 0) force = 128; - if (distance <= 0) distance = force; + if (force == 0) force = 128; + if (distance <= 0) distance = abs(force); // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) @@ -928,7 +930,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) sourcenothrust = true; self->target->flags2 &= ~MF2_NODMGTHRUST; } - int sourceflags2 = self->target != NULL ? self->target->flags2 : 0; P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); P_CheckSplash(self, distance << FRACBITS); @@ -1104,7 +1105,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) missile->FriendPlayer = 0; } } - P_CheckMissileSpawn(missile); + P_CheckMissileSpawn(missile, self->radius); } } } @@ -1128,6 +1129,7 @@ enum CBA_Flags CBAF_NORANDOM = 2, CBAF_EXPLICITANGLE = 4, CBAF_NOPITCH = 8, + CBAF_NORANDOMPUFFZ = 16, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) @@ -1147,6 +1149,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) int i; int bangle; int bslope = 0; + int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; if (self->target || (flags & CBAF_AIMFACING)) { @@ -1177,7 +1180,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) if (!(flags & CBAF_NORANDOM)) damage *= ((pr_cabullet()%3)+1); - P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype); + P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); } } return 0; @@ -1208,9 +1211,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMeleeAttack) { if (meleesound) S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); - P_DamageMobj (self->target, self, self, damage, damagetype); + int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); if (bleed) - P_TraceBleed (damage, self->target, self); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } else { @@ -1245,9 +1248,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) damagetype = NAME_Melee; // Melee is the default type if (meleesound) S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); - P_DamageMobj (self->target, self, self, damage, damagetype); + int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); if (bleed) - P_TraceBleed (damage, self->target, self); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); } else if (ti) { @@ -1263,7 +1266,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) { missile->tracer = self->target; } - P_CheckMissileSpawn(missile); + P_CheckMissileSpawn(missile, self->radius); } } return 0; @@ -1303,6 +1306,7 @@ enum FB_Flags FBF_EXPLICITANGLE = 4, FBF_NOPITCH = 8, FBF_NOFLASH = 16, + FBF_NORANDOMPUFFZ = 32, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) @@ -1324,6 +1328,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) int i; int bangle; int bslope = 0; + int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; if ((flags & FBF_USEAMMO) && weapon) { @@ -1342,7 +1347,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff); - S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + if (weapon != NULL) + { + S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } if ((numbullets == 1 && !player->refire) || numbullets == 0) { @@ -1351,7 +1359,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) if (!(flags & FBF_NORANDOM)) damage *= ((pr_cwbullet()%3)+1); - P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype); + P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags); } else { @@ -1378,7 +1386,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) if (!(flags & FBF_NORANDOM)) damage *= ((pr_cwbullet()%3)+1); - P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype); + P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); } } return 0; @@ -1464,6 +1472,7 @@ enum CPF_USEAMMO = 1, CPF_DAGGER = 2, CPF_PULLIN = 4, + CPF_NORANDOMPUFFZ = 8, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) @@ -1504,8 +1513,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff); + int puffFlags = LAF_ISMELEEATTACK | (flags & CPF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, true, &linetarget); + P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &linetarget); // turn to face target if (linetarget) @@ -1513,7 +1523,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) if (lifesteal) P_GiveBody (self, (damage * lifesteal) >> FRACBITS); - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } self->angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y); @@ -1548,6 +1561,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) PARAM_FLOAT_OPT (sparsity) { sparsity = 1; } PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; } PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; } + PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; } if (range == 0) range = 8192*FRACUNIT; if (sparsity == 0) sparsity=1.0; @@ -1578,7 +1592,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) slope = pr_crailgun.Random2() * (spread_z / 255); } - P_RailAttack (self, damage, spawnofs_xy, color1, color2, maxdiff, flags, pufftype, angle, slope, range, duration, sparsity, driftspeed, spawnclass); + P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angle, slope, range, duration, sparsity, driftspeed, spawnclass); return 0; } @@ -1613,6 +1627,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) PARAM_FLOAT_OPT (sparsity) { sparsity = 1; } PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; } PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; } + PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; } if (range == 0) range = 8192*FRACUNIT; if (sparsity == 0) sparsity = 1; @@ -1696,7 +1711,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) slopeoffset = pr_crailgun.Random2() * (spread_z / 255); } - P_RailAttack (self, damage, spawnofs_xy, color1, color2, maxdiff, flags, pufftype, angleoffset, slopeoffset, range, duration, sparsity, driftspeed, spawnclass); + P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angleoffset, slopeoffset, range, duration, sparsity, driftspeed, spawnclass); self->x = saved_x; self->y = saved_y; @@ -1854,92 +1869,139 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) // Common code for A_SpawnItem and A_SpawnItemEx // //=========================================================================== + enum SIX_Flags { - SIXF_TRANSFERTRANSLATION=1, - SIXF_ABSOLUTEPOSITION=2, - SIXF_ABSOLUTEANGLE=4, - SIXF_ABSOLUTEVELOCITY=8, - SIXF_SETMASTER=16, - SIXF_NOCHECKPOSITION=32, - SIXF_TELEFRAG=64, - // 128 is used by Skulltag! - SIXF_TRANSFERAMBUSHFLAG=256, - SIXF_TRANSFERPITCH=512, - SIXF_TRANSFERPOINTERS=1024, + SIXF_TRANSFERTRANSLATION = 1 << 0, + SIXF_ABSOLUTEPOSITION = 1 << 1, + SIXF_ABSOLUTEANGLE = 1 << 2, + SIXF_ABSOLUTEVELOCITY = 1 << 3, + SIXF_SETMASTER = 1 << 4, + SIXF_NOCHECKPOSITION = 1 << 5, + SIXF_TELEFRAG = 1 << 6, + SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum + SIXF_TRANSFERAMBUSHFLAG = 1 << 8, + SIXF_TRANSFERPITCH = 1 << 9, + SIXF_TRANSFERPOINTERS = 1 << 10, + SIXF_USEBLOODCOLOR = 1 << 11, + SIXF_CLEARCALLERTID = 1 << 12, + SIXF_MULTIPLYSPEED = 1 << 13, + SIXF_TRANSFERSCALE = 1 << 14, + SIXF_TRANSFERSPECIAL = 1 << 15, + SIXF_CLEARCALLERSPECIAL = 1 << 16, }; - static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) { - if (mo) + if (mo == NULL) { - AActor * originator = self; + return false; + } + AActor *originator = self; - if ((flags & SIXF_TRANSFERTRANSLATION) && !(mo->flags2 & MF2_DONTTRANSLATE)) + if (!(mo->flags2 & MF2_DONTTRANSLATE)) + { + if (flags & SIXF_TRANSFERTRANSLATION) { mo->Translation = self->Translation; } - if (flags & SIXF_TRANSFERPOINTERS) + else if (flags & SIXF_USEBLOODCOLOR) { - mo->target = self->target; - mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set - mo->tracer = self->tracer; + // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. + PalEntry bloodcolor = self->GetBloodColor(); + mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); } + } + if (flags & SIXF_TRANSFERPOINTERS) + { + mo->target = self->target; + mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set + mo->tracer = self->tracer; + } - mo->angle=self->angle; - if (flags & SIXF_TRANSFERPITCH) mo->pitch = self->pitch; - while (originator && originator->isMissile()) originator = originator->target; + mo->angle = self->angle; + if (flags & SIXF_TRANSFERPITCH) + { + mo->pitch = self->pitch; + } + while (originator && originator->isMissile()) + { + originator = originator->target; + } - if (flags & SIXF_TELEFRAG) + if (flags & SIXF_TELEFRAG) + { + P_TeleportMove(mo, mo->x, mo->y, mo->z, true); + // This is needed to ensure consistent behavior. + // Otherwise it will only spawn if nothing gets telefragged + flags |= SIXF_NOCHECKPOSITION; + } + if (mo->flags3 & MF3_ISMONSTER) + { + if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) { - P_TeleportMove(mo, mo->x, mo->y, mo->z, true); - // This is needed to ensure consistent behavior. - // Otherwise it will only spawn if nothing gets telefragged - flags |= SIXF_NOCHECKPOSITION; + // The monster is blocked so don't spawn it at all! + mo->ClearCounters(); + mo->Destroy(); + return false; } - if (mo->flags3&MF3_ISMONSTER) + else if (originator) { - if (!(flags&SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) + if (originator->flags3 & MF3_ISMONSTER) { - // The monster is blocked so don't spawn it at all! - mo->ClearCounters(); - mo->Destroy(); - return false; + // If this is a monster transfer all friendliness information + mo->CopyFriendliness(originator, true); + if (flags & SIXF_SETMASTER) mo->master = originator; // don't let it attack you (optional)! } - else if (originator) + else if (originator->player) { - if (originator->flags3&MF3_ISMONSTER) - { - // If this is a monster transfer all friendliness information - mo->CopyFriendliness(originator, true); - if (flags&SIXF_SETMASTER) mo->master = originator; // don't let it attack you (optional)! - } - else if (originator->player) - { - // A player always spawns a monster friendly to him - mo->flags |= MF_FRIENDLY; - mo->SetFriendPlayer(originator->player); + // A player always spawns a monster friendly to him + mo->flags |= MF_FRIENDLY; + mo->SetFriendPlayer(originator->player); - AActor * attacker=originator->player->attacker; - if (attacker) + AActor * attacker=originator->player->attacker; + if (attacker) + { + if (!(attacker->flags&MF_FRIENDLY) || + (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) { - if (!(attacker->flags&MF_FRIENDLY) || - (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) - { - // Target the monster which last attacked the player - mo->LastHeard = mo->target = attacker; - } + // Target the monster which last attacked the player + mo->LastHeard = mo->target = attacker; } } } } - else if (!(flags & SIXF_TRANSFERPOINTERS)) - { - // If this is a missile or something else set the target to the originator - mo->target=originator? originator : self; - } } + else if (!(flags & SIXF_TRANSFERPOINTERS)) + { + // If this is a missile or something else set the target to the originator + mo->target = originator ? originator : self; + } + if (flags & SIXF_TRANSFERSCALE) + { + mo->scaleX = self->scaleX; + mo->scaleY = self->scaleY; + } + if (flags & SIXF_TRANSFERAMBUSHFLAG) + { + mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); + } + if (flags & SIXF_CLEARCALLERTID) + { + self->RemoveFromHash(); + self->tid = 0; + } + if (flags & SIXF_TRANSFERSPECIAL) + { + mo->special = self->special; + memcpy(mo->args, self->args, sizeof(self->args)); + } + if (flags & SIXF_CLEARCALLERSPECIAL) + { + self->special = 0; + memset(self->args, 0, sizeof(self->args)); + } + return true; } @@ -2021,6 +2083,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) PARAM_ANGLE_OPT (angle) { angle = 0; } PARAM_INT_OPT (flags) { flags = 0; } PARAM_INT_OPT (chance) { chance = 0; } + PARAM_INT_OPT (tid) { tid = 0; } if (missile == NULL) { @@ -2066,17 +2129,30 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) xvel = newxvel; } - AActor * mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); + AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); bool res = InitSpawnedItem(self, mo, flags); ACTION_SET_RESULT(res); // for an inventory item's use state - if (mo) + if (res) { - mo->velx = xvel; - mo->vely = yvel; - mo->velz = zvel; + if (tid != 0) + { + assert(mo->tid == 0); + mo->tid = tid; + mo->AddToHash(); + } + if (flags & SIXF_MULTIPLYSPEED) + { + mo->velx = FixedMul(xvel, mo->Speed); + mo->vely = FixedMul(yvel, mo->Speed); + mo->velz = FixedMul(zvel, mo->Speed); + } + else + { + mo->velx = xvel; + mo->vely = yvel; + mo->velz = zvel; + } mo->angle = angle; - if (flags & SIXF_TRANSFERAMBUSHFLAG) - mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); } return numret; } @@ -2146,8 +2222,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) bo->vely = xy_vely + z_vely + (self->vely >> 1); bo->velz = xy_velz + z_velz; - bo->target= self; - P_CheckMissileSpawn (bo); + bo->target = self; + P_CheckMissileSpawn (bo, self->radius); } else { @@ -2476,15 +2552,18 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) for (i = 0; i < GetDefaultByType(debris)->health; i++) { mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), - self->y+((pr_spawndebris()-128)<<12), - self->z+(pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); - if (mo && transfer_translation) + self->y + ((pr_spawndebris()-128)<<12), + self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); + if (mo) { - mo->Translation = self->Translation; - } - if (mo && i < mo->GetClass()->NumOwnedStates) - { - mo->SetState (mo->GetClass()->OwnedStates + i); + if (transfer_translation) + { + mo->Translation = self->Translation; + } + if (i < mo->GetClass()->NumOwnedStates) + { + mo->SetState (mo->GetClass()->OwnedStates + i); + } mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); @@ -2722,11 +2801,14 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) TThinkerIterator it; AActor *mo; - while ( (mo = it.Next()) ) + if (self->master != NULL) { - if (mo->master == self->master && mo != self) + while ( (mo = it.Next()) ) { - P_DamageMobj(mo, self, self, mo->health, damagetype, DMG_NO_ARMOR | DMG_NO_FACTOR); + if (mo->master == self->master && mo != self) + { + P_DamageMobj(mo, self, self, mo->health, damagetype, DMG_NO_ARMOR | DMG_NO_FACTOR); + } } } return 0; @@ -3002,7 +3084,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! if (self->player != NULL && - skins[self->player->userinfo.skin].othergame) + skins[self->player->userinfo.GetSkin()].othergame) { ACTION_JUMP(jump); } @@ -3041,6 +3123,295 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) return 0; } +//========================================================================== +// +// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, +// fixed range = 0, angle angle = 0, angle pitch = 0, +// fixed offsetheight = 32, fixed offsetwidth = 0, +// int ptr_target = AAPTR_DEFAULT (target) ) +// +//========================================================================== + +enum CLOF_flags +{ + CLOFF_NOAIM_VERT = 0x1, + CLOFF_NOAIM_HORZ = 0x2, + + CLOFF_JUMPENEMY = 0x4, + CLOFF_JUMPFRIEND = 0x8, + CLOFF_JUMPOBJECT = 0x10, + CLOFF_JUMPNONHOSTILE = 0x20, + + CLOFF_SKIPENEMY = 0x40, + CLOFF_SKIPFRIEND = 0x80, + CLOFF_SKIPOBJECT = 0x100, + CLOFF_SKIPNONHOSTILE = 0x200, + + CLOFF_MUSTBESHOOTABLE = 0x400, + + CLOFF_SKIPTARGET = 0x800, + CLOFF_ALLOWNULL = 0x1000, + CLOFF_CHECKPARTIAL = 0x2000, + + CLOFF_MUSTBEGHOST = 0x4000, + CLOFF_IGNOREGHOST = 0x8000, + + CLOFF_MUSTBESOLID = 0x10000, + CLOFF_BEYONDTARGET = 0x20000, + + CLOFF_FROMBASE = 0x40000, + CLOFF_MUL_HEIGHT = 0x80000, + CLOFF_MUL_WIDTH = 0x100000, + + CLOFF_JUMP_ON_MISS = 0x200000, + CLOFF_AIM_VERT_NOOFFSET = 0x400000, +}; + +struct LOFData +{ + AActor *Self; + AActor *Target; + int Flags; + bool BadActor; +}; + +ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) +{ + LOFData *data = (LOFData *)userdata; + int flags = data->Flags; + + if (trace.HitType != TRACE_HitActor) + { + return TRACE_Stop; + } + if (trace.Actor == data->Target) + { + if (flags & CLOFF_SKIPTARGET) + { + if (flags & CLOFF_BEYONDTARGET) + { + return TRACE_Skip; + } + return TRACE_Abort; + } + return TRACE_Stop; + } + if (flags & CLOFF_MUSTBESHOOTABLE) + { // all shootability checks go here + if (!(trace.Actor->flags & MF_SHOOTABLE)) + { + return TRACE_Skip; + } + if (trace.Actor->flags2 & MF2_NONSHOOTABLE) + { + return TRACE_Skip; + } + } + if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) + { + return TRACE_Skip; + } + if (flags & CLOFF_MUSTBEGHOST) + { + if (!(trace.Actor->flags3 & MF3_GHOST)) + { + return TRACE_Skip; + } + } + else if (flags & CLOFF_IGNOREGHOST) + { + if (trace.Actor->flags3 & MF3_GHOST) + { + return TRACE_Skip; + } + } + if ( + ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Stop; + } + if ( + ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Skip; + } + data->BadActor = true; + return TRACE_Abort; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) +{ + // Check line of fire + + /* + Not accounted for / I don't know how it works: FLOORCLIP + */ + + AActor *target; + fixed_t + x1, y1, z1, + vx, vy, vz; + + PARAM_ACTION_PROLOGUE; + PARAM_STATE (jump); + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (range) { range = 0; } + PARAM_FIXED_OPT (minrange) { minrange = 0; } + { + PARAM_ANGLE_OPT (angle) { angle = 0; } + PARAM_ANGLE_OPT (pitch) { pitch = 0; } + PARAM_FIXED_OPT (offsetheight) { offsetheight = 0; } + PARAM_FIXED_OPT (offsetwidth) { offsetwidth = 0; } + PARAM_INT_OPT (ptr_target) { ptr_target = AAPTR_DEFAULT; } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default + + if (flags & CLOFF_MUL_HEIGHT) + { + if (self->player != NULL) + { + // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player + offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); + } + else + { + offsetheight = FixedMul(offsetheight, self->height); + } + } + if (flags & CLOFF_MUL_WIDTH) + { + offsetwidth = FixedMul(self->radius, offsetwidth); + } + + x1 = self->x; + y1 = self->y; + z1 = self->z + offsetheight - self->floorclip; + + if (!(flags & CLOFF_FROMBASE)) + { // default to hitscan origin + + // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player + z1 += (self->height >> 1); + if (self->player != NULL) + { + z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); + } + else + { + z1 += 8*FRACUNIT; + } + } + + if (target) + { + FVector2 xyvec(target->x - x1, target->y - y1); + fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); + + if (range && !(flags & CLOFF_CHECKPARTIAL)) + { + if (distance > range) + return numret; + } + + { + angle_t ang; + + if (flags & CLOFF_NOAIM_HORZ) + { + ang = self->angle; + } + else ang = R_PointToAngle2 (x1, y1, target->x, target->y); + + angle += ang; + + ang >>= ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + + if (flags & CLOFF_NOAIM_VERT) + { + pitch += self->pitch; + } + else if (flags & CLOFF_AIM_VERT_NOOFFSET) + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); + } + else + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); + } + } + else if (flags & CLOFF_ALLOWNULL) + { + angle += self->angle; + pitch += self->pitch; + + angle_t ang = self->angle >> ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + else + { + return numret; + } + + angle >>= ANGLETOFINESHIFT; + pitch = (0-pitch)>>ANGLETOFINESHIFT; + + vx = FixedMul (finecosine[pitch], finecosine[angle]); + vy = FixedMul (finecosine[pitch], finesine[angle]); + vz = -finesine[pitch]; + } + + /* Variable set: + + jump, flags, target + x1,y1,z1 (trace point of origin) + vx,vy,vz (trace unit vector) + range + */ + + sector_t *sec = P_PointInSector(x1, y1); + + if (range == 0) + { + range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; + } + + FTraceResults trace; + LOFData lof_data; + + lof_data.Self = self; + lof_data.Target = target; + lof_data.Flags = flags; + lof_data.BadActor = false; + + Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, + CheckLOFTraceFunc, &lof_data); + + if (trace.HitType == TRACE_HitActor || + ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) + { + if (minrange > 0 && trace.Distance < minrange) + { + return numret; + } + ACTION_JUMP(jump); + } + return numret; +} + //========================================================================== // // A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, @@ -3068,7 +3439,8 @@ enum JLOS_flags JLOSF_TARGETLOS=128, JLOSF_FLIPFOV=256, JLOSF_ALLYNOJUMP=512, - JLOSF_COMBATANTONLY=1024 + JLOSF_COMBATANTONLY=1024, + JLOSF_NOAUTOAIM=2048, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) @@ -3118,7 +3490,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) else { // Does the player aim at something that can be shot? - P_BulletSlope(self, &target); + P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); if (!target) return numret; @@ -3366,18 +3738,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) TThinkerIterator it; AActor *mo; - while ( (mo = it.Next()) ) + if (self->master != NULL) { - if (mo->master == self->master && mo != self) + while ( (mo = it.Next()) ) { - if (amount > 0) + if (mo->master == self->master && mo != self) { - P_DamageMobj(mo, self, self, amount, damagetype, DMG_NO_ARMOR); - } - else if (amount < 0) - { - amount = -amount; - P_GiveBody(mo, amount); + if (amount > 0) + { + P_DamageMobj(mo, self, self, amount, damagetype, DMG_NO_ARMOR); + } + else if (amount < 0) + { + amount = -amount; + P_GiveBody(mo, amount); + } } } } @@ -3495,14 +3870,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); if (linkchange) self->UnlinkFromWorld(); - if (value) - { - *flagp |= fd->flagbit; - } - else - { - *flagp &= ~fd->flagbit; - } + ModActorFlag(self, fd, value); if (linkchange) self->LinkToWorld(); } kill_after = self->CountsAsKill(); @@ -3590,14 +3958,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) if (fd != NULL) { - if (fd->structoffset == -1) - { - if (CheckDeprecatedFlags(owner, cls, fd->flagbit)) - { - ACTION_JUMP(jumpto); - } - } - else if (fd->flagbit & *(DWORD*)(((char*)owner) + fd->structoffset)) + if (CheckActorFlag(owner, fd)) { ACTION_JUMP(jumpto); } @@ -3638,9 +3999,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) TThinkerIterator it; AActor *mo; - while ( (mo = it.Next()) ) + while ((mo = it.Next()) != NULL) { - if ( ( mo->master == self ) && ( ( mo->health <= 0 ) || removeall) ) + if (mo->master == self && (mo->health <= 0 || removeall)) { P_RemoveThing(mo); } @@ -3661,11 +4022,14 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) TThinkerIterator it; AActor *mo; - while ( (mo = it.Next()) ) + if (self->master != NULL) { - if ( ( mo->master == self->master ) && ( mo != self ) && ( ( mo->health <= 0 ) || removeall) ) + while ((mo = it.Next()) != NULL) { - P_RemoveThing(mo); + if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) + { + P_RemoveThing(mo); + } } } return 0; @@ -3698,9 +4062,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren) TThinkerIterator it; AActor *mo; - while ((mo = it.Next())) + while ((mo = it.Next()) != NULL) { - if ( mo->master == self ) + if (mo->master == self) { P_Thing_Raise(mo); } @@ -3720,11 +4084,14 @@ DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings) TThinkerIterator it; AActor *mo; - while ( (mo = it.Next()) ) + if (self->master != NULL) { - if ( ( mo->master == self->master ) && ( mo != self ) ) + while ((mo = it.Next()) != NULL) { - P_Thing_Raise(mo); + if (mo->master == self->master && mo != self) + { + P_Thing_Raise(mo); + } } } return 0; @@ -4323,11 +4690,11 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) damage >>= 2; if (damage) { - P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); + int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); if (spawnblood) { - P_SpawnBlood(dx, dy, dz, angle, damage, self->target); - P_TraceBleed(damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); + P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); + P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); } } } @@ -4768,4 +5135,19 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) self->tics = tics_to_set; return 0; -} \ No newline at end of file +} + +//========================================================================== +// +// A_SetDamageType +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME(damagetype); + + self->DamageType = damagetype; + return 0; +} diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index f154bd189..96ab1373b 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -55,10 +55,10 @@ static TArray variables; //========================================================================== // [RH] Keep GCC quiet by not using offsetof on Actor types. -#define DEFINE_FLAG(prefix, name, type, variable) { prefix##_##name, #name, (int)(size_t)&((type*)1)->variable - 1 } -#define DEFINE_FLAG2(symbol, name, type, variable) { symbol, #name, (int)(size_t)&((type*)1)->variable - 1 } -#define DEFINE_DEPRECATED_FLAG(name) { DEPF_##name, #name, -1 } -#define DEFINE_DUMMY_FLAG(name) { DEPF_UNUSED, #name, -1 } +#define DEFINE_FLAG(prefix, name, type, variable) { prefix##_##name, #name, (int)(size_t)&((type*)1)->variable - 1, sizeof(((type *)0)->variable) } +#define DEFINE_FLAG2(symbol, name, type, variable) { symbol, #name, (int)(size_t)&((type*)1)->variable - 1, sizeof(((type *)0)->variable) } +#define DEFINE_DEPRECATED_FLAG(name) { DEPF_##name, #name, -1, 0 } +#define DEFINE_DUMMY_FLAG(name) { DEPF_UNUSED, #name, -1, 0 } static FFlagDef ActorFlags[]= { @@ -255,6 +255,7 @@ static FFlagDef ActorFlags[]= DEFINE_FLAG2(BOUNCE_ExplodeOnWater, EXPLODEONWATER, AActor, BounceFlags), DEFINE_FLAG2(BOUNCE_MBF, MBFBOUNCER, AActor, BounceFlags), DEFINE_FLAG2(BOUNCE_AutoOffFloorOnly, BOUNCEAUTOOFFFLOORONLY, AActor, BounceFlags), + DEFINE_FLAG2(BOUNCE_UseBounceState, USEBOUNCESTATE, AActor, BounceFlags), // Deprecated flags. Handling must be performed in HandleDeprecatedFlags DEFINE_DEPRECATED_FLAG(FIREDAMAGE), @@ -299,10 +300,10 @@ static FFlagDef InventoryFlags[] = DEFINE_FLAG(IF, PERSISTENTPOWER, AInventory, ItemFlags), DEFINE_FLAG(IF, RESTRICTABSOLUTELY, AInventory, ItemFlags), DEFINE_FLAG(IF, NEVERRESPAWN, AInventory, ItemFlags), + DEFINE_FLAG(IF, NOSCREENFLASH, AInventory, ItemFlags), DEFINE_DEPRECATED_FLAG(PICKUPFLASH), - DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP), -}; + DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP),}; static FFlagDef WeaponFlags[] = { @@ -336,6 +337,13 @@ static FFlagDef PlayerPawnFlags[] = // PlayerPawn flags DEFINE_FLAG(PPF, NOTHRUSTWHENINVUL, APlayerPawn, PlayerFlags), DEFINE_FLAG(PPF, CANSUPERMORPH, APlayerPawn, PlayerFlags), + DEFINE_FLAG(PPF, CROUCHABLEMORPH, APlayerPawn, PlayerFlags), +}; + +static FFlagDef PowerSpeedFlags[] = +{ + // PowerSpeed flags + DEFINE_FLAG(PSF, NOTRAIL, APowerSpeed, SpeedFlags), }; static const struct FFlagList { const PClass * const *Type; FFlagDef *Defs; int NumDefs; } FlagLists[] = @@ -344,6 +352,7 @@ static const struct FFlagList { const PClass * const *Type; FFlagDef *Defs; int { &RUNTIME_CLASS_CASTLESS(AInventory), InventoryFlags, countof(InventoryFlags) }, { &RUNTIME_CLASS_CASTLESS(AWeapon), WeaponFlags, countof(WeaponFlags) }, { &RUNTIME_CLASS_CASTLESS(APlayerPawn), PlayerPawnFlags,countof(PlayerPawnFlags) }, + { &RUNTIME_CLASS_CASTLESS(APowerSpeed), PowerSpeedFlags,countof(PowerSpeedFlags) }, }; #define NUM_FLAG_LISTS (countof(FlagLists)) @@ -428,7 +437,7 @@ FFlagDef *FindFlag (const PClass *type, const char *part1, const char *part2) // //========================================================================== -const char *GetFlagName(int flagnum, int flagoffset) +const char *GetFlagName(unsigned int flagnum, int flagoffset) { for(size_t i = 0; i < countof(ActorFlags); i++) { diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index 7aa7b8d34..e05172d02 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -83,6 +83,10 @@ DEFINE_MEMBER_VARIABLE(scaleY, AActor) DEFINE_MEMBER_VARIABLE(Score, AActor) DEFINE_MEMBER_VARIABLE(accuracy, AActor) DEFINE_MEMBER_VARIABLE(stamina, AActor) +DEFINE_MEMBER_VARIABLE(height, AActor) +DEFINE_MEMBER_VARIABLE(radius, AActor) +DEFINE_MEMBER_VARIABLE(reactiontime, AActor) +DEFINE_MEMBER_VARIABLE(meleerange, AActor) ExpEmit::ExpEmit(VMFunctionBuilder *build, int type) : RegNum(build->Registers[type].Get(1)), RegType(type), Konst(false), Fixed(false) @@ -1716,7 +1720,7 @@ FxExpression *FxBinaryInt::Resolve(FCompileContext& ctx) if (right->ValueType != VAL_Int) { right = new FxIntCast(right); - right = left->Resolve(ctx); + right = right->Resolve(ctx); } if (left == NULL || right == NULL) { @@ -3620,9 +3624,9 @@ FxExpression *FxClassTypeCast::Resolve(FCompileContext &ctx) FName clsname = basex->EvalExpression(NULL).GetName(); const PClass *cls = NULL; - if (clsname != NAME_None || !ctx.isconst) + if (clsname != NAME_None) { - cls= PClass::FindClass(clsname); + cls = PClass::FindClass(clsname); if (cls == NULL) { if (!ctx.lax) diff --git a/src/thingdef/thingdef_parse.cpp b/src/thingdef/thingdef_parse.cpp index c9fb01911..d07f481a1 100644 --- a/src/thingdef/thingdef_parse.cpp +++ b/src/thingdef/thingdef_parse.cpp @@ -465,15 +465,7 @@ void HandleActorFlag(FScanner &sc, Baggage &bag, const char *part1, const char * } else { - DWORD * flagvar = (DWORD*) ((char*)defaults + fd->structoffset); - if (mod == '+') - { - *flagvar |= fd->flagbit; - } - else - { - *flagvar &= ~fd->flagbit; - } + ModActorFlag(defaults, fd, mod == '+'); } } else diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index 0d115ec5b..eff01efdb 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -109,6 +109,91 @@ static APlayerPawn::MetaClass *FindClassTentativePlayerPawn(const char *name) return static_cast(FindClassTentative(name, RUNTIME_CLASS(APlayerPawn))); } +//========================================================================== +// +// Sets or clears a flag, taking field width into account. +// +//========================================================================== +void ModActorFlag(AActor *actor, FFlagDef *fd, bool set) +{ + // Little-Endian machines only need one case, because all field sizes + // start at the same address. (Unless the machine has unaligned access + // exceptions, in which case you'll need multiple cases for it too.) +#ifdef __BIG_ENDIAN__ + if (fd->fieldsize == 4) +#endif + { + DWORD *flagvar = (DWORD *)((char *)actor + fd->structoffset); + if (set) + { + *flagvar |= fd->flagbit; + } + else + { + *flagvar &= ~fd->flagbit; + } + } +#ifdef __BIG_ENDIAN__ + else if (fd->fieldsize == 2) + { + WORD *flagvar = (WORD *)((char *)actor + fd->structoffset); + if (set) + { + *flagvar |= fd->flagbit; + } + else + { + *flagvar &= ~fd->flagbit; + } + } + else + { + assert(fd->fieldsize == 1); + BYTE *flagvar = (BYTE *)((char *)actor + fd->structoffset); + if (set) + { + *flagvar |= fd->flagbit; + } + else + { + *flagvar &= ~fd->flagbit; + } + } +#endif +} + +//========================================================================== +// +// Returns whether an actor flag is true or not. +// +//========================================================================== + +INTBOOL CheckActorFlag(const AActor *owner, FFlagDef *fd) +{ + if (fd->structoffset == -1) + { + return CheckDeprecatedFlags(owner, owner->GetClass(), fd->flagbit); + } + else +#ifdef __BIG_ENDIAN__ + if (fd->fieldsize == 4) +#endif + { + return fd->flagbit & *(DWORD *)(((char*)owner) + fd->structoffset); + } +#ifdef __BID_ENDIAN__ + else if (fd->fieldsize == 2) + { + return fd->flagbit & *(WORD *)(((char*)owner) + fd->structoffset); + } + else + { + assert(fd->fieldsize == 1); + return fd->flagbit & *(BYTE *)(((char*)owner) + fd->structoffset); + } +#endif +} + //=========================================================================== // // HandleDeprecatedFlags @@ -184,7 +269,7 @@ void HandleDeprecatedFlags(AActor *defaults, PClassActor *info, bool set, int in // //=========================================================================== -bool CheckDeprecatedFlags(AActor *actor, PClassActor *info, int index) +bool CheckDeprecatedFlags(const AActor *actor, PClassActor *info, int index) { // A deprecated flag is false if // a) it hasn't been added here @@ -225,11 +310,11 @@ bool CheckDeprecatedFlags(AActor *actor, PClassActor *info, int index) return (actor->BounceFlags & (BOUNCE_TypeMask|BOUNCE_UseSeeSound)) == BOUNCE_DoomCompat; case DEPF_PICKUPFLASH: - return static_cast(actor)->PickupFlash == PClass::FindClass("PickupFlash"); + return static_cast(actor)->PickupFlash == PClass::FindClass("PickupFlash"); // A pure name lookup may or may not be more efficient, but I know no static identifier for PickupFlash. case DEPF_INTERHUBSTRIP: - return !(static_cast(actor)->InterHubAmount); + return !(static_cast(actor)->InterHubAmount); } return false; // Any entirely unknown flag is not set @@ -553,6 +638,16 @@ DEFINE_PROPERTY(scale, F, Actor) defaults->scaleX = defaults->scaleY = id; } +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(floatbobphase, I, Actor) +{ + PROP_INT_PARM(id, 0); + if (id < -1 || id >= 64) I_Error ("FloatBobPhase must be in range [-1,63]"); + defaults->FloatBobPhase = id; +} + //========================================================================== // //========================================================================== @@ -1774,6 +1869,24 @@ DEFINE_CLASS_PROPERTY(selectionorder, I, Weapon) defaults->SelectionOrder = i; } +//========================================================================== +// +//========================================================================== +DEFINE_CLASS_PROPERTY(minselectionammo1, I, Weapon) +{ + PROP_INT_PARM(i, 0); + defaults->MinSelAmmo1 = i; +} + +//========================================================================== +// +//========================================================================== +DEFINE_CLASS_PROPERTY(minselectionammo2, I, Weapon) +{ + PROP_INT_PARM(i, 0); + defaults->MinSelAmmo2 = i; +} + //========================================================================== // //========================================================================== @@ -1807,7 +1920,7 @@ DEFINE_CLASS_PROPERTY(yadjust, F, Weapon) DEFINE_CLASS_PROPERTY(bobstyle, S, Weapon) { static const char *names[] = { "Normal", "Inverse", "Alpha", "InverseAlpha", "Smooth", "InverseSmooth", NULL }; - static const int flags[] = { AWeapon::BobNormal, + static const int styles[] = { AWeapon::BobNormal, AWeapon::BobInverse, AWeapon::BobAlpha, AWeapon::BobInverseAlpha, AWeapon::BobSmooth, AWeapon::BobInverseSmooth, }; PROP_STRING_PARM(id, 0); @@ -1817,7 +1930,7 @@ DEFINE_CLASS_PROPERTY(bobstyle, S, Weapon) I_Error("Unknown bobstyle %s", id); match = 0; } - defaults->BobStyle |= flags[match]; + defaults->BobStyle = styles[match]; } //========================================================================== diff --git a/src/thingdef/thingdef_states.cpp b/src/thingdef/thingdef_states.cpp index 03e042a21..9bd3a562a 100644 --- a/src/thingdef/thingdef_states.cpp +++ b/src/thingdef/thingdef_states.cpp @@ -265,6 +265,18 @@ do_stop: state.Fast = true; continue; } + if (sc.Compare("NODELAY")) + { + if (bag.statedef.GetStateLabelIndex(NAME_Spawn) == bag.statedef.GetStateCount()) + { + state.NoDelay = true; + } + else + { + sc.ScriptMessage("NODELAY may only be used immediately after Spawn:"); + } + continue; + } if (sc.Compare("OFFSET")) { // specify a weapon offset @@ -327,8 +339,7 @@ do_stop: // int paramindex = PrepareStateParameters(&state, numparams, bag.Info->Class); // int paramstart = paramindex; bool varargs = params[numparams - 1] == '+'; - int argcount = 0; - + int varargcount = 0; if (varargs) { @@ -373,7 +384,7 @@ do_stop: // StateParams.Set(paramindex++, x); tcall->Parameters.Push(new FxParameter(x)); params++; - argcount++; + varargcount++; if (*params) { if (*params == '+') diff --git a/src/timidity/instrum_dls.cpp b/src/timidity/instrum_dls.cpp index 0243461e6..fe5e4fdcd 100644 --- a/src/timidity/instrum_dls.cpp +++ b/src/timidity/instrum_dls.cpp @@ -992,7 +992,7 @@ void PrintDLS(DLS_Data *data) } printf("\n"); } - if ( data->waveList ) { + if ( data->waveList && data->ptbl ) { DWORD i; printf("Waves:\n"); for ( i = 0; i < data->ptbl->cCues; ++i ) { diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 87b4e0842..cd07b44a1 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -53,6 +53,7 @@ #include "gi.h" #include "g_level.h" #include "st_stuff.h" +#include "sbar.h" #include "i_system.h" #include "i_video.h" @@ -1377,8 +1378,32 @@ bool DCanvas::ClipBox (int &x, int &y, int &w, int &h, const BYTE *&src, const i return false; } +//========================================================================== +// +// V_SetBorderNeedRefresh +// +// Flag the border as in need of updating. (Probably because something that +// was on top of it has changed. +// +//========================================================================== + +void V_SetBorderNeedRefresh() +{ + if (screen != NULL) + { + BorderNeedRefresh = screen->GetPageCount(); + } +} + +//========================================================================== +// +// V_DrawFrame +// // Draw a frame around the specified area using the view border // frame graphics. The border is drawn outside the area, not in it. +// +//========================================================================== + void V_DrawFrame (int left, int top, int width, int height) { FTexture *p; @@ -1411,7 +1436,7 @@ void V_DrawFrame (int left, int top, int width, int height) //========================================================================== // -// +// V_DrawBorder // //========================================================================== @@ -1455,7 +1480,7 @@ static void V_DrawViewBorder (void) // Will draw borders around itself, too. if (SCREENWIDTH > 320) { - SB_state = screen->GetPageCount (); + ST_SetNeedRefresh(); } if (viewwidth == SCREENWIDTH) diff --git a/src/v_font.cpp b/src/v_font.cpp index c1923e4ac..d34b08be5 100644 --- a/src/v_font.cpp +++ b/src/v_font.cpp @@ -244,32 +244,69 @@ int NumTextColors; // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static const BYTE myislower[256] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0 -}; - static TArray TranslationParms[2]; static TArray TranslationLookup; static TArray TranslationColors; // CODE -------------------------------------------------------------------- +static bool myislower(int code) +{ + if (code >= 'a' && code <= 'z') + return true; + if (code != 0xF7 && code != 0xFF && (code & 0xE0) == 0xE0) + return true; + return false; +} + +// Returns a character without an accent mark. +// FIXME: Only valid for CP-1252; we should go Unicode at some point. + +static int stripaccent(int code) +{ + if (code < 0x8a) + return code; + if (code == 0x8a) // Latin capital letter S with caron + return 'S'; + if (code == 0x8e) // Latin capital letter Z with caron + return 'Z'; + if (code == 0x9a) // Latin small letter S with caron + return 's'; + if (code == 0x9e) // Latin small letter Z with caron + return 'z'; + if (code == 0x9f) // Latin capital letter Y with diaeresis + return 'Y'; + if (code == 0xff) // Latin small letter Y with diaeresis + return 'y'; + // Every other accented character has the high two bits set. + if ((code & 0xC0) == 0) + return code; + // Make lowercase characters uppercase so there are half as many tests. + int acode = code & 0xDF; + if (acode >= 0xC0 && acode <= 0xC5) // A with accents + return 'A' + (code & 0x20); + if (acode == 0xC7) // Cedilla + return 'C' + (acode & 0x20); + if (acode >= 0xC8 && acode <= 0xCB) // E with accents + return 'E' + (code & 0x20); + if (acode >= 0xCC && acode <= 0xCF) // I with accents + return 'I' + (code & 0x20); + if (acode == 0xD0) // Eth + return 'D' + (code & 0x20); + if (acode == 0xD1) // N with tilde + return 'N' + (code & 0x20); + if ((acode >= 0xD2 && acode <= 0xD6) || // O with accents + acode == 0xD8) // O with stroke + return 'O' + (code & 0x20); + if (acode >= 0xD9 && acode <= 0xDC) // U with accents + return 'U' + (code & 0x20); + if (acode == 0xDD) // Y with accute + return 'Y' + (code & 0x20); + if (acode == 0xDE) // Thorn + return 'P' + (code & 0x20); // well, it sort of looks like a 'P' + return code; +} + FFont *V_GetFont(const char *name) { FFont *font = FFont::FindFont (name); @@ -608,6 +645,7 @@ int FFont::SimpleTranslation (BYTE *colorsused, BYTE *translation, BYTE *reverse qsort (reverse+1, j-1, 1, compare); *luminosity = new double[j]; + (*luminosity)[0] = 0.0; // [BL] Prevent uninitalized memory max = 0.0; min = 100000000.0; for (i = 1; i < j; i++) @@ -760,7 +798,8 @@ int FFont::GetCharCode(int code, bool needpic) const { return code; } - if (myislower[code]) + // Try converting lowercase characters to uppercase. + if (myislower(code)) { code -= 32; if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].Pic != NULL)) @@ -768,6 +807,16 @@ int FFont::GetCharCode(int code, bool needpic) const return code; } } + // Try stripping accents from accented characters. + int newcode = stripaccent(code); + if (newcode != code) + { + code = newcode; + if (code >= FirstChar && code <= LastChar && (!needpic || Chars[code - FirstChar].Pic != NULL)) + { + return code; + } + } return -1; } @@ -908,6 +957,7 @@ FFont::FFont (int lump) Chars = NULL; PatchRemap = NULL; Name = NULL; + Cursor = '_'; } //========================================================================== @@ -1073,7 +1123,7 @@ void FSingleLumpFont::LoadFON2 (int lump, const BYTE *data) FontHeight = data[4] + data[5]*256; FirstChar = data[6]; LastChar = data[7]; - ActiveColors = data[10]; + ActiveColors = data[10]+1; PatchRemap = NULL; RescalePalette = data[9] == 0; @@ -1125,9 +1175,9 @@ void FSingleLumpFont::LoadFON2 (int lump, const BYTE *data) SpaceWidth = totalwidth * 2 / (3 * count); } - memcpy(PaletteData, palette, (ActiveColors+1)*3); + memcpy(PaletteData, palette, ActiveColors*3); - data_p = palette + (ActiveColors+1)*3; + data_p = palette + ActiveColors*3; for (i = 0; i < count; ++i) { @@ -2014,8 +2064,8 @@ void FSpecialFont::LoadTranslations() // FFont :: FixXMoves // // If a font has gaps in its characters, set the missing characters' -// XMoves to either SpaceWidth or the uppercase variant's XMove. Missing -// XMoves must be initialized with INT_MIN beforehand. +// XMoves to either SpaceWidth or the unaccented or uppercase variant's +// XMove. Missing XMoves must be initialized with INT_MIN beforehand. // //========================================================================== @@ -2025,7 +2075,8 @@ void FFont::FixXMoves() { if (Chars[i].XMove == INT_MIN) { - if (myislower[i + FirstChar]) + // Try an uppercase character. + if (myislower(i + FirstChar)) { int upper = i - 32; if (upper >= 0) @@ -2034,6 +2085,17 @@ void FFont::FixXMoves() continue; } } + // Try an unnaccented character. + int noaccent = stripaccent(i + FirstChar); + if (noaccent != i + FirstChar) + { + noaccent -= FirstChar; + if (noaccent >= 0) + { + Chars[i].XMove = Chars[noaccent].XMove; + continue; + } + } Chars[i].XMove = SpaceWidth; } } @@ -2587,7 +2649,12 @@ void V_InitFonts() } if (!(BigFont = FFont::FindFont("BigFont"))) { - if (gameinfo.gametype & GAME_DoomChex) + int lump = Wads.CheckNumForName("BIGFONT"); + if (lump >= 0) + { + BigFont = new FSingleLumpFont("BigFont", lump); + } + else if (gameinfo.gametype & GAME_DoomChex) { BigFont = new FSingleLumpFont ("BigFont", Wads.GetNumForName ("DBIGFONT")); } @@ -2597,7 +2664,15 @@ void V_InitFonts() } else { - BigFont = new FFont ("BigFont", "FONTB%02u", HU_FONTSTART, HU_FONTSIZE, 1, -1); + lump = Wads.CheckNumForName("HBIGFONT"); + if (lump >= 0) + { + BigFont = new FSingleLumpFont("BigFont", lump); + } + else + { + BigFont = new FFont ("BigFont", "FONTB%02u", HU_FONTSTART, HU_FONTSIZE, 1, -1); + } } } if (!(ConFont = FFont::FindFont("ConsoleFont"))) diff --git a/src/v_video.cpp b/src/v_video.cpp index e6145cc95..855b9fda3 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -491,7 +491,7 @@ int V_GetColorFromString (const DWORD *palette, const char *cstr) else { normal: - // Treat it as a space-delemited hexadecimal string + // Treat it as a space-delimited hexadecimal string for (i = 0; i < 3; ++i) { // Skip leading whitespace @@ -1605,7 +1605,7 @@ void V_Init2() FBaseCVar::ResetColors (); C_NewModeAdjust(); M_InitVideoModesMenu(); - BorderNeedRefresh = screen->GetPageCount (); + V_SetBorderNeedRefresh(); setsizeneeded = true; } diff --git a/src/v_video.h b/src/v_video.h index f6a1caeba..09022d9ff 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -485,6 +485,7 @@ void V_DrawFrame (int left, int top, int width, int height); void V_DrawBorder (int x1, int y1, int x2, int y2); void V_RefreshViewBorder (); +void V_SetBorderNeedRefresh(); #if defined(X86_ASM) || defined(X64_ASM) extern "C" void ASM_PatchPitch (void); diff --git a/src/vectors.h b/src/vectors.h index c0783e250..c1bcf74b9 100644 --- a/src/vectors.h +++ b/src/vectors.h @@ -439,7 +439,7 @@ struct TVector3 // Vector subtraction TVector3 &operator-= (const TVector3 &other) { - X -= other.X, Y -= other.Y, Z - other.Z; + X -= other.X, Y -= other.Y, Z -= other.Z; return *this; } @@ -1166,7 +1166,7 @@ struct TRotator // Vector subtraction TRotator &operator-= (const TRotator &other) { - Pitch -= other.Pitch, Yaw -= other.Yaw, Roll - other.Roll; + Pitch -= other.Pitch, Yaw -= other.Yaw, Roll -= other.Roll; return *this; } diff --git a/src/w_wad.cpp b/src/w_wad.cpp index 84da1c474..11a9b4213 100644 --- a/src/w_wad.cpp +++ b/src/w_wad.cpp @@ -55,6 +55,7 @@ #include "gi.h" #include "doomerrors.h" #include "resourcefiles/resourcefile.h" +#include "md5.h" // MACROS ------------------------------------------------------------------ @@ -180,6 +181,7 @@ void FWadCollection::InitMultipleFiles (TArray &filenames) { I_FatalError ("W_InitMultipleFiles: no files found"); } + RenameNerve(); RenameSprites(); // [RH] Set up hash table @@ -810,6 +812,71 @@ void FWadCollection::RenameSprites () } } +//========================================================================== +// +// RenameNerve +// +// Renames map headers and map name pictures in nerve.wad so as to load it +// alongside Doom II and offer both episodes without causing conflicts. +// MD5 checksum for NERVE.WAD: 967d5ae23daf45196212ae1b605da3b0 +// +//========================================================================== +void FWadCollection::RenameNerve () +{ + if (gameinfo.gametype != GAME_Doom) + return; + + bool found = false; + BYTE cksum[16]; + static const BYTE nerve[16] = { 0x96, 0x7d, 0x5a, 0xe2, 0x3d, 0xaf, 0x45, 0x19, + 0x62, 0x12, 0xae, 0x1b, 0x60, 0x5d, 0xa3, 0xb0 }; + size_t nervesize = 3819855; // NERVE.WAD's file size + int w = IWAD_FILENUM; + while (++w < GetNumWads()) + { + FileReader *fr = GetFileReader(w); + if (fr == NULL) + { + continue; + } + if (fr->GetLength() != (long)nervesize) + { + // Skip MD5 computation when there is a + // cheaper way to know this is not the file + continue; + } + fr->Seek(0, SEEK_SET); + MD5Context md5; + md5.Update(fr, fr->GetLength()); + md5.Final(cksum); + if (memcmp(nerve, cksum, 16) == 0) + { + found = true; + break; + } + } + + if (!found) + return; + + for (int i = GetFirstLump(w); i <= GetLastLump(w); i++) + { + // Only rename the maps from NERVE.WAD + assert(LumpInfo[i].wadnum == w); + if (LumpInfo[i].lump->dwName == MAKE_ID('C', 'W', 'I', 'L')) + { + LumpInfo[i].lump->Name[0] = 'N'; + } + else if (LumpInfo[i].lump->dwName == MAKE_ID('M', 'A', 'P', '0')) + { + LumpInfo[i].lump->Name[6] = LumpInfo[i].lump->Name[4]; + LumpInfo[i].lump->Name[5] = '0'; + LumpInfo[i].lump->Name[4] = 'L'; + LumpInfo[i].lump->dwName = MAKE_ID('L', 'E', 'V', 'E'); + } + } +} + //========================================================================== // // W_FindLump diff --git a/src/w_wad.h b/src/w_wad.h index 76d94959d..e0b21b66c 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -227,7 +227,8 @@ protected: void InitHashChains (); // [RH] Set up the lumpinfo hashing private: - void RenameSprites (); + void RenameSprites(); + void RenameNerve(); void DeleteAll(); }; diff --git a/src/wi_stuff.cpp b/src/wi_stuff.cpp index 41981c45f..37b418ce9 100644 --- a/src/wi_stuff.cpp +++ b/src/wi_stuff.cpp @@ -253,8 +253,8 @@ static FTexture* sucks; static FTexture* killers; // "killers", "victims" static FTexture* victims; static FTexture* total; // "Total", your face, your dead face -static FTexture* star; -static FTexture* bstar; +//static FTexture* star; +//static FTexture* bstar; static FTexture* p; // Player graphic static FTexture* lnames[2]; // Name graphics of each level (centered) @@ -1655,7 +1655,7 @@ void WI_drawNetgameStats () FTexture *pic = TexMan[player->mo->ScoreIcon]; screen->DrawTexture(pic, icon_x, y, DTA_CleanNoMove, true, TAG_DONE); } - screen->DrawText(SmallFont, color, name_x, y + ypadding, player->userinfo.netname, DTA_CleanNoMove, true, TAG_DONE); + screen->DrawText(SmallFont, color, name_x, y + ypadding, player->userinfo.GetName(), DTA_CleanNoMove, true, TAG_DONE); WI_drawPercent(SmallFont, kills_x, y + ypadding, cnt_kills[i], wbs->maxkills, false, color); missed_kills -= cnt_kills[i]; if (ng_state >= 4) @@ -1995,10 +1995,11 @@ void WI_loadData(void) killers = TexMan["WIKILRS"]; // "killers" (vertical] victims = TexMan["WIVCTMS"]; // "victims" (horiz] total = TexMan["WIMSTT"]; // "total" - star = TexMan["STFST01"]; // your face - bstar = TexMan["STFDEAD0"]; // dead face +// star = TexMan["STFST01"]; // your face +// bstar = TexMan["STFDEAD0"]; // dead face p = TexMan["STPBANY"]; } +#if 0 else if (gameinfo.gametype & GAME_Raven) { if (gameinfo.gametype == GAME_Heretic) @@ -2017,6 +2018,7 @@ void WI_loadData(void) star = BigFont->GetChar('*', NULL); bstar = star; } +#endif // Use the local level structure which can be overridden by hubs lnametexts[0] = level.LevelName; diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp index 3598f52d1..648104271 100644 --- a/src/win32/fb_d3d9.cpp +++ b/src/win32/fb_d3d9.cpp @@ -66,7 +66,7 @@ #include "r_main.h" #include "r_data/r_translate.h" #include "f_wipe.h" -#include "st_stuff.h" +#include "sbar.h" #include "win32iface.h" #include "doomstat.h" #include "v_palette.h" @@ -225,7 +225,8 @@ const char *const D3DFB::ShaderNames[D3DFB::NUM_SHADERS] = CUSTOM_CVAR(Bool, vid_hw2d, true, CVAR_NOINITCALL) { - BorderNeedRefresh = SB_state = screen->GetPageCount(); + V_SetBorderNeedRefresh(); + ST_SetNeedRefresh(); } CVAR(Bool, d3d_antilag, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) diff --git a/tools/lemon/CMakeLists.txt b/tools/lemon/CMakeLists.txt index 0a6b2319c..91b144575 100644 --- a/tools/lemon/CMakeLists.txt +++ b/tools/lemon/CMakeLists.txt @@ -5,6 +5,13 @@ set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG" ) add_executable( lemon lemon.c ) # Lemon wants lempar.c in its directory +if( MSVC ) +add_custom_command( TARGET lemon + POST_BUILD + COMMAND echo $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/lempar.c $ ) +else( MSVC ) add_custom_command( TARGET lemon POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/lempar.c ${CMAKE_CURRENT_BINARY_DIR} ) +endif( MSVC ) \ No newline at end of file diff --git a/wadsrc/CMakeLists.txt b/wadsrc/CMakeLists.txt index 19a210abc..422052511 100644 --- a/wadsrc/CMakeLists.txt +++ b/wadsrc/CMakeLists.txt @@ -1,9 +1,11 @@ cmake_minimum_required( VERSION 2.4 ) - +get_target_property(ZIPDIR_EXE zipdir LOCATION) + add_custom_command( OUTPUT ${ZDOOM_OUTPUT_DIR}/zdoom.pk3 - COMMAND ${CMAKE_BINARY_DIR}/tools/zipdir/zipdir -udf ${ZDOOM_OUTPUT_DIR}/zdoom.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static - DEPENDS zipdir ${CMAKE_CURRENT_SOURCE_DIR}/static ) - + COMMAND ${ZIPDIR_EXE} -udf ${ZDOOM_OUTPUT_DIR}/zdoom.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ZDOOM_OUTPUT_DIR}/zdoom.pk3 $ + DEPENDS zipdir ${CMAKE_CURRENT_SOURCE_DIR}/static ) + add_custom_target( pk3 ALL DEPENDS ${ZDOOM_OUTPUT_DIR}/zdoom.pk3 ) diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 1f50643da..5e1f7a30b 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -16,6 +16,7 @@ ACTOR Actor native //: Thinker WallBounceFactor 0.75 BounceCount -1 FloatSpeed 4 + FloatBobPhase -1 // randomly initialize by default Gravity 1 DamageFactor 1.0 PushFactor 0.25 @@ -56,6 +57,10 @@ ACTOR Actor native //: Thinker native int score; native int accuracy; native int stamina; + native fixed_t height; + native fixed_t radius; + native int reactiontime; + native fixed_t meleerange; // Meh, MBF redundant functions. Only for DeHackEd support. action native A_Turn(float angle = 0); @@ -196,7 +201,7 @@ ACTOR Actor native //: Thinker action native A_Jump(int chance = 256, state label, ...); action native A_CustomMissile(class missiletype, float spawnheight = 32, int spawnofs_xy = 0, float angle = 0, int flags = 0, float pitch = 0); action native A_CustomBulletAttack(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", float range = 0, int flags = 0); - action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, int aim = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none"); + action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, int aim = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none", float spawnofs_z = 0); action native A_JumpIfHealthLower(int health, state label); action native A_JumpIfCloser(float distance, state label); action native A_JumpIfTracerCloser(float distance, state label); @@ -208,7 +213,7 @@ ACTOR Actor native //: Thinker action native A_GiveInventory(class itemtype, int amount = 0, int giveto = AAPTR_DEFAULT); action native A_TakeInventory(class itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT); action native A_SpawnItem(class itemtype = "Unknown", float distance = 0, float zheight = 0, bool useammo = true, bool transfer_translation = false); - action native A_SpawnItemEx(class itemtype, float xofs = 0, float yofs = 0, float zofs = 0, float xvel = 0, float yvel = 0, float zvel = 0, float angle = 0, int flags = 0, int failchance = 0); + action native A_SpawnItemEx(class itemtype, float xofs = 0, float yofs = 0, float zofs = 0, float xvel = 0, float yvel = 0, float zvel = 0, float angle = 0, int flags = 0, int failchance = 0, int tid=0); action native A_Print(string whattoprint, float time = 0, name fontname = ""); action native A_PrintBold(string whattoprint, float time = 0, name fontname = ""); action native A_Log(string whattoprint); @@ -265,6 +270,7 @@ ACTOR Actor native //: Thinker action native A_LookEx(int flags = 0, float minseedist = 0, float maxseedist = 0, float maxheardist = 0, float fov = 0, state label = ""); action native A_ClearLastHeard(); action native A_ClearTarget(); + action native A_CheckLOF(state jump, int flags = 0, float range = 0, float minrange = 0, float angle = 0, float pitch = 0, float offsetheight = 0, float offsetwidth = 0, int ptr_target = AAPTR_DEFAULT); action native A_JumpIfTargetInLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); action native A_JumpIfInTargetLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); action native A_DamageMaster(int amount, name damagetype = "none"); @@ -293,6 +299,7 @@ ACTOR Actor native //: Thinker action native A_SetSpecial(int spec, int arg0 = 0, int arg1 = 0, int arg2 = 0, int arg3 = 0, int arg4 = 0); action native A_Quake(int intensity, int duration, int damrad, int tremrad, sound sfx = "world/quake"); action native A_SetTics(int tics); + action native A_SetDamageType(name damagetype); action native A_CheckSightOrRange(float distance, state label); diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 1c3cf8c7d..b838e9bac 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -27,6 +27,7 @@ const int CBAF_AIMFACING = 1; const int CBAF_NORANDOM = 2; const int CBAF_EXPLICITANGLE = 4; const int CBAF_NOPITCH = 8; +const int CBAF_NORANDOMPUFFZ = 16; // Flags for A_GunFlash const int GFF_NOEXTCHANGE = 1; @@ -37,20 +38,27 @@ const int FBF_NORANDOM = 2; const int FBF_EXPLICITANGLE = 4; const int FBF_NOPITCH = 8; const int FBF_NOFLASH = 16; +const int FBF_NORANDOMPUFFZ = 32; // Flags for A_SpawnItemEx -const int SXF_TRANSFERTRANSLATION=1; -const int SXF_ABSOLUTEPOSITION=2; -const int SXF_ABSOLUTEANGLE=4; -const int SXF_ABSOLUTEMOMENTUM=8; -const int SXF_ABSOLUTEVELOCITY=8; -const int SXF_SETMASTER=16; +const int SXF_TRANSFERTRANSLATION = 1; +const int SXF_ABSOLUTEPOSITION = 2; +const int SXF_ABSOLUTEANGLE = 4; +const int SXF_ABSOLUTEMOMENTUM = 8; +const int SXF_ABSOLUTEVELOCITY = 8; +const int SXF_SETMASTER = 16; const int SXF_NOCHECKPOSITION = 32; -const int SXF_TELEFRAG=64; -const int SXF_CLIENTSIDE=128; // only used by Skulltag -const int SXF_TRANSFERAMBUSHFLAG=256; -const int SXF_TRANSFERPITCH=512; -const int SXF_TRANSFERPOINTERS=1024; +const int SXF_TELEFRAG = 64; +const int SXF_CLIENTSIDE = 128; // only used by Skulltag +const int SXF_TRANSFERAMBUSHFLAG = 256; +const int SXF_TRANSFERPITCH = 512; +const int SXF_TRANSFERPOINTERS = 1024; +const int SXF_USEBLOODCOLOR = 2048; +const int SXF_CLEARCALLERTID = 4096; +const int SXF_MULTIPLYSPEED = 8192; +const int SXF_TRANSFERSCALE = 16384; +const int SXF_TRANSFERSPECIAL = 32768; +const int SXF_CLEARCALLERSPECIAL = 65536; // Flags for A_Chase const int CHF_FASTCHASE = 1; @@ -84,6 +92,7 @@ const int JLOSF_TARGETLOS = 128; const int JLOSF_FLIPFOV = 256; const int JLOSF_ALLYNOJUMP = 512; const int JLOSF_COMBATANTONLY = 1024; +const int JLOSF_NOAUTOAIM = 2048; // Flags for A_ChangeVelocity const int CVF_RELATIVE = 1; @@ -91,12 +100,13 @@ const int CVF_REPLACE = 2; // Flags for A_WeaponReady const int WRF_NOBOB = 1; -const int WRF_NOFIRE = 12; const int WRF_NOSWITCH = 2; const int WRF_NOPRIMARY = 4; const int WRF_NOSECONDARY = 8; +const int WRF_NOFIRE = WRF_NOPRIMARY | WRF_NOSECONDARY; const int WRF_ALLOWRELOAD = 16; const int WRF_ALLOWZOOM = 32; +const int WRF_DISABLESWITCH = 64; // Morph constants const int MRF_ADDSTAMINA = 1; @@ -148,10 +158,10 @@ const int SMF_CURSPEED = 4; const int CPF_USEAMMO = 1; const int CPF_DAGGER = 2; const int CPF_PULLIN = 4; +const int CPF_NORANDOMPUFFZ = 8; // Flags for A_Teleport -const int TF_TELEFRAG = 1; -const int TF_RANDOMDECIDE = 2; +const int TF_TELEFRAG = 1;const int TF_RANDOMDECIDE = 2; // Flags for A_WolfAttack const int WAF_NORANDOM = 1; @@ -292,3 +302,44 @@ Const Int WARPF_TESTONLY = 0x200; // flags for A_SetPitch const int SPF_FORCECLAMP = 1; + + +// flags for A_CheckLOF + +enum +{ + CLOFF_NOAIM_VERT = 0x1, + CLOFF_NOAIM_HORZ = 0x2, + + CLOFF_JUMPENEMY = 0x4, + CLOFF_JUMPFRIEND = 0x8, + CLOFF_JUMPOBJECT = 0x10, + CLOFF_JUMPNONHOSTILE = 0x20, + + CLOFF_SKIPENEMY = 0x40, + CLOFF_SKIPFRIEND = 0x80, + CLOFF_SKIPOBJECT = 0x100, + CLOFF_SKIPNONHOSTILE = 0x200, + + CLOFF_MUSTBESHOOTABLE = 0x400, + + CLOFF_SKIPTARGET = 0x800, + CLOFF_ALLOWNULL = 0x1000, + CLOFF_CHECKPARTIAL = 0x2000, + + CLOFF_MUSTBEGHOST = 0x4000, + CLOFF_IGNOREGHOST = 0x8000, + + CLOFF_MUSTBESOLID = 0x10000, + CLOFF_BEYONDTARGET = 0x20000, + + CLOFF_FROMBASE = 0x40000, + CLOFF_MUL_HEIGHT = 0x80000, + CLOFF_MUL_WIDTH = 0x100000, + + CLOFF_JUMP_ON_MISS = 0x200000, + CLOFF_AIM_VERT_NOOFFSET = 0x400000, + + CLOFF_SKIPOBSTACLES = CLOFF_SKIPENEMY|CLOFF_SKIPFRIEND|CLOFF_SKIPOBJECT|CLOFF_SKIPNONHOSTILE, + CLOFF_NOAIM = CLOFF_NOAIM_VERT|CLOFF_NOAIM_HORZ +}; diff --git a/wadsrc/static/actors/doom/doommisc.txt b/wadsrc/static/actors/doom/doommisc.txt index 0b481427f..131bb273d 100644 --- a/wadsrc/static/actors/doom/doommisc.txt +++ b/wadsrc/static/actors/doom/doommisc.txt @@ -6,7 +6,7 @@ ACTOR ExplosiveBarrel 2035 SpawnID 125 Health 20 Radius 10 - Height 34 + Height 42 +SOLID +SHOOTABLE +NOBLOOD diff --git a/wadsrc/static/actors/doom/doomweapons.txt b/wadsrc/static/actors/doom/doomweapons.txt index 4b0386524..8be33c0ca 100644 --- a/wadsrc/static/actors/doom/doomweapons.txt +++ b/wadsrc/static/actors/doom/doomweapons.txt @@ -21,7 +21,7 @@ ACTOR Fist : Weapon Weapon.SelectionOrder 3700 Weapon.Kickback 100 Obituary "$OB_MPFIST" - Tag "$FIST" + Tag "$TAG_FIST" +WEAPON.WIMPY_WEAPON +WEAPON.MELEEWEAPON States diff --git a/wadsrc/static/actors/doom/scriptedmarine.txt b/wadsrc/static/actors/doom/scriptedmarine.txt index 820499557..fecd0ac0d 100644 --- a/wadsrc/static/actors/doom/scriptedmarine.txt +++ b/wadsrc/static/actors/doom/scriptedmarine.txt @@ -18,7 +18,7 @@ ACTOR ScriptedMarine 9100 native DeathSound "*death" PainSound "*pain50" - action native A_M_Refire (); + action native A_M_Refire (bool ignoremissile=false); action native A_M_CheckAttack (); action native A_MarineChase (); action native A_MarineLook (); @@ -55,24 +55,24 @@ ACTOR ScriptedMarine 9100 native Melee.Fist: PLAY E 4 A_FaceTarget - PLAY F 4 A_M_Punch(1) + PLAY E 4 A_M_Punch(1) PLAY A 9 - PLAY A 0 A_M_Refire + PLAY A 0 A_M_Refire(1) Loop PLAY A 5 A_FaceTarget Goto See Melee.Berserk: PLAY E 4 A_FaceTarget - PLAY F 4 A_M_Punch(10) + PLAY E 4 A_M_Punch(10) PLAY A 9 - PLAY A 0 A_M_Refire + PLAY A 0 A_M_Refire(1) Loop PLAY A 5 A_FaceTarget Goto See Melee.Chainsaw: PLAY E 4 A_MarineNoise - PLAY F 4 BRIGHT A_M_Saw - PLAY F 0 A_M_SawRefire + PLAY E 4 A_M_Saw + PLAY E 0 A_M_SawRefire goto Melee.Chainsaw+1 PLAY A 0 Goto See @@ -116,25 +116,27 @@ ACTOR ScriptedMarine 9100 native Missile.Rocket: PLAY E 8 PLAY F 6 BRIGHT A_M_FireMissile + PLAY E 6 PLAY A 0 A_M_Refire Loop PLAY A 0 Goto See Missile.Plasma: PLAY E 2 A_FaceTarget + PLAY E 0 A_FaceTarget PLAY F 3 BRIGHT A_M_FirePlasma PLAY A 0 A_M_Refire - Loop + Goto Missile.Plasma+1 PLAY A 0 Goto See Missile.Railgun: PLAY E 4 A_M_CheckAttack - PLAY F 6 A_M_FireRailgun + PLAY F 6 BRIGHT A_M_FireRailgun Goto See Missile.BFG: PLAY E 5 A_M_BFGSound PLAY EEEEE 5 A_FaceTarget - PLAY F 6 A_M_FireBFG + PLAY F 6 BRIGHT A_M_FireBFG PLAY A 4 A_FaceTarget PLAY A 0 A_M_Refire Loop diff --git a/wadsrc/static/actors/heretic/dsparil.txt b/wadsrc/static/actors/heretic/dsparil.txt index 79d457ae7..6ecef5b5c 100644 --- a/wadsrc/static/actors/heretic/dsparil.txt +++ b/wadsrc/static/actors/heretic/dsparil.txt @@ -68,10 +68,10 @@ ACTOR Sorcerer1 7 SRCR F 7 A_Scream SRCR G 7 SRCR HIJK 6 - SRCR L 25 A_PlaySoundEx("dsparil/zap", "body") + SRCR L 25 A_PlaySound("dsparil/zap", CHAN_BODY, 1, false, ATTN_NONE) SRCR MN 5 SRCR O 4 - SRCR L 20 A_PlaySoundEx("dsparil/zap", "body") + SRCR L 20 A_PlaySound("dsparil/zap", CHAN_BODY, 1, false, ATTN_NONE) SRCR MN 5 SRCR O 4 SRCR L 12 @@ -152,9 +152,9 @@ ACTOR Sorcerer2 Loop Rise: SOR2 AB 4 - SOR2 C 4 A_PlaySoundEx("dsparil/rise", "Body") + SOR2 C 4 A_PlaySound("dsparil/rise", CHAN_BODY, 1, false, ATTN_NONE) SOR2 DEF 4 - SOR2 G 12 A_PlaySoundEx("dsparil/sight", "Body") + SOR2 G 12 A_PlaySound("dsparil/sight", CHAN_BODY, 1, false, ATTN_NONE) Goto See Pain: SOR2 Q 3 @@ -171,15 +171,15 @@ ACTOR Sorcerer2 Death: SDTH A 8 A_Sor2DthInit SDTH B 8 - SDTH C 8 A_PlaySoundEx("dsparil/scream", "Body") + SDTH C 8 A_PlaySound("dsparil/scream", CHAN_BODY, 1, false, ATTN_NONE) DeathLoop: SDTH DE 7 SDTH F 7 A_Sor2DthLoop - SDTH G 6 A_PlaySoundEx("dsparil/explode", "Body") + SDTH G 6 A_PlaySound("dsparil/explode", CHAN_BODY, 1, false, ATTN_NONE) SDTH H 6 SDTH I 18 SDTH J 6 A_NoBlocking - SDTH K 6 A_PlaySoundEx("dsparil/bones", "Body") + SDTH K 6 A_PlaySound("dsparil/bones", CHAN_BODY, 1, false, ATTN_NONE) SDTH LMN 6 SDTH O -1 A_BossDeath Stop diff --git a/wadsrc/static/actors/heretic/ironlich.txt b/wadsrc/static/actors/heretic/ironlich.txt index 4b7e0ff17..21234cf52 100644 --- a/wadsrc/static/actors/heretic/ironlich.txt +++ b/wadsrc/static/actors/heretic/ironlich.txt @@ -40,6 +40,10 @@ ACTOR Ironlich 6 LICH A 5 A_FaceTarget LICH B 20 A_LichAttack Goto See + Pain: + LICH A 4 + LICH A 4 A_Pain + Goto See Death: LICH C 7 LICH D 7 A_Scream diff --git a/wadsrc/static/actors/hexen/demons.txt b/wadsrc/static/actors/hexen/demons.txt index cb1428ee7..1ecb2222c 100644 --- a/wadsrc/static/actors/hexen/demons.txt +++ b/wadsrc/static/actors/hexen/demons.txt @@ -20,7 +20,7 @@ ACTOR Demon1 31 DeathSound "DemonDeath" ActiveSound "DemonActive" Obituary "$OB_DEMON1" - const int ChunkFlags = SXF_TRANSFERTRANSLATION|SXF_ABSOLUTEVELOCITY; + const int ChunkFlags = SXF_TRANSFERTRANSLATION | SXF_ABSOLUTEVELOCITY; States { Spawn: @@ -53,11 +53,11 @@ ACTOR Demon1 31 Stop XDeath: DEMN H 6 - DEMN I 0 A_SpawnItemEx("Demon1Chunk1", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 90) - DEMN I 0 A_SpawnItemEx("Demon1Chunk2", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) - DEMN I 0 A_SpawnItemEx("Demon1Chunk3", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) - DEMN I 0 A_SpawnItemEx("Demon1Chunk4", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) - DEMN I 6 A_SpawnItemEx("Demon1Chunk5", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) + DEMN I 0 A_SpawnItemEx("Demon1Chunk1", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle+90), frandom[DemonChunks](1,4.984375)*sin(Angle+90), 8, 90, ChunkFlags) + DEMN I 0 A_SpawnItemEx("Demon1Chunk2", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) + DEMN I 0 A_SpawnItemEx("Demon1Chunk3", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) + DEMN I 0 A_SpawnItemEx("Demon1Chunk4", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) + DEMN I 6 A_SpawnItemEx("Demon1Chunk5", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) Goto Death+2 Ice: DEMN Q 5 A_FreezeDeath @@ -249,11 +249,11 @@ ACTOR Demon2 : Demon1 8080 Stop XDeath: DEM2 H 6 - DEM2 I 0 A_SpawnItemEx("Demon2Chunk1", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 90) - DEM2 I 0 A_SpawnItemEx("Demon2Chunk2", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) - DEM2 I 0 A_SpawnItemEx("Demon2Chunk3", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) - DEM2 I 0 A_SpawnItemEx("Demon2Chunk4", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) - DEM2 I 6 A_SpawnItemEx("Demon2Chunk5", 0,0,45, 1+(random[DemonChunks](0,255)*0.015625), 1+(random[DemonChunks](0,255)*0.015625), ChunkFlags, 270) + DEM2 I 0 A_SpawnItemEx("Demon2Chunk1", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle+90), frandom[DemonChunks](1,4.984375)*sin(Angle+90), 8, 90, ChunkFlags) + DEM2 I 0 A_SpawnItemEx("Demon2Chunk2", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) + DEM2 I 0 A_SpawnItemEx("Demon2Chunk3", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) + DEM2 I 0 A_SpawnItemEx("Demon2Chunk4", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) + DEM2 I 6 A_SpawnItemEx("Demon2Chunk5", 0,0,45, frandom[DemonChunks](1,4.984375)*cos(Angle-90), frandom[DemonChunks](1,4.984375)*sin(Angle-90), 8, 270, ChunkFlags) Goto Death+2 } } diff --git a/wadsrc/static/actors/hexen/firedemon.txt b/wadsrc/static/actors/hexen/firedemon.txt index cb29cdb07..4873e2609 100644 --- a/wadsrc/static/actors/hexen/firedemon.txt +++ b/wadsrc/static/actors/hexen/firedemon.txt @@ -46,6 +46,7 @@ ACTOR FireDemon 10060 FDMN ABC 5 Bright A_FiredChase Loop Pain: + FDMN D 0 Bright A_UnSetInvulnerable FDMN D 6 Bright A_Pain Goto Chase Missile: diff --git a/wadsrc/static/actors/hexen/pig.txt b/wadsrc/static/actors/hexen/pig.txt index 45b649597..a99789b5b 100644 --- a/wadsrc/static/actors/hexen/pig.txt +++ b/wadsrc/static/actors/hexen/pig.txt @@ -73,7 +73,6 @@ ACTOR PigPlayer : PlayerPawn native Player.SoundClass "Pig" Player.DisplayName "Pig" Player.MorphWeapon "Snout" - -PICKUP States { Spawn: diff --git a/wadsrc/static/actors/raven/artitele.txt b/wadsrc/static/actors/raven/artitele.txt index 1f0fe73d2..79811fd53 100644 --- a/wadsrc/static/actors/raven/artitele.txt +++ b/wadsrc/static/actors/raven/artitele.txt @@ -4,6 +4,7 @@ ACTOR ArtiTeleport : Inventory 36 native { Game Raven + SpawnID 18 +COUNTITEM +FLOATBOB +INVENTORY.INVBAR diff --git a/wadsrc/static/actors/shared/inventory.txt b/wadsrc/static/actors/shared/inventory.txt index 957b3943f..9bd936a6e 100644 --- a/wadsrc/static/actors/shared/inventory.txt +++ b/wadsrc/static/actors/shared/inventory.txt @@ -11,7 +11,7 @@ ACTOR Inventory native action native A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", float range = 0, float lifesteal = 0); action native A_FireBullets(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, float range = 0); action native A_FireCustomMissile(class missiletype, float angle = 0, bool useammo = true, int spawnofs_xy = 0, float spawnheight = 0, bool aimatangle = false, float pitch = 0); - action native A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none"); + action native A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none", float spawnofs_z = 0); action native A_Light(int extralight); action native A_Light0(); action native A_Light1(); @@ -142,6 +142,7 @@ Actor HealthPickup : Inventory native Actor Key : Inventory native { + +DONTGIB // Don't disappear due to a crusher +INVENTORY.INTERHUBSTRIP Inventory.PickupSound "misc/k_pkup" } @@ -226,7 +227,7 @@ ACTOR PowerWeaponLevel2 : Powerup native Inventory.Icon "SPINBK0" } -ACTOR PowerSpeed: Powerup native +ACTOR PowerSpeed : Powerup native { Powerup.Duration -45 Speed 1.5 diff --git a/wadsrc/static/actors/strife/peasants.txt b/wadsrc/static/actors/strife/peasants.txt index c0e988558..e7bbdadcf 100644 --- a/wadsrc/static/actors/strife/peasants.txt +++ b/wadsrc/static/actors/strife/peasants.txt @@ -12,6 +12,8 @@ ACTOR Peasant : StrifeHumanoid +FRIENDLY -COUNTKILL +NOSPLASHALERT + +FLOORCLIP + +JUSTHIT MinMissileChance 150 MaxStepHeight 16 MaxDropoffHeight 32 @@ -27,7 +29,7 @@ ACTOR Peasant : StrifeHumanoid Loop See: PEAS AABBCCDD 5 A_Wander - Loop + Goto Spawn Melee: PEAS E 10 A_FaceTarget PEAS F 8 A_CustomMeleeAttack(2*random[PeasantAttack](1,5)+2) diff --git a/wadsrc/static/actors/strife/strifestuff.txt b/wadsrc/static/actors/strife/strifestuff.txt index a80685c35..b0f4024f4 100644 --- a/wadsrc/static/actors/strife/strifestuff.txt +++ b/wadsrc/static/actors/strife/strifestuff.txt @@ -365,8 +365,8 @@ ACTOR PillarAlienPower 227 States { Spawn: - APOW A -1 A_LoopActiveSound - Stop + APOW A 4 A_LoopActiveSound + Loop } } @@ -510,7 +510,7 @@ ACTOR WaterDropOnFloor 103 Game Strife +NOBLOCKMAP ConversationID 224, -1, -1 - ActiveSound "world/waterdrips" + ActiveSound "world/waterdrip" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifeweapons.txt b/wadsrc/static/actors/strife/strifeweapons.txt index 5e3a419d5..deea01a52 100644 --- a/wadsrc/static/actors/strife/strifeweapons.txt +++ b/wadsrc/static/actors/strife/strifeweapons.txt @@ -534,7 +534,6 @@ ACTOR Mauler2 : Mauler Weapon.AmmoGive1 0 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler" - Obituary "$OB_MPMAULER2" Tag "$TAG_MAULER2" action native A_FireMauler2Pre (); diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt index e6b3af2fb..795c7f0b0 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -141,12 +141,16 @@ DCE862393CAAA6FF1294FB7056B53057 // UAC Ultra map07: Contains a scroller dependi } 1D9E43988940CCD3555724E15DD8B1AB // Happy Time Circus map01 has bad teleporters +040F83028FFA74456E335ED300BE2C33 // MAP17 (TownInfection.wad) +156FA31F5FF72A87BF345B68029D3051 // MAP02 (nprject5.wad) +AF40D0E49BD1B76D4B1AADD8212ADC46 // MAP01 (the wad that shall not be named =P) { ignoreteleporttags } 8FF30D57F6CE64F085A6567EC302842A // Enjay's test map for this flag 9D7893D09FEE55C31B73C670DF850962 // Memento Mori II, map12 +91E173901E6F3800B81AB646976247CD // Claustrophobia 1024, map01 { badangles } @@ -284,3 +288,55 @@ F481922F4881F74760F3C0437FD5EDD0 // map03 clearlinespecial 1107 clearlinespecial 1108 } + +5B862477519B21B30059A466F2FF6460 // Khorus, map08 +{ + // This map uses a voodoo conveyor with slanted walls to shunt the + // voodoo doll into side areas. For some reason, this voodoo doll + // is unable to slide on them, because the slide calculation gets + // them slightly inside the walls and thinks they are stuck. I could + // not reproduce this with the real player, which is what has me + // stumped. So, help them out be attaching some ThrustThing specials + // to the walls. + setlinespecial 443 ThrustThing 96 4 0 0 0 + setlineflags 443 0x200 // repeatable + setactivation 443 16 // SPAC_Push + setlinespecial 455 ThrustThing 96 4 0 0 0 + setlineflags 455 0x200 // repeatable + setactivation 455 16 // SPAC_Push +} + +6DC9F6CCEAE7A91AEC48EBE506F22BC4 // Void +{ + // Slightly squash the pillars in the starting room with "stimpacks" + // floating on them so that they can be obtained. + sectorflooroffset 62 -8 + setwallyscale 286 front bot 1.090909 + setwallyscale 287 front bot 1.090909 + setwallyscale 288 front bot 1.090909 + setwallyscale 289 front bot 1.090909 + + sectorflooroffset 63 -8 + setwallyscale 290 front bot 1.090909 + setwallyscale 291 front bot 1.090909 + setwallyscale 292 front bot 1.090909 + setwallyscale 293 front bot 1.090909 + + sectorflooroffset 118 -8 + setwallyscale 710 front bot 1.090909 + setwallyscale 711 front bot 1.090909 + setwallyscale 712 front bot 1.090909 + setwallyscale 713 front bot 1.090909 + + sectorflooroffset 119 -8 + setwallyscale 714 front bot 1.090909 + setwallyscale 715 front bot 1.090909 + setwallyscale 716 front bot 1.090909 + setwallyscale 717 front bot 1.090909 +} + +65A1EB4C87386F290816660A52932FF1 // Master Levels, garrison.wad +{ + rebuildnodes +} + diff --git a/wadsrc/static/iwadinfo.txt b/wadsrc/static/iwadinfo.txt index c626dcbfa..e47951d31 100644 --- a/wadsrc/static/iwadinfo.txt +++ b/wadsrc/static/iwadinfo.txt @@ -220,6 +220,21 @@ IWad BannerColors = "32 54 43", "c6 dc d1" } +IWad +{ + Name = "DOOM 1: BFG Edition" + Autoname = "Doom1" + Game = "Doom" + Config = "Doom" + Mapinfo = "mapinfo/ultdoom.txt" + Compatibility = "Shorttex" + MustContain = "E1M1","E2M1","E2M2","E2M3","E2M4","E2M5","E2M6","E2M7","E2M8","E2M9", + "E3M1","E3M2","E3M3","E3M4","E3M5","E3M6","E3M7","E3M8","E3M9", + "DPHOOF","BFGGA0","HEADA1","CYBRA1","SPIDA1D1", "E4M2", + "DMENUPIC", "M_ACPT", "M_CAN", "M_EXITO", "M_CHG" + BannerColors = "54 54 54", "a8 a8 a8" +} + IWad { Name = "The Ultimate DOOM" @@ -283,6 +298,19 @@ IWad BannerColors = "a8 00 00", "a8 a8 a8" } +IWad +{ + Name = "DOOM 2: BFG Edition" + Autoname = "Doom2BFG" + Game = "Doom" + Config = "Doom" + Mapinfo = "mapinfo/doom2bfg.txt" + Compatibility = "Shorttex" + MustContain = "MAP01", "DMENUPIC", "M_ACPT", "M_CAN", "M_EXITO", "M_CHG" + BannerColors = "a8 00 00", "a8 a8 a8" + Load = "nerve.wad" +} + // Doom 2 must be last to be checked becaude MAP01 is its only requirement IWad { @@ -300,10 +328,14 @@ IWad Names { + "bfgdoom2.wad" + "doom2bfg.wad" "doom2f.wad" "doom2.wad" "plutonia.wad" "tnt.wad" + "bfgdoom.wad" + "doombfg.wad" "doomu.wad" "doom.wad" "doom1.wad" @@ -315,6 +347,7 @@ Names "hexdemo.wad" "strife1.wad" "strife0.wad" + "strife.wad" "freedoom.wad" "freedoom1.wad" "freedoomu.wad" diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 7564ff85b..0c8fff5d3 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -190,6 +190,19 @@ HUSTR_29 = "level 29: the living end"; HUSTR_30 = "level 30: icon of sin"; HUSTR_31 = "level 31: wolfenstein"; HUSTR_32 = "level 32: grosse"; +HUSTR_31B = "level 31: idkfa"; +HUSTR_32B = "level 32: keen"; +HUSTR_33 = "level 33: betray"; + +NHUSTR_1 = "level 1: the earth base"; +NHUSTR_2 = "level 2: the pain labs"; +NHUSTR_3 = "level 3: canyon of the dead"; +NHUSTR_4 = "level 4: hell mountain"; +NHUSTR_5 = "level 5: vivisection"; +NHUSTR_6 = "level 6: inferno of blood"; +NHUSTR_7 = "level 7: baron's banquet"; +NHUSTR_8 = "level 8: tomb of malevolence"; +NHUSTR_9 = "level 9: march of the demons"; PHUSTR_1 = "level 1: congo"; PHUSTR_2 = "level 2: well of souls"; @@ -540,6 +553,26 @@ T6TEXT = "As you step off the transport, you hear\n" "the stomp of a cyberdemon's iron shoe."; +NERVETEXT = + "TROUBLE WAS BREWING AGAIN IN YOUR FAVORITE\n" + "VACATION SPOT... HELL. SOME CYBERDEMON\n" + "PUNK THOUGHT HE COULD TURN HELL INTO A\n" + "PERSONAL AMUSEMENT PARK, AND MAKE EARTH\n" + "THE TICKET BOOTH.\n" + "\n" + "WELL THAT HALF-ROBOT FREAK SHOW DIDN'T\n" + "KNOW WHO WAS COMING TO THE FAIR. THERE'S\n" + "NOTHING LIKE A SHOOTING GALLERY FULL OF\n" + "HELLSPAWN TO GET THE BLOOD PUMPING...\n" + "\n" + "NOW THE WALLS OF THE DEMON'S LABYRINTH\n" + "ECHO WITH THE SOUND OF HIS METALLIC LIMBS\n" + "HITTING THE FLOOR. HIS DEATH MOAN GURGLES\n" + "OUT THROUGH THE MESS YOU LEFT OF HIS FACE.\n" + "\n" + "THIS RIDE IS CLOSED.\n"; + + // Cast list (must appear in this order) CC_ZOMBIE = "ZOMBIEMAN"; CC_SHOTGUN = "SHOTGUN GUY"; @@ -773,6 +806,7 @@ OB_MPMINIMISSILELAUNCHER = "%o gulped down %k's missile."; OB_MPSTRIFEGRENADE = "%o was inverted by %k's H-E grenade."; OB_MPPHOSPHOROUSGRENADE = "%o took a flame bath in %k's phosphorous pyre."; OB_MPFLAMETHROWER = "%o was barbecued by %k."; +OB_MPMAULER1 = "%o was zapped by %k."; OB_MPMAULER = "%o was viciously vaporized by %k."; OB_MPSIGIL = "%o bowed down to the sheer power of %k's Sigil."; @@ -965,7 +999,7 @@ TAG_SECURITYKEY = "Security Key"; TAG_COREKEY = "Core Key"; // "New_Key1" in the Teaser TAG_MAULERKEY = "Mauler Key"; // "New_Key2" in the Teaser TAG_FACTORYKEY = "Factory Key"; // "New_Key3" in the Teaser -TAG_MINEKEY = "Mine_Key"; // "New_Key4" in the Teaser +TAG_MINEKEY = "Mine Key"; // "New_Key4" in the Teaser TAG_NEWKEY5 = "New Key5"; TAG_ORACLEPASS = "Oracle Pass"; diff --git a/wadsrc/static/language.ptb b/wadsrc/static/language.ptb new file mode 100644 index 000000000..1dc0e36c0 --- /dev/null +++ b/wadsrc/static/language.ptb @@ -0,0 +1,1748 @@ +/* Português Brasileiro. Feito por Patryck Padilha de Oliveira */ +/* Não está totalmente completo */ +/* Doom está traduzido 100% */ +/* Chex(R) Quest traduzido 100% */ +/* Heretic 75% traduzido (algumas telas ainda faltam) */ +/* Hexen ainda não traduzido */ +/* Strife totalmente traduzido neste arquivo, certas partes não serão implementadas no ZDoom */ +/* Plutonia Experiment ainda precisa ser traduzido */ +/* Para atualizações visite: */ +/* http://forum.zdoom.org/viewtopic.php?f=34&t=34682 */ +/* Versão 0.97 - 07/12/2012 */ +/* No final das traduções, tentarei adicionar os outros jogos suportados pelo ZDoom */ + +[ptb pt br] + +D_DEVSTR = "Modo de desenvolvimento ATIVADO.\n"; +D_CDROM = "Rodando do CD/DVD-ROM: zdoom.ini em c:\\zdoomdat\n"; +PRESSKEY = "aperte qualquer tecla."; +PRESSYN = "escolha Sim ou Não"; + +QUITMSG = "deseja mesmo sair?"; +TXT_YES = "Sim"; +TXT_NO = "Não"; + +// Mensagens de saída DooM 1 +QUITMSG1 = "por favor, não saia, \nprecisamos matá-los..."; +QUITMSG2 = "vamo nessa, isso já está\nficando insuportável!"; +QUITMSG3 = "eu não sairia se fosse você.\nWindows é muito mais chato."; +QUITMSG4 = "então você gosta mais do windows\ndo que de mim, certo?"; +QUITMSG5 = "nao vá, eu acho que vi algo\nse mexendo naquele canto!"; +QUITMSG6 = "sabe, na próxima vez,\nvocê já era."; +QUITMSG7 = "vá em frente, saia.\nveja se me preocupo."; + +// Mensagens de saída DooM II +QUITMSG8 = " >sair \n >pedindo permissão para sair\n(tem certeza??)"; +QUITMSG9 = "nao vá agora, um \nburaco negro lhe espera\n na saida!"; +QUITMSG10 = "saia daqui e volte\npara seus programas chatos."; +QUITMSG11 = "se fosse seu chefe, \nvocê estaria demitido!"; +QUITMSG12 = "OK, pode ir.\nmas pode esquecer sua pontuação!"; +QUITMSG13 = "vá. \nquando voltar, estarei lhe esperando\ncom meu BFG."; +QUITMSG14 = "você tem sorte por não\nlhe dar um prêmio por pensar\n em sair."; + +// Mensagens de saída Strife +QUITMSG15 = "aonde pensa que vai?!\n e quanto a nos?"; +QUITMSG16 = "interrompendo carnificina....\n tem certeza disso?"; +QUITMSG17 = "mas voce e a nossa\n unica esperanca!"; +QUITMSG18 = "ninguem vai embora assim.\n"; +QUITMSG19 = "pensei que fosse diferente..."; +QUITMSG20 = "OK! tchau para voce tambem!"; +QUITMSG21 = "pode ir.\nmas sabemos seu esconderijo..."; +QUITMSG22 = "hein? pode repetir?\nvoce deseja sair?"; + +// Mensagens de saída Chex +QUITMSG23 = "Nao saia agora, precisamos\nacabar com eles!"; +QUITMSG24 = "Nao desista --- porque\neles nunca desistirao!"; +QUITMSG25 = "Nao va.\nPrecisamos de sua ajuda!"; +QUITMSG26 = "Espero que tudo isso\nso seja fome."; +QUITMSG27 = "Nao va.\nPrecisamos de sua ajuda!"; +QUITMSG28 = "Nao nos abandone!!!"; +QUITMSG29 = "O verdadeiro Chex(R) Warrior\n nunca desistiria!"; + +LOADNET = "impossível carregar, porque \n está em partida online!\naperte uma tecla."; +QLOADNET = "impossível carregar, porque está online!\n\naperte uma tecla."; +QSAVESPOT = "escolha um slot antes de fazer isso de novo.\n\naperte uma tecla."; +SAVEDEAD = "precisa estar jogando para fazê-lo.\n\naperte uma tecla."; +QSPROMPT = "deseja mesmo salvar aqui?\n\n'%s'\n"; +QLPROMPT = "deseja carregar esse jogo?\n\n'%s'\n"; +NEWGAME = "impossivel iniciar um novo jogo\nenquanto se está online.\n\naperte uma tecla."; +NIGHTMARE = "tem certeza? nem eu mesmo me\nsujeito a jogar nesse modo.\n"; +SWSTRING = "essa e a versao shareware de doom.\nque contem somente o primeiro episódio.\npara continuar, compre a trilogia.\n\naperte uma tecla."; +MSGOFF = "Mensagens DESATIVADO"; +MSGON = "Mensagens ATIVADO"; +NETEND = "nâo pode desligar esse jogo!\n\naperte uma tecla."; +ENDGAME = "tem certeza que deseja fechar esse jogo?\n"; +DOSY = "tem certeza disso?"; +EMPTYSTRING = "V A Z I O"; +GOTARMOR = "Colete equipado."; +GOTMEGA = "Armadura equipada."; +GOTHTHBONUS = "Pegou bônus de vida"; +GOTARMBONUS = "Pegou bônus de proteção (colete)"; +GOTSTIM = "Pegou um kit de primeiros socorros\npequeno."; +GOTMEDINEED = "Pegou um kit de primeiros socorros\n na HORA CERTA!!"; +GOTMEDIKIT = "Pegou um kit de primeiros socorros\ngrande."; +GOTSUPER = "Supercarga!"; +GOTBLUECARD = "Achou a credencial azul."; +GOTYELWCARD = "Achou a credencial amarela."; +GOTREDCARD = "Achou a credencial vermelha."; +GOTBLUESKUL = "Achou o crânio azul."; +GOTYELWSKUL = "Achou o crânio amarelo."; +GOTREDSKUL = "Achou o crânio vermelho."; +GOTINVUL = "Invencibilidade (30 sec)!"; +GOTBERSERK = "Pílulas de força (30 sec)!"; +GOTINVIS = "Invisibilidade (30 sec)"; +GOTSUIT = "Roupa anti-radiação (1 min)"; +GOTMAP = "Mapa do nível"; +GOTVISOR = "Óculos de visão noturna (1 min)"; +GOTMSPHERE = "Megaesfera!"; +GOTCLIP = "Pegou um clipe."; +GOTCLIPBOX = "Caixa de munição equipada."; +GOTROCKET = "Foguete equipado."; +GOTROCKBOX = "Caixa de foguetes equipada."; +GOTCELL = "Pack de energia(pequeno) equipada."; +GOTCELLBOX = "Pack de energia(grande) equipada."; +GOTSHELLS = "Cartuchos de espingarda coletada."; +GOTSHELLBOX = "Caixa de cartuchos de espingarda coletada."; +GOTBACKPACK = "Mochila CHEIA de munição coletada!"; +GOTBFG9000 = "BFG9000 equipado. Uau!"; +GOTCHAINGUN = "Metralhadora equipada!"; +GOTCHAINSAW = "A motoserra, enfim!"; +GOTLAUNCHER = "Lança-foguetes equipada!"; +GOTPLASMA = "Metralhadora de plasma equipada!"; +GOTSHOTGUN = "Espingarda equipada!"; +GOTSHOTGUN2 = "Espingarda de cano duplo equipada!"; +PD_BLUEO = "Precisa de credencial azul para ativar este objeto"; +PD_REDO = "Precisa de credencial vermelha para ativar este objeto"; +PD_YELLOWO = "Precisa de credencial amarela para ativar este objeto"; +PD_BLUEK = "Precisa de credencial azul para ativar esta porta"; +PD_REDK = "Precisa de credencial vermelha para ativar esta porta"; +PD_YELLOWK = "Precisa de credencial amarela para ativar esta porta"; +PD_BLUECO = "Precisa de credencial azul para ativar este objeto"; +PD_REDCO = "Precisa de credencial vermelha para ativar este objeto"; +PD_YELLOWCO = "Precisa de credencial amarela para ativar este objeto"; +PD_BLUESO = "Precisa de crânio azul para ativar este objeto"; +PD_REDSO = "Precisa de crânio vermelho para ativar este objeto"; +PD_YELLOWSO = "Precisa de crânio amarelo para ativar este objeto"; +GGSAVED = "Jogo salvo."; +HUSTR_MSGU = "[Mensagem não enviada]"; +PICKUP_PISTOL_DROPPED = "Pistola equipada."; +BETA_BONUS1 = "Voce pegou uma adaga demoniaca."; +BETA_BONUS2 = "Voce pegou um skullchest."; +BETA_BONUS3 = "Voce pegou um cetro maligno."; +BETA_BONUS4 = "Voce pegou uma biblia nao sagrada."; + +// Níveis (preferi deixar original por compatibilidade) +HUSTR_E1M1 = "E1M1: Hangar"; +HUSTR_E1M2 = "E1M2: Nuclear Plant"; +HUSTR_E1M3 = "E1M3: Toxin Refinery"; +HUSTR_E1M4 = "E1M4: Command Control"; +HUSTR_E1M5 = "E1M5: Phobos Lab"; +HUSTR_E1M6 = "E1M6: Central Processing"; +HUSTR_E1M7 = "E1M7: Computer Station"; +HUSTR_E1M8 = "E1M8: Phobos Anomaly"; +HUSTR_E1M9 = "E1M9: Military Base"; +HUSTR_E2M1 = "E2M1: Deimos Anomaly"; +HUSTR_E2M2 = "E2M2: Containment Area"; +HUSTR_E2M3 = "E2M3: Refinery"; +HUSTR_E2M4 = "E2M4: Deimos Lab"; +HUSTR_E2M5 = "E2M5: Command Center"; +HUSTR_E2M6 = "E2M6: Halls of the Damned"; +HUSTR_E2M7 = "E2M7: Spawning Vats"; +HUSTR_E2M8 = "E2M8: Tower of Babel"; +HUSTR_E2M9 = "E2M9: Fortress of Mystery"; +HUSTR_E3M1 = "E3M1: Hell Keep"; +HUSTR_E3M2 = "E3M2: Slough of Despair"; +HUSTR_E3M3 = "E3M3: Pandemonium"; +HUSTR_E3M4 = "E3M4: House of Pain"; +HUSTR_E3M5 = "E3M5: Unholy Cathedral"; +HUSTR_E3M6 = "E3M6: Mt. Erebus"; +HUSTR_E3M7 = "E3M7: Limbo"; +HUSTR_E3M8 = "E3M8: Dis"; +HUSTR_E3M9 = "E3M9: Warrens"; +HUSTR_E4M1 = "E4M1: Hell Beneath"; +HUSTR_E4M2 = "E4M2: Perfect Hatred"; +HUSTR_E4M3 = "E4M3: Sever The Wicked"; +HUSTR_E4M4 = "E4M4: Unruly Evil"; +HUSTR_E4M5 = "E4M5: They Will Repent"; +HUSTR_E4M6 = "E4M6: Against Thee Wickedly"; +HUSTR_E4M7 = "E4M7: And Hell Followed"; +HUSTR_E4M8 = "E4M8: Unto The Cruel"; +HUSTR_E4M9 = "E4M9: Fear"; + +HUSTR_1 = "nivel 1: entryway"; +HUSTR_2 = "nivel 2: underhalls"; +HUSTR_3 = "nivel 3: the gantlet"; +HUSTR_4 = "nivel 4: the focus"; +HUSTR_5 = "nivel 5: the waste tunnels"; +HUSTR_6 = "nivel 6: the crusher"; +HUSTR_7 = "nivel 7: dead simple"; +HUSTR_8 = "nivel 8: tricks and traps"; +HUSTR_9 = "nivel 9: the pit"; +HUSTR_10 = "nivel 10: refueling base"; +HUSTR_11 = "nivel 11: 'o' of destruction!"; +HUSTR_12 = "nivel 12: the factory"; +HUSTR_13 = "nivel 13: downtown"; +HUSTR_14 = "nivel 14: the inmost dens"; +HUSTR_15 = "nivel 15: industrial zone"; +HUSTR_16 = "nivel 16: suburbs"; +HUSTR_17 = "nivel 17: tenements"; +HUSTR_18 = "nivel 18: the courtyard"; +HUSTR_19 = "nivel 19: the citadel"; +HUSTR_20 = "nivel 20: gotcha!"; +HUSTR_21 = "nivel 21: nirvana"; +HUSTR_22 = "nivel 22: the catacombs"; +HUSTR_23 = "nivel 23: barrels o' fun"; +HUSTR_24 = "nivel 24: the chasm"; +HUSTR_25 = "nivel 25: bloodfalls"; +HUSTR_26 = "nivel 26: the abandoned mines"; +HUSTR_27 = "nivel 27: monster condo"; +HUSTR_28 = "nivel 28: the spirit world"; +HUSTR_29 = "nivel 29: the living end"; +HUSTR_30 = "nivel 30: icon of sin"; +HUSTR_31 = "nivel 31: wolfenstein"; +HUSTR_32 = "nivel 32: grosse"; + +PHUSTR_1 = "nivel 1: congo"; +PHUSTR_2 = "nivel 2: well of souls"; +PHUSTR_3 = "nivel 3: aztec"; +PHUSTR_4 = "nivel 4: caged"; +PHUSTR_5 = "nivel 5: ghost town"; +PHUSTR_6 = "nivel 6: baron's lair"; +PHUSTR_7 = "nivel 7: caughtyard"; +PHUSTR_8 = "nivel 8: realm"; +PHUSTR_9 = "nivel 9: abattoire"; +PHUSTR_10 = "nivel 10: onslaught"; +PHUSTR_11 = "nivel 11: hunted"; +PHUSTR_12 = "nivel 12: speed"; +PHUSTR_13 = "nivel 13: the crypt"; +PHUSTR_14 = "nivel 14: genesis"; +PHUSTR_15 = "nivel 15: the twilight"; +PHUSTR_16 = "nivel 16: the omen"; +PHUSTR_17 = "nivel 17: compound"; +PHUSTR_18 = "nivel 18: neurosphere"; +PHUSTR_19 = "nivel 19: nme"; +PHUSTR_20 = "nivel 20: the death domain"; +PHUSTR_21 = "nivel 21: slayer"; +PHUSTR_22 = "nivel 22: impossible mission"; +PHUSTR_23 = "nivel 23: tombstone"; +PHUSTR_24 = "nivel 24: the final frontier"; +PHUSTR_25 = "nivel 25: the temple of darkness"; +PHUSTR_26 = "nivel 26: bunker"; +PHUSTR_27 = "nivel 27: anti-christ"; +PHUSTR_28 = "nivel 28: the sewers"; +PHUSTR_29 = "nivel 29: odyssey of noises"; +PHUSTR_30 = "nivel 30: the gateway of hell"; +PHUSTR_31 = "nivel 31: cyberden"; +PHUSTR_32 = "nivel 32: go 2 it"; + +THUSTR_1 = "nivel 1: system control"; +THUSTR_2 = "nivel 2: human bbq"; +THUSTR_3 = "nivel 3: power control"; +THUSTR_4 = "nivel 4: wormhole"; +THUSTR_5 = "nivel 5: hanger"; +THUSTR_6 = "nivel 6: open season"; +THUSTR_7 = "nivel 7: prison"; +THUSTR_8 = "nivel 8: metal"; +THUSTR_9 = "nivel 9: stronghold"; +THUSTR_10 = "nivel 10: redemption"; +THUSTR_11 = "nivel 11: storage facility"; +THUSTR_12 = "nivel 12: crater"; +THUSTR_13 = "nivel 13: nukage processing"; +THUSTR_14 = "nivel 14: steel works"; +THUSTR_15 = "nivel 15: dead zone"; +THUSTR_16 = "nivel 16: deepest reaches"; +THUSTR_17 = "nivel 17: processing area"; +THUSTR_18 = "nivel 18: mill"; +THUSTR_19 = "nivel 19: shipping/respawning"; +THUSTR_20 = "nivel 20: central processing"; +THUSTR_21 = "nivel 21: administration center"; +THUSTR_22 = "nivel 22: habitat"; +THUSTR_23 = "nivel 23: lunar mining project"; +THUSTR_24 = "nivel 24: quarry"; +THUSTR_25 = "nivel 25: baron's den"; +THUSTR_26 = "nivel 26: ballistyx"; +THUSTR_27 = "nivel 27: mount pain"; +THUSTR_28 = "nivel 28: heck"; +THUSTR_29 = "nivel 29: river styx"; +THUSTR_30 = "nivel 30: last call"; +THUSTR_31 = "nivel 31: pharaoh"; +THUSTR_32 = "nivel 32: caribbean"; + +HUSTR_TALKTOSELF1 = "Você pensa..."; +HUSTR_TALKTOSELF2 = "Tem alguém aí?"; +HUSTR_TALKTOSELF3 = "Não tem ninguém que possa responder"; +HUSTR_TALKTOSELF4 = "Você está ficando louco..."; +HUSTR_TALKTOSELF5 = "Eu juro que vi alguém..."; +HUSTR_MESSAGESENT = "[Mensagem enviada]"; + +AMSTR_FOLLOWON = "Siga-me ATIVADO"; +AMSTR_FOLLOWOFF = "Siga-me DESATIVADO"; +AMSTR_GRIDON = "Area quad. ATIVADO"; +AMSTR_GRIDOFF = "Area quad. DESATIVADO"; +AMSTR_TEXON = "Modo Textura ATIVADO"; +AMSTR_TEXOFF = "Modo Textura DESATIVADO"; +AMSTR_MARKEDSPOT = "Marcado!"; +AMSTR_MARKSCLEARED = "Marcas retiradas."; +STSTR_MUS = "Trocando musica..."; +STSTR_NOMUS = "IMPOSSIVEL"; +STSTR_DQDON = "Modo DEUS ATIVADO"; +STSTR_DQDOFF = "Modo DEUS DESATIVADO"; +STSTR_KFAADDED = "O Pacote completo... Trapaceiro!"; +STSTR_FAADDED = "Precisa de mais municao?"; +STSTR_NCON = "No Clip ATIVADO"; +STSTR_NCOFF = "No Clip DESATIVADO"; +STSTR_BEHOLD = "Escolha um poder:\nForça-s; invulnerabilidade-v; invisibilidade-i\nanti-radiação-r; mapa-a; óculos-l"; +STSTR_BEHOLDX = "Power-up ATIVADO"; +STSTR_CHOPPERS = "... nao enche."; +STSTR_CLEV = "Pulando para...\n"; +TXT_BUDDHAON = "Modo Buddha ATIVADO"; +TXT_BUDDHAOFF = "Modo Buddha DESATIVADO"; +TXT_DEFAULTPICKUPMSG = "Você pegou um objeto"; + +E1TEXT = + "Apos todo o esforço limpando \n" + "a base da lua, voce deveria ganhar um prêmio, \n" + "não deveria? Hein? Cadê o maravilhoso \n" + "presente: voltar para casa? \n" + "Parece que este pesadelo só está começando. \n" + "\n" + "Cheira como carne podre, mas é a base Deimos, \n" + "eu acho. Parece que estou preso no meio do \n" + "Inferno... \n" + "\n" + "O único jeito de sair é passando dentro. \n" + "\n" + "Para continuar, escolha o prox. episódio, \n" + "The Shores of Hell e, na sequência, \n" + "Inferno!\n"; +E2TEXT = + "Você conseguiu! O gigante cyber-\n" + "demon que comandava a base lunar\n" + "Deimos foi morto, e voce foi o\n" + "triunfante nessa batalha... E agora?\n" + "Você olha pelo parapeito da base e\n" + "entao vê a assustadora verdade.\n" + "\n" + "A base está flutuando em cima do\n" + "que parece um portal para o inferno!\n" + "Você nunca ouviu falar de alguém que\n" + "escapou de lá, mas aqueles que você\n" + "matou se arrependem de ter lhe conhe-\n" + "cido. Você toma fôlego e comeca sua jor-\n" + "nada no ultimo episódio de DooM! \n" + " Inferno\n" + "\n" + " "; +E3TEXT = + "O inigualável spiderdemon que esque-\n" + "matizou a invasão na lua causou tanta\n" + "destruição e morte que foi enfim derro-\n" + "tado.\n" + "\n" + "Uma passagem secreta abre e você entra.\n" + "Você mostrou que inferno nenhum pode\n" + "contê-lo, pelo menos agora acabou.\n" + "Agora está na hora de voltar para a Terra,\n" + "para enfim descansar.\n" + "Lar doce lar.\n" + "\n" + "Você imagina o que aconteceu nesse\n" + "meio tempo enquanto voce batalhava\n" + "contra o mal. Espero que nenhum daque-\n" + "les monstros tenham vindo junto com\n" + "você naquela passagem ..."; +E4TEXT = + "O spider mastermind deve ter sido \n" + "mandado junto com sua legião de monstros\n" + "para os confins do inferno depois dessa\n" + "batalha. sorte sua que você se manteve\n" + "vivo para fazer de tudo para limpar\n" + "esse mundo de sofrimento de uma forma que\n" + "só um herói poderia fazer diante de tanto mal.\n" + "\n" + "aliás, alguem teria que pagar pelo que\n" + "aconteceu com daisy, seu coelho de estimação.\n" + "\n" + "Mas agora, voce vê o que aconteceu no\n" + "planeta enquanto estava fora: dor e sangue\n" + "trazido pelos demônios que estão a solta nas\n" + "cidades. agora é hora de acabar com isso!\n" + "você recarrega sua pistola e continua seu árduo\n" + "trabalho: acabar com esses monstros!"; +C1TEXT = + "VOCÊ FOI FUNDO DENTRO DO PORTO\n" + "ESTELAR, MAS ALGO ESTÁ ERRADO. OS\n" + "MONSTROS TROUXERAM SUA REALIDADE\n" + "COM ELES, E A TECNOLOGIA AQUI CONTIDA\n" + "ESTÁ SENDO MODIFICADA COM SUA PRESENÇA.\n" + "\n" + "ADIANTE, VOCÊ VÊ UM POSTO DE GUARDA,\n" + "UMA ZONA FORTIFICADA. SE VOCÊ PASSAR ALI,\n" + "PODERÁ ENTRAR DENTRO DO CORAÇÃO DA\n" + "BASE ESTELAR E DESATIVAR O DISPOSITIVO\n" + "QUE MANTÊM O QUE SOBROU DA POPULAÇÃO\n" + "DA TERRA REFÉM."; +C2TEXT = + "VOCÊ CONSEGUIU! APÓS ESSA VITÓRIA VOCÊ SALVA\n" + "A RAÇA HUMANA DA DIZIMACAO, FAZENDO COM QUE\n" + "ELES EVACUASSEM A TERRA DE VEZ. AGORA VOCÊ\n" + "É O ÚNICO HUMANO NA FACE DA TERRA.\n" + "MUTAÇÕES CANIBAIS, ALIENS CARNÍVOROS\n" + "E MAUS ESPÍRITOS SÃO SEUS VIZINHOS.\n" + "VOCÊ SENTA E ESPERA PELA MORTE, FELIZ\n" + "POR TER SALVADO A POPULAÇÃO.\n" + "\n" + "MAS ELES MANDAM UMA MENSAGEM DO ESPAÇO:\n" + "\"SENSORES LOCALIZARAM O FOCO DA INVASÃO\n" + "SE VOCÊ FOR LÁ, PODERÁ BLOQUEAR SUA ENTRADA.\n" + "A BASE ALIEN FICA NO CORAÇÃO DE SUA CIDADE\n" + "NATAL, NÃO MUITO LONGE DO PORTO ESTELAR.\" \n" + "LENTAMENTE E SENTINDO MUITA DOR, VOCÊ SE\n" + "LEVANTA E DECIDE CONTINUAR A LIMPEZA.\n"; +C3TEXT = + "VOCÊ CONSEGUE CHEGAR NO CORAÇÃO DA CIDADE,\n" + "CERCADA PELOS CADÁVERES DE SEUS INIMIGOS.\n" + "VOCÊ NAO VÊ NENHUM JEITO DE DESTRUIR A ENTRADA\n" + "POR ESTE LADO, ENTAO VOCE CERRA OS DENTES\n" + "E MERGULHA ATRAVÉS DA PASSAGEM.\n" + "\n" + "DEVE HAVER UM JEITO DE FECHÁ-LA DO OUTRO LADO.\n" + "PRA QUÊ SE PREOCUPAR SE TEREI QUE ATRAVESSAR O\n" + "INFERNO PARA FECHÁ-LA?"; +C4TEXT = + "A HORRENDA VISTA DO MAIOR DEMÔNIO QUE\n" + "VOCÊ JÁ VIU DESMORONA NA SUA FRENTE\n" + "DEPOIS DE VOCE LANCAR ALGUNS FOGUETES\n" + "DENTRO DE SEU CÉREBRO EXPOSTO. ELE\n" + "MURCHA E MORRE, SEUS MEMBROS ESTAO\n" + "DEVASTANDO MILHAS E MILHAS DA\n" + "SUPERFÍCIE DO INFERNO.\n" + "\n" + "VOCÊ CONSEGUIU. A INVASÃO ACABOU.\n" + "A TERRA ESTÁ SALVA. O INFERNO DESTRUIDO.\n" + "VOCÊ IMAGINA PARA ONDE OS MAUS IRÃO\n" + "QUANDO MORREREM AGORA. VOCÊ SECA O\n" + "SUOR DE SUA TESTA E COMEÇA A LONGA\n" + "JORNADA DE VOLTA PRA CASA. E PENSA EM\n" + "RECONSTRUIR A TERRA PARA SER MAIS\n" + "DIVERTIDA DO QUE ANTES.\n"; +C5TEXT = + "MEUS PARABÉNS, VOCÊ ACHOU O NÍVEL SECRETO!\n" + "PARECE TER SIDO CONSTRUÍDO POR HUNANOS,\n" + "EM VEZ DE DEMÔNIOS. VOCÊ IMAGINA O QUE\n" + "DEVE HABITAR OS CORREDORES DESTE LUGAR.\n"; +C6TEXT = + "MEUS PARABÉNS, DE NOVO! VOCÊ ACHOU\n" + "OUTRO NÍVEL SECRETO. QUERO VER PASSAR\n" + "ESSE AGORA!\n"; + +// Plutonia and TNT intermission screens (50% ainda não traduzido) +P1TEXT = + "Voce vangloria sua vitoria em cima da carcaca\n" + "do Guardiao. Apos mata-lo, voce arranca o \n" + "Acelerador das garras fedorentas do Inferno.\n" + "Voce relaxa e olha em volta da sala. Droga!\n" + "Deveria ter pelo menos mais um prototipo\n" + "funcionando, mas voce nao o acha em lugar\n" + "nenhum. Os demonios devem ter levado-o\n" + "\n" + "Voce deve achar o prototipo, ou todos os\n" + "seus esforcos serao em vao. Mexa-se,\n" + "lute, mate. Ah, se mantenha vivo tambem.\n"; +P2TEXT = + "Nem o labirinto mortal do Arch-Vile pode\n" + "para-lo, e agora voce tem o prototipo do\n" + "Acelerador, o qual está com pouca energia\n" + "e logo se desativara permanentemente.\n" + "\n" + "Voce e bom nesse tipo de coisa."; +P3TEXT = + "Voce batalhou e fez de tudo para chegar\n" + "no coracao da colmeia demoniaca. Esta na\n" + "hora de destruir o protetor do portao, cuja\n" + "prole esta vindo aos montes para a Terra.\n" + "E, ele e mau, mas voce sabe quem e pior!\n" + "\n" + "Sorrindo maldosamente, voce checa seu\n" + "equipamento e se prepara para dar ao\n" + "bastardo um pequeno inferno de sua propria\n" + "criacao!"; +P4TEXT = + "A face do protetor do portao esta espalhada\n" + "por todo o lugar. Seu corpo entra em colapso,\n" + "um portao inverso se forma e engole os restos\n" + "do ultimo Acelerador, sem mencionar os poucos\n" + "demonios que restaram. Voce conseguiu.\n" + "O Inferno voltou ao seu lugar, para os vivos\n" + "viverem suas vidas. Lembre-se de pedir aos\n" + "seus netos para colocarem um lanca-foguete\n" + "em seu caixao. Se for para o Inferno, vai precisar\n" + "para poder terminar o que comecou......\n"; +P5TEXT = + "Voce achou o segundo nivel mais dificil que temos.\n" + "Espero que tenha salvo o jogo uns dois niveis atras.\n" + "Para veteranos apenas."; +P6TEXT = + "Estava pensando qual ERA o nivel mais dificil\n" + "que preparamos? Agora voce sabe.\n" + "Ninguem sai vivo."; +T1TEXT = + "You've fought your way out of the infested\n" + "experimental labs. It seems that UAC has\n" + "once again gulped it down. With their\n" + "high turnover, it must be hard for poor\n" + "old UAC to buy corporate health insurance\n" + "nowadays..\n" + "\n" + "Ahead lies the military complex, now\n" + "swarming with diseased horrors hot to get\n" + "their teeth into you. With luck, the\n" + "complex still has some warlike ordnance\n" + "laying around."; +T2TEXT = + "You hear the grinding of heavy machinery\n" + "ahead. You sure hope they're not stamping\n" + "out new hellspawn, but you're ready to\n" + "ream out a whole herd if you have to.\n" + "They might be planning a blood feast, but\n" + "you feel about as mean as two thousand\n" + "maniacs packed into one mad killer.\n" + "\n" + "You don't plan to go down easy."; +T3TEXT = + "The vista opening ahead looks real damn\n" + "familiar. Smells familiar, too -- like\n" + "fried excrement. You didn't like this\n" + "place before, and you sure as hell ain't\n" + "planning to like it now. The more you\n" + "brood on it, the madder you get.\n" + "Hefting your gun, an evil grin trickles\n" + "onto your face. Time to take some names."; +T4TEXT = + "Suddenly, all is silent, from one horizon\n" + "to the other. The agonizing echo of Hell\n" + "fades away, the nightmare sky turns to\n" + "blue, the heaps of monster corpses start \n" + "to evaporate along with the evil stench \n" + "that filled the air. Jeeze, maybe you've\n" + "done it. Have you really won?\n" + "\n" + "Something rumbles in the distance.\n" + "A blue light begins to glow inside the\n" + "ruined skull of the demon-spitter."; +T5TEXT = + "What now? Looks totally different. Kind\n" + "of like King Tut's condo. Well,\n" + "whatever's here can't be any worse\n" + "than usual. Can it? Or maybe it's best\n" + "to let sleeping gods lie.."; +T6TEXT = + "Time for a vacation. You've burst the\n" + "bowels of hell and by golly you're ready\n" + "for a break. You mutter to yourself,\n" + "Maybe someone else can kick Hell's ass\n" + "next time around. Ahead lies a quiet town,\n" + "with peaceful flowing water, quaint\n" + "buildings, and presumably no Hellspawn.\n" + "\n" + "As you step off the transport, you hear\n" + "the stomp of a cyberdemon's iron shoe."; + +// Lista de personagens (ordem deve ser seguida a risca) +CC_ZOMBIE = "ZOMBIEMAN"; +CC_SHOTGUN = "SHOTGUN GUY"; +CC_HEAVY = "HEAVY WEAPON DUDE"; +CC_IMP = "IMP"; +CC_DEMON = "DEMON"; +CC_LOST = "LOST SOUL"; +CC_CACO = "CACODEMON"; +CC_HELL = "HELL KNIGHT"; +CC_BARON = "BARON OF HELL"; +CC_ARACH = "ARACHNOTRON"; +CC_PAIN = "PAIN ELEMENTAL"; +CC_REVEN = "REVENANT"; +CC_MANCU = "MANCUBUS"; +CC_ARCH = "ARCH-VILE"; +CC_SPIDER = "THE SPIDER MASTERMIND"; +CC_CYBER = "THE CYBERDEMON"; +CC_HERO = "OUR HERO"; + +// New strings from BOOM +PD_BLUEC = "Precisa de uma credencial azul para abrir a porta"; +PD_REDC = "Precisa de uma credencial vermelha para abrir a porta"; +PD_YELLOWC = "Precisa de uma credencial amarela para abrir a porta"; +PD_BLUES = "Precisa de um crânio azul para abrir a porta"; +PD_REDS = "Precisa de um crânio vermelho para abrir a porta"; +PD_YELLOWS = "Precisa de um crânio amarelo para abrir a porta"; +PD_ANY = "Qualquer chave abre a porta"; +PD_ANYOBJ = "Qualquer chave ativa este objeto"; +PD_ALL3 = "Precisa das 3 chaves para abrir a porta"; +PD_ALL3O = "Precisa das 3 chaves para ativar este objeto"; +PD_ALL6 = "Precisa das 6 chaves para abrir a porta"; +PD_ALL6O = "Precisa das 6 chaves para ativar este objeto"; +PD_ALLKEYS = "Precisa de todas as chaves"; + +// MBF (BOOM?) narration backgrounds +bgflatE1 = "FLOOR4_8"; +bgflatE2 = "SFLR6_1"; +bgflatE3 = "MFLR8_4"; +bgflatE4 = "MFLR8_3"; +bgflat06 = "SLIME16"; +bgflat11 = "RROCK14"; +bgflat20 = "RROCK07"; +bgflat30 = "RROCK17"; +bgflat15 = "RROCK13"; +bgflat31 = "RROCK19"; +bgcastcall = "BOSSBACK"; + +// Mensagens de fim de partida online +TXT_FRAGLIMIT = "Pontuação atingida."; +TXT_TIMELIMIT = "Tempo atingido."; + +// Mensagens de pontos cumulativos +SPREEKILLSELF = "%o estava indo bem até se matar!"; +SPREEOVER = "A matança de %o foi interrompida por %k"; +SPREE5 = "%k está indo bem!"; +SPREE10 = "%k continua indo bem!"; +SPREE15 = "%k está dominando!"; +SPREE20 = "%k não se cansa por nada!"; +SPREE25 = "%k parece profissional!"; + +// Mensagens de COMBO +MULTI2 = "COMBO 2X!"; +MULTI3 = "COMBO 3X!"; +MULTI4 = "COMBO 4X!"; +MULTI5 = "Santa Mãe do Céu!!!"; + +// Obituario +// Primeiro suicidios, depois as mortes +OB_SUICIDE = "%o suicidou-se."; +OB_FALLING = "%o caiu."; +OB_CRUSH = "%o foi esmagado."; +OB_EXIT = "%o tentou sair."; +OB_WATER = "%o não sabe nadar."; +OB_SLIME = "%o se entoxicou."; +OB_LAVA = "%o derreteu."; +OB_BARREL = "%o explodiu."; +OB_SPLASH = "%o parou no lugar errado."; +OB_R_SPLASH = "%o deveria ter se afastado."; +OB_ROCKET = "%o deveria ter se afastado."; +OB_KILLEDSELF = "%o se matou."; + +OB_VOODOO = "%o morreu pelo poder do voodoo."; +OB_STEALTHBABY = "%o achou que viu um arachnotron."; +OB_STEALTHVILE = "%o achou que viu um archvile."; +OB_STEALTHBARON = "%o achou que viu um Baron of Hell."; +OB_STEALTHCACO = "%o achou que viu um cacodemon."; +OB_STEALTHCHAINGUY = "%o achou que viu um chaingunner."; +OB_STEALTHDEMON = "%o achou que viu um demon."; +OB_STEALTHKNIGHT = "%o achou que viu um Hell Knight."; +OB_STEALTHIMP = "%o achou que viu um imp."; +OB_STEALTHFATSO = "%o achou que viu um mancubus."; +OB_STEALTHUNDEAD = "%o achou que viu um revenant."; +OB_STEALTHSHOTGUY = "%o achou que viu um sergeant."; +OB_STEALTHZOMBIE = "%o achou que viu um zombieman."; +OB_UNDEADHIT = "%o foi socado por um revenant."; +OB_IMPHIT = "%o foi dilacerado por um imp."; +OB_CACOHIT = "%o chegu muito perto de um cacodemon."; +OB_DEMONHIT = "%o foi mordido por um demon."; +OB_SPECTREHIT = "%o foi comido vivo por um spectre."; +OB_BARONHIT = "%o foi cortado por um Baron of Hell."; +OB_KNIGHTHIT = "%o foi estripado por um Hell Knight."; +OB_ZOMBIE = "%o foi morto por um zombieman."; +OB_SHOTGUY = "%o levou um tiro de um sergeant."; +OB_VILE = "%o foi incinerado pelo archvile."; +OB_UNDEAD = "%o nao conseguiu desviar da bola de\nfogo do revenant."; +OB_FATSO = "%o foi esmagado por um mancubus."; +OB_CHAINGUY = "%o foi perfurado por um chaingunner."; +OB_SKULL = "%o se assustou com um lost soul."; +OB_IMP = "%o foi queimado por um imp."; +OB_CACO = "%o foi golpeado por um cacodemon."; +OB_BARON = "%o foi ferido por um Baron of Hell."; +OB_KNIGHT = "%o foi espalmado por um Hell Knight."; +OB_SPIDER = "%o se ajoelhou em frente do spider demon."; +OB_BABY = "%o foi pego por um arachnotron."; +OB_CYBORG = "%o foi arrebentado pelo cyberdemon."; +OB_WOLFSS = "%o viu um Nazi."; +OB_DOG = "%o foi atacado por um cachorro."; + +OB_CHICKEN = "%o foi cutucado ate a morte."; +OB_BEAST = "%o foi carbonizado por um weredragon."; +OB_CLINK = "%o foi cortado por um sabreclaw."; +OB_DSPARIL1 = "%o foi escaldado pela serpente de D'Sparil."; +OB_DSPARIL1HIT = "%o foi mastigado pela serpente de D'Sparil."; +OB_DSPARIL2 = "%o nao foi pareo para D'Sparil."; +OB_DSPARIL2HIT = "%o foi morto por D'Sparil."; +OB_HERETICIMP = "%o foi cicatrizado por um gargoyle."; +OB_HERETICIMPHIT = "%o foi cortado por um gargoyle."; +OB_IRONLICH = "%o foi devastado por um ironlich."; +OB_IRONLICHHIT = "%o cheou muito perto de um ironlich."; +OB_BONEKNIGHT = "%o foi decepado por um undead warrior."; +OB_BONEKNIGHTHIT = "%o foi picotado por um undead warrior."; +OB_MUMMY = "%o foi esmagado por um golem."; +OB_MUMMYLEADER = "%o agonizou na frente de um nitrogolem."; +OB_SNAKE = "%o foi sacudido por um ophidian."; +OB_WIZARD = "%o foi amaldicoado por um wizard."; +OB_WIZARDHIT = "%o foi apalpado por um wizard."; + +//Hexen Obituaries (Ainda não traduzido) +OB_FIREDEMON = "%o tasted an Afrit's fire."; +OB_DEMON1 = "%o was scalded by a Serpent."; +OB_DEMON2 = "%o was poisoned by a Serpent."; +OB_ETTIN = "%o was mashed by an Ettin."; +OB_CENTAUR = "%o was cut up by a Centaur."; +OB_SLAUGHTAURHIT = "%o was cut up by a Slaughtaur."; +OB_SLAUGHTAUR = "%o was struck down by a Slaughtaur's fireball."; +OB_BISHOP = "%o succumbed to a Bishop's dark power."; +OB_ICEGUY = "%o was frozen solid by a Wendigo."; +OB_SERPENTHIT = "%o was mauled by a Stalker."; +OB_SERPENT = "%o was melted by a Stalker."; +OB_WRAITH = "%o was charred by a Reiver."; +OB_WRAITHHIT = "%o had %p life stolen by a Reiver."; +OB_DRAGON = "%o was incinerated by the Death Wyvern."; +OB_KORAX = "%o was swept from the board by Korax."; +OB_FBOSS = "%o was slain by Zedek."; +OB_MBOSS = "%o couldn't absorb Menelkir's Mana."; +OB_CBOSS = "%o was baptized by Traductus."; +OB_HERESIARCH = "%o had %p bones rolled by the Heresiarch."; + +//Strife obituaries (ainda não traduzido) +OB_ACOLYTE = "%o was zealously shot down by an Acolyte."; +OB_MACIL = "%o should have never rebelled against Macil."; +OB_REBEL = "%o was gunned down by a Rebel."; +OB_BEGGAR = "%o was beaten to death by the poor."; +OB_PEASANT = "%o should have never picked a fight with a civilian."; +OB_ALIENSPECTE = "%o was struck down by the Spectre."; +OB_ENTITY = "%o felt the wrath of The One God."; +OB_LOREMASTER = "%o couldn't escape from the Lore Master's grasp."; +OB_PROGRAMMER = "%o was deleted by the Programmer."; +OB_STFBISHOP = "%o was blown away by the Bishop."; +OB_SENTINEL = "%o was shot down by a Sentinel."; +OB_CRUSADER = "%o was swept away by a Crusader."; +OB_INQUISITOR = "%o was sentenced by an Inquisitor."; +OB_STALKER = "%o was bugged by a Stalker."; +OB_TURRET = "%o triggered the automatic defenses."; +OB_TEMPLARHIT = "%o was clawed by a Templar."; +OB_TEMPLAR = "%o was vaporized by a Templar."; +OB_REAVERHIT = "%o was sliced open by a Reaver."; +OB_REAVER = "%o was shot down by a Reaver."; +OB_COMMONUS = "%o foi eslameado por um flemoid."; + +// Chex obituaries +OB_BIPEDICUS = "%o foi eslameado por um bipedicus."; +OB_BIPEDICUS2 = "%o foi eslameado por um armored bipedicus."; +OB_CYCLOPTIS = "%o foi eslameado por um cycloptis."; +OB_SUPERCYCLOPTIS = "%o foi eslameado por um super cycloptis."; +OB_FLEMBRANE = "%o foi derrotado pela Flembrane."; +OB_STRIDICUS = "%o foi eslameado por um stridicus."; +OB_LARVA = "%o foi eslameado por uma larva."; +OB_QUADRUMPUS = "%o foi eslameado por um quadrumpus."; +OB_MAXIMUS = "%o foi derrotado por um Maximus."; +// Existe um "You defeated a maximus" que eu não pude traduzir, se alguém souber qual a linha que traduz me diga no fórum onde você o baixou +OB_FLEMMINE = "%o foi eslameado por um Flem mine."; +OB_SNOTFOLUS = "%o foi derrotado pelo Lord Snotfolus."; + +OB_MPFIST = "%k fez justiça com as próprias mãos, na cara de %o."; +OB_MPCHAINSAW = "%k usou o poder da motoserra para abrir %o."; +OB_MPPISTOL = "%o foi morto pela pistola de %k."; +OB_MPSHOTGUN = "%o foi morto pela espingarda de %k."; +OB_MPSSHOTGUN = "%o foi morto pela espingarda de cano duplo de %k."; +OB_MPCHAINGUN = "%o foi massacrado pela metralhadora de %k."; +OB_MPROCKET = "%o não viu o foguete de %k."; +OB_MPR_SPLASH = "%o quase escapou do foguete de %k. Quase..."; +OB_MPPLASMARIFLE = "%o foi torrado pelo rifle de plasma de %k."; +OB_MPBFG_BOOM = "%o foi pego pelo BFG de %k."; +OB_MPBFG_SPLASH = "Ninguem se esconde do BFG de %k, nem mesmo %o."; +OB_MPTELEFRAG = "%o foi "telefragged" por %k."; +OB_RAILGUN = "%o was railed by %k."; +OB_MPBFG_MBF = "%o foi arrebentado pelo BFG de %k."; + +OB_MPSTAFF = "%o levou uma sova do bastão de %k."; +OB_MPGAUNTLETS = "%o tomou um choque das luvas de %k."; +OB_MPGOLDWAND = "%o acenou para o bastao magico de %k."; +OB_MPCROSSBOW = "%o foi esmigalhado pela besta magica de %k."; +OB_MPBLASTER = "%o sentiu o poder da garra de dragao de %k."; +OB_MPSKULLROD = "%o foi mandado bem pra baixo pelo bastao do inferno de %k."; +OB_MPPHOENIXROD = "%o virou cinzas com o bastao da fenix de %k."; +OB_MPMACE = "%o saiu pulando apos ver a clava de fogo de %k."; + +OB_MPPSTAFF = "%o tomou um tapa do bastao de %k."; +OB_MPPGAUNTLETS = "%o sangrou nas luvas de %k."; +OB_MPPGOLDWAND = "%o foi massacrado pelo bastao magico de %k."; +OB_MPPCROSSBOW = "%o foi flechado pela besta magica de %k."; +OB_MPPBLASTER = "%o foi cortado pela garra de dragao de %k."; +OB_MPPSKULLROD = "%k usou seu bastao do inferno para banir %o."; +OB_MPPPHOENIXROD = "%o foi queimado pelo bastao da fenix de %k."; +OB_MPPMACE = "%o foi esmagado pela clava de %k."; + +OB_MPFWEAPFIST = "%o was beaten to a pulp by %k's bare fists."; +OB_MPFWEAPAXE = "%o got the axe from %k."; +OB_MPFWEAPHAMMERM = "%o had %p head caved in by %k's hammer."; +OB_MPFWEAPHAMMERR = "%o's soul was forged anew by %k's hammer."; +OB_MPFWEAPQUIETUS = "%o was silenced by %k's mighty Quietus."; +OB_MPCWEAPMACE = "%o got a mace to the face from %k."; +OB_MPCWEAPSTAFFM = "%o was bitten by %k's serpent staff."; +OB_MPCWEAPSTAFFR = "%o choked on %k's serpent staff."; +OB_MPCWEAPFLAME = "%o was lit up by %k's flames."; +OB_MPCWEAPWRAITHVERGE = "%o was cleansed by %k's Wraithverge."; +OB_MPMWEAPWAND = "%o took one too many sapphire beams from %k."; +OB_MPMWEAPFROST = "%o was turned into a frosty fellow by %k."; +OB_MPMWEAPLIGHTNING = "%o recieved a shocking revelation from %k."; +OB_MPMWEAPBLOODSCOURGE = "%o was wiped off the face of the universe by %k's Bloodscourge."; + +OB_MPPUNCHDAGGER = "%o foi pego pela faca de %k."; +OB_MPELECTRICBOLT = "%o recebeu alguns choques de %k."; +OB_MPPOISONBOLT = "%o recebeu uma dose letal da ira de %k."; +OB_MPASSAULTGUN = "%o virou uma peneira diante da metralhadora de %k."; +OB_MPMINIMISSILELAUNCHER = "%o engoliu misseis de %k."; +OB_MPSTRIFEGRENADE = "%o foi pego pelas granadas de %k."; +OB_MPPHOSPHOROUSGRENADE = "%o nao fugiu do fogo causado pelas granadas PH de %k."; +OB_MPFLAMETHROWER = "%o virou churrasco por causa de %k."; +OB_MPMAULER = "%o foi vaporizado por %k."; +OB_MPSIGIL = "%o sentiu o poder de %k:SIGIL."; + +// Same as OB_MPTELEFRAG, but shown when a monster telefrags you +OB_MONTELEFRAG = "%o foi "telefragged" por um monstro."; + +OB_DEFAULT = "%o morreu."; +OB_MPDEFAULT = "%o foi morto por %k."; +OB_FRIENDLY1 = "%k é cego."; +OB_FRIENDLY2 = "%k pensa que sabe o que faz."; +OB_FRIENDLY3 = "%k errou o tiro."; +OB_FRIENDLY4 = "%k perde outro ponto."; + +// Item tags: Doom weapons +TAG_FIST ="Soco Ingles"; +TAG_CHAINSAW = "Motoserra"; +TAG_PISTOL = "Pistola"; +TAG_SHOTGUN = "Espingarda"; +TAG_SUPERSHOTGUN = "Espingarda de cano duplo"; +TAG_CHAINGUN = "Metralhadora"; +TAG_ROCKETLAUNCHER = "Lança-foguetes"; +TAG_PLASMARIFLE = "Rifle de Plasma"; +TAG_BFG9000 = "O BFG 9000"; + +// Item tags: Heretic weapons +TAG_STAFF = "Bastao"; +TAG_GAUNTLETS = "Luvas do Necromancer"; +TAG_GOLDWAND = "Bastao magico"; +TAG_CROSSBOW = "Besta magica"; +TAG_BLASTER = "Garras de dragao"; +TAG_SKULLROD = "Bastao do inferno"; +TAG_PHOENIXROD = "Bastao da Fenix"; +TAG_MACE = "Clava de fogo"; + +// Item tags: Heretic artifacts +TAG_ARTIEGG = "Ovo mutante"; +TAG_ARTIFIREBOMB = "Bomba-relogio dos Antigos"; +TAG_ARTIFLY = "Asas de ira"; +TAG_ARTIHEALTH = "Frasco de quartzo"; +TAG_ARTIINVISIBILITY = "Esfera negra"; +TAG_ARTIINVULNERABILITY = "Anel da invencibilidade"; +TAG_ARTISUPERHEALTH = "Urna mistica"; +TAG_ARTITELEPORT = "Dispositivo do caos"; +TAG_ARTITOMEOFPOWER = "Livro do poder"; +TAG_ARTITORCH = "Tocha"; + +// Item tags: Hexen weapons +TAG_CWEAPMACE = "Mace of Contrition"; +TAG_CWEAPSTAFF = "Serpent Staff"; +TAG_CWEAPFLAME = "Firestorm"; +TAG_CWEAPWRAITHVERGE = "Wraithverge"; +TAG_FWEAPFIST = "Spiked Gauntlets"; +TAG_FWEAPAXE = "Timon's Axe"; +TAG_FWEAPHAMMER = "Hammer of Retribution"; +TAG_FWEAPQUIETUS = "Quietus"; +TAG_MWEAPWAND = "Sapphire Wand"; +TAG_MWEAPFROST = "Frost Shards"; +TAG_MWEAPLIGHTNING = "Arcs of Death"; +TAG_MWEAPBLOODSCOURGE = "Bloodscourge"; + +// Item tags: Hexen artifacts +TAG_ARTIBLASTRADIUS = "Disc of Repulsion"; +TAG_ARTIBOOSTARMOR = "Dragonskin Bracers"; +TAG_ARTIBOOSTMANA = "Krater of Might"; +TAG_ARTIPOISONBAG = "Flechette"; +TAG_ARTIPOISONBAG1 = "Poison Cloud Flechette"; +TAG_ARTIPOISONBAG2 = "Timebomb Flechette"; +TAG_ARTIPOISONBAG3 = "Grenade Flechette"; +TAG_ARTIHEALINGRADIUS = "Mystic Ambit Incant"; +TAG_ARTIDEFENDER = "Icon of the Defender"; +TAG_ARTIPORK = "Porkelator"; +TAG_ARTISPEED = "Boots of Speed"; +TAG_ARTISUMMON = "Dark Servant"; +TAG_ARTITELEPORTOTHER = "Banishment Device"; + +// Item tags: Hexen puzzle items +TAG_ARTIPUZZSKULL = "Yorick's Skull"; +TAG_ARTIPUZZGEMBIG = "Heart of D'Sparil"; +TAG_ARTIPUZZGEMRED = "Ruby Planet"; +TAG_ARTIPUZZGEMGREEN1 = "Emerald Planet (1)"; +TAG_ARTIPUZZGEMGREEN2 = "Emerald Planet (2)"; +TAG_ARTIPUZZGEMBLUE1 = "Sapphire Planet (1)"; +TAG_ARTIPUZZGEMBLUE2 = "Sapphire Planet (2)"; +TAG_ARTIPUZZBOOK1 = "Daemon Codex"; +TAG_ARTIPUZZBOOK2 = "Liber Obscura"; +TAG_ARTIPUZZSKULL2 = "Flame Mask"; +TAG_ARTIPUZZFWEAPON = "Glaive Seal"; +TAG_ARTIPUZZCWEAPON = "Holy Relic"; +TAG_ARTIPUZZMWEAPON = "Sigil of the Magus"; +TAG_ARTIPUZZGEAR1 = "Iron gear"; +TAG_ARTIPUZZGEAR2 = "Brass gear"; +TAG_ARTIPUZZGEAR3 = "Brass and iron gear"; +TAG_ARTIPUZZGEAR4 = "Silver and brass gear"; + +// Item tags: Strife weapons +TAG_PUNCHDAGGER = "Faca"; +TAG_STRIFECROSSBOW1 = "Besta"; +TAG_STRIFECROSSBOW2 = "Besta"; +TAG_ASSAULTGUN = "Metralhadora"; +TAG_MMLAUNCHER = "Mini Lanca-misseis"; +TAG_FLAMER = "Lanca-chamas"; +TAG_MAULER1 = "Mauler"; +TAG_MAULER2 = "Mauler"; +TAG_GLAUNCHER1 = "Lanca-granadas"; +TAG_GLAUNCHER2 = "Lanca-granadas"; +TAG_SIGIL = "SIGIL"; + +// Item tags: Strife artifacts +TAG_COIN = "moeda(s)"; +TAG_MEDPATCH = "Primeiros socorros (pequeno)"; +TAG_MEDICALKIT = "Primeiros socorros (medio)"; +TAG_SURGERYKIT = "Primeiros socorros (grande)"; // "full_health" in the Teaser +TAG_BELDINSRING = "Anel"; +TAG_OFFERINGCHALICE = "Calice de oferenda"; +TAG_EAR = "Orelha"; +TAG_BROKENCOUPLING = "Acoplador de energia quebrado"; +TAG_SHADOWARMOR = "Armadura das sombras"; +TAG_ENVSUIT = "Roupa anti-radiacao"; +TAG_GUARDUNIFORM = "Uniforme - Guarda"; +TAG_OFFICERSUNIFORM = "Uniforme - Oficial"; +TAG_FTHROWERPARTS = "Partes do lanca-chamas"; +TAG_REPORT = "Relatorio"; +TAG_INFO = "Informacao"; +TAG_TARGETER = "Targeter"; +TAG_COMMUNICATOR = "Transmissor"; +TAG_DEGNINORE = "Minerio Degnin"; +TAG_GUNTRAINING = "Precisao"; +TAG_HEALTHTRAINING = "Forca"; +TAG_SCANNER = "Scanner"; +TAG_PRISONPASS = "Credencial - Prisao"; +TAG_ALARM = "Alarme"; +TAG_AMMOFILLUP = "Municao"; +TAG_HEALTHFILLUP = "Saude"; +TAG_TELEPORTERBEACON = "Sinalizador para teletransporte"; +TAG_METALARMOR = "Armadura de metal"; +TAG_LEATHER = "Armadura leve"; +TAG_HEGRENADES = "Municao - Granadas HE"; +TAG_PHGRENADES = "Municao - Granadas PH"; // "Fire-Grenade_Rounds" in the Teaser +TAG_CLIPOFBULLETS = "Clipe de balas"; // "bullets" in the Teaser +TAG_BOXOFBULLETS = "Caixa de balas"; +TAG_MINIMISSILES = "Misseis"; //"rocket" in the Teaser +TAG_CRATEOFMISSILES = "Caixa de Misseis"; //"box_of_rockets" in the Teaser +TAG_ENERGYPOD = "Energia (pequeno)"; +TAG_ENERGYPACK = "Energia (grande)"; +TAG_POISONBOLTS = "Flechas (veneno)"; // "poison_arrows" in the Teaser +TAG_ELECTRICBOLTS = "Flechas (eletricas)"; // "electric_arrows" in the Teaser +TAG_AMMOSATCHEL = "Pacote de municao"; // "Back_pack" in the Teaser + +// Item tags: Strife keys +TAG_BASEKEY = "Chave da Base"; +TAG_GOVSKEY = "Chave do Governador"; // "Rebel_Key" in the Teaser +TAG_PASSCARD = "Cartao de passe"; +TAG_IDBADGE = "Distintivo de Identificacao"; +TAG_PRISONKEY = "Chave da prisao"; +TAG_SEVEREDHAND = "Mao"; +TAG_POWER1KEY = "Chave Power1"; +TAG_POWER2KEY = "Chave Power2"; +TAG_POWER3KEY = "Chave Power3"; +TAG_GOLDKEY = "Chave de ouro"; +TAG_IDCARD = "Cartao de Identificacao"; +TAG_SILVERKEY = "Chave de prata"; +TAG_ORACLEKEY = "Chave do Oraculo"; +TAG_MILITARYID = "ID do militar"; +TAG_ORDERKEY = "Chave da Ordem"; +TAG_WAREHOUSEKEY = "Chave do barracao"; +TAG_BRASSKEY = "Chave de bronze"; +TAG_REDCRYSTALKEY = "Chave - Cristal vermelho"; +TAG_BLUECRYSTALKEY = "Chave - Cristal azul"; +TAG_CHAPELKEY = "Chave da capela"; +TAG_CATACOMBKEY = "Chave da catatumba"; // "Tunnel_Key" in the Teaser +TAG_SECURITYKEY = "Chave de seguranca"; +TAG_COREKEY = "Chave principal"; // "New_Key1" in the Teaser +TAG_MAULERKEY = "Chave - Mauler"; // "New_Key2" in the Teaser +TAG_FACTORYKEY = "Chave da fabrica"; // "New_Key3" in the Teaser +TAG_MINEKEY = "Chave da mina"; // "New_Key4" in the Teaser +TAG_NEWKEY5 = "Chave Nova5"; +TAG_ORACLEPASS = "Passe do Oraculo"; + +// Item tags: misc Strife stuff +TAG_10GOLD = "10 moedas"; +TAG_25GOLD = "25 moedas"; +TAG_50GOLD = "50 moedas"; +TAG_300GOLD = "sortudo! 300 moedas"; +TAG_QUEST4 = "quest4"; +TAG_QUEST5 = "quest5"; +TAG_QUEST6 = "quest6"; + +// Item tags: Strife NPCs +TAG_ACOLYTE = "ACOLYTE"; +TAG_ARMORER = "Armeiro"; +TAG_BARKEEP = "Garcom de bar"; +TAG_BEGGAR = "Pedinte"; +TAG_MACIL1 = "MACIL"; +TAG_MACIL2 = "MACIL"; +TAG_MEDIC = "Medico"; +TAG_ORACLE = "Oraculo"; +TAG_PRIEST = "PRIEST"; +TAG_RATBUDDY = "rat buddy"; +TAG_REBEL = "Rebelde"; +TAG_TEMPLAR = "TEMPLAR"; +TAG_WEAPONSMITH = "Ferreiro"; + +// Item tags: Chex weapons +TAG_SPOON = "Colher"; +TAG_SPORK = "Super garfo"; +TAG_MINIZORCHER = "Mini Zorcher"; +TAG_LARGEZORCHER = "Zorcher grande"; +TAG_SUPERLARGEZORCHER = "Super Zorcher"; +TAG_RAPIDZORCHER = "Zorcher automatico"; +TAG_ZORCHPROPULSOR = "Propulsor Zorch"; +TAG_PHASINGZORCHER = "Mega Zorcher"; +TAG_LAZDEVICE = "LAZ Device"; + +// Heretic strings (ainda não traduzida) +HE1TEXT = + "with the destruction of the iron\n" + "liches and their minions, the last\n" + "of the undead are cleared from this\n" + "plane of existence.\n\n" + "those creatures had to come from\n" + "somewhere, though, and you have the\n" + "sneaky suspicion that the fiery\n" + "portal of hell's maw opens onto\n" + "their home dimension.\n\n" + "to make sure that more undead\n" + "(or even worse things) don't come\n" + "through, you'll have to seal hell's\n" + "maw from the other side. of course\n" + "this means you may get stuck in a\n" + "very unfriendly world, but no one\n" + "ever said being a Heretic was easy!"; +HE2TEXT = + "the mighty maulotaurs have proved\n" + "to be no match for you, and as\n" + "their steaming corpses slide to the\n" + "ground you feel a sense of grim\n" + "satisfaction that they have been\n" + "destroyed.\n\n" + "the gateways which they guarded\n" + "have opened, revealing what you\n" + "hope is the way home. but as you\n" + "step through, mocking laughter\n" + "rings in your ears.\n\n" + "was some other force controlling\n" + "the maulotaurs? could there be even\n" + "more horrific beings through this\n" + "gate? the sweep of a crystal dome\n" + "overhead where the sky should be is\n" + "certainly not a good sign...."; +HE3TEXT = + "the death of d'sparil has loosed\n" + "the magical bonds holding his\n" + "creatures on this plane, their\n" + "dying screams overwhelming his own\n" + "cries of agony.\n\n" + "your oath of vengeance fulfilled,\n" + "you enter the portal to your own\n" + "world, mere moments before the dome\n" + "shatters into a million pieces.\n\n" + "but if d'sparil's power is broken\n" + "forever, why don't you feel safe?\n" + "was it that last shout just before\n" + "his death, the one that sounded\n" + "like a curse? or a summoning? you\n" + "can't really be sure, but it might\n" + "just have been a scream.\n\n" + "then again, what about the other\n" + "serpent riders?"; +HE4TEXT = + "you thought you would return to your\n" + "own world after d'sparil died, but\n" + "his final act banished you to his\n" + "own plane. here you entered the\n" + "shattered remnants of lands\n" + "conquered by d'sparil. you defeated\n" + "the last guardians of these lands,\n" + "but now you stand before the gates\n" + "to d'sparil's stronghold. until this\n" + "moment you had no doubts about your\n" + "ability to face anything you might\n" + "encounter, but beyond this portal\n" + "lies the very heart of the evil\n" + "which invaded your world. d'sparil\n" + "might be dead, but the pit where he\n" + "was spawned remains. now you must\n" + "enter that pit in the hopes of\n" + "finding a way out. and somewhere,\n" + "in the darkest corner of d'sparil's\n" + "demesne, his personal bodyguards\n" + "await your arrival ..."; +HE5TEXT = + "as the final maulotaur bellows his\n" + "death-agony, you realize that you\n" + "have never come so close to your own\n" + "destruction. not even the fight with\n" + "d'sparil and his disciples had been\n" + "this desperate. grimly you stare at\n" + "the gates which open before you,\n" + "wondering if they lead home, or if\n" + "they open onto some undreamed-of\n" + "horror. you find yourself wondering\n" + "if you have the strength to go on,\n" + "if nothing but death and pain await\n" + "you. but what else can you do, if\n" + "the will to fight is gone? can you\n" + "force yourself to continue in the\n" + "face of such despair? do you have\n" + "the courage? you find, in the end,\n" + "that it is not within you to\n" + "surrender without a fight. eyes\n" + "wide, you go to meet your fate."; + +// EPISODE 1 - THE CITY OF THE DAMNED +HHUSTR_E1M1 = "THE DOCKS"; +HHUSTR_E1M2 = "THE DUNGEONS"; +HHUSTR_E1M3 = "THE GATEHOUSE"; +HHUSTR_E1M4 = "THE GUARD TOWER"; +HHUSTR_E1M5 = "THE CITADEL"; +HHUSTR_E1M6 = "THE CATHEDRAL"; +HHUSTR_E1M7 = "THE CRYPTS"; +HHUSTR_E1M8 = "HELL'S MAW"; +HHUSTR_E1M9 = "THE GRAVEYARD"; + +// EPISODE 2 - HELL'S MAW +HHUSTR_E2M1 = "THE CRATER"; +HHUSTR_E2M2 = "THE LAVA PITS"; +HHUSTR_E2M3 = "THE RIVER OF FIRE"; +HHUSTR_E2M4 = "THE ICE GROTTO"; +HHUSTR_E2M5 = "THE CATACOMBS"; +HHUSTR_E2M6 = "THE LABYRINTH"; +HHUSTR_E2M7 = "THE GREAT HALL"; +HHUSTR_E2M8 = "THE PORTALS OF CHAOS"; +HHUSTR_E2M9 = "THE GLACIER"; + +// EPISODE 3 - THE DOME OF D'SPARIL +HHUSTR_E3M1 = "THE STOREHOUSE"; +HHUSTR_E3M2 = "THE CESSPOOL"; +HHUSTR_E3M3 = "THE CONFLUENCE"; +HHUSTR_E3M4 = "THE AZURE FORTRESS"; +HHUSTR_E3M5 = "THE OPHIDIAN LAIR"; +HHUSTR_E3M6 = "THE HALLS OF FEAR"; +HHUSTR_E3M7 = "THE CHASM"; +HHUSTR_E3M8 = "D'SPARIL'S KEEP"; +HHUSTR_E3M9 = "THE AQUIFER"; + +// EPISODE 4: THE OSSUARY +HHUSTR_E4M1 = "CATAFALQUE"; +HHUSTR_E4M2 = "BLOCKHOUSE"; +HHUSTR_E4M3 = "AMBULATORY"; +HHUSTR_E4M4 = "SEPULCHER"; +HHUSTR_E4M5 = "GREAT STAIR"; +HHUSTR_E4M6 = "HALLS OF THE APOSTATE"; +HHUSTR_E4M7 = "RAMPARTS OF PERDITION"; +HHUSTR_E4M8 = "SHATTERED BRIDGE"; +HHUSTR_E4M9 = "MAUSOLEUM"; + +// EPISODE 5: THE STAGNANT DEMESNE +HHUSTR_E5M1 = "OCHRE CLIFFS"; +HHUSTR_E5M2 = "RAPIDS"; +HHUSTR_E5M3 = "QUAY"; +HHUSTR_E5M4 = "COURTYARD"; +HHUSTR_E5M5 = "HYDRATYR"; +HHUSTR_E5M6 = "COLONNADE"; +HHUSTR_E5M7 = "FOETID MANSE"; +HHUSTR_E5M8 = "FIELD OF JUDGEMENT"; +HHUSTR_E5M9 = "SKEIN OF D'SPARIL"; + +// Keys + +TXT_GOTBLUEKEY = "CHAVE AZUL"; +TXT_GOTYELLOWKEY = "CHAVE AMARELA"; +TXT_GOTGREENKEY = "CHAVE AZUL"; + +// Artifacts + +TXT_ARTIHEALTH = "FRASCO DE QUARTZO"; +TXT_ARTIFLY = "ASAS DE IRA"; +TXT_ARTIINVULNERABILITY = "ANEL DE INVENCIBILIDADE"; +TXT_ARTITOMEOFPOWER = "LIVRO DO PODER"; +TXT_ARTIINVISIBILITY = "ESFERA NEGRA"; +TXT_ARTIEGG = "OVO MUTANTE"; +TXT_ARTISUPERHEALTH = "URNA MISTICA"; +TXT_ARTITORCH = "TOCHA"; +TXT_ARTIFIREBOMB = "BOMBA-RELOGIO DOS ANTIGOS"; +TXT_ARTITELEPORT = "DISPOSITIVO DO CAOS"; + +// Items + +TXT_ITEMHEALTH = "AMPOLA DE CRISTAL"; +TXT_ITEMBAGOFHOLDING = "BOLSA DE UTENSILIOS"; +TXT_ITEMSHIELD1 = "ESCUDO DE PRATA"; +TXT_ITEMSHIELD2 = "ESCUDO ENCANTADO"; +TXT_ITEMSUPERMAP = "PERGAMINHO - MAPA"; + +// Ammo + +TXT_AMMOGOLDWAND1 = "CRISTAL MAGICO"; +TXT_AMMOGOLDWAND2 = "GEODO DE CRISTAL"; +TXT_AMMOMACE1 = "ESFERAS PARA CLAVA"; +TXT_AMMOMACE2 = "PILHA DE ESFERAS PARA CLAVA"; +TXT_AMMOCROSSBOW1 = "FLECHAS MAGICAS"; +TXT_AMMOCROSSBOW2 = "MAIS FLECHAS MAGICAS"; +TXT_AMMOBLASTER1 = "ESFERA PARA GARRAS"; +TXT_AMMOBLASTER2 = "ESFERA DE ENERGIA"; +TXT_AMMOSKULLROD1 = "RUNAS PEQUENAS"; +TXT_AMMOSKULLROD2 = "RUNAS GRANDES"; +TXT_AMMOPHOENIXROD1 = "ESFERA DE CHAMAS"; +TXT_AMMOPHOENIXROD2 = "ESFERA DO INFERNO"; + +// Weapons + +TXT_WPNMACE = "CLAVA DE FOGO"; +TXT_WPNCROSSBOW = "BESTA MAGICA"; +TXT_WPNBLASTER = "GARRAS DE DRAGAO"; +TXT_WPNSKULLROD = "BASTAO DO INFERNO"; +TXT_WPNPHOENIXROD = "BASTAO DA FENIX"; +TXT_WPNGAUNTLETS = "LUVAS DO NECROMANCER"; + +TXT_NEEDBLUEKEY = "VOCE PRECISA DA CHAVE AZUL PARA ABRIR A PORTA"; +TXT_NEEDGREENKEY = "VOCE PRECISA DA CHAVE VERDE PARA ABRIR A PORTA"; +TXT_NEEDYELLOWKEY = "VOCE PRECISA DA CHAVE AMARELA PARA ABRIR A PORTA"; + +TXT_CHEATPOWERON = "PODER ATIVADO"; +TXT_CHEATPOWEROFF = "PODER DESATIVADO"; +TXT_CHEATHEALTH = "SAUDE CHEIA"; +TXT_CHEATKEYS = "TODAS AS CHAVES"; +TXT_CHEATSOUNDON = "DEBUG DE SOM ATIVADO"; +TXT_CHEATSOUNDOFF = "DEBUG DE SOM DESATIVADO"; +TXT_CHEATIDDQD = "CODIGO ERRADO, TENTE NOVAMENTE!"; +TXT_CHEATIDKFA = "SO NAO TIRO O BASTAO PORQUE NAO POSSO"; +TXT_CHEATTICKERON = "TICKER ATIVADO"; +TXT_CHEATTICKEROFF = "TICKER DESATIVADO"; +TXT_CHEATARTIFACTS3 = "ESTA FELIZ AGORA?"; +TXT_MIDASTOUCH = "YOU GOT THE MIDAS TOUCH, BABY"; +TXT_GOTSTUFF = "Pegou tudo?"; +TXT_FREEZEON = "modo congelado ATIVADO"; +TXT_FREEZEOFF = "modo congelado DeSaTiVaDo"; +TXT_STRANGE = "Voce se sente estranho"; +TXT_STRANGER = "Voce se sente mais estranho."; +TXT_NOTSTRANGE = "voce se sente melhor agora."; +TXT_LEADBOOTSON = "BOTAS ATIVADAS"; +TXT_LEADBOOTSOFF = "BOTAS DESATIVADAS"; +TXT_LIGHTER = "Voce se sente leve"; +TXT_GRAVITY = "Gravidade volta a funcionar normalmente"; + +RAVENQUITMSG = "DESEJA MESMO SAIR?"; + +// Hexen strings + +// Mana + +TXT_MANA_1 = "MANA AZUL"; +TXT_MANA_2 = "MANA VERDE"; +TXT_MANA_BOTH = "MANA COMBINADA"; + +// Keys + +TXT_KEY_STEEL = "STEEL KEY"; +TXT_KEY_CAVE = "CAVE KEY"; +TXT_KEY_AXE = "AXE KEY"; +TXT_KEY_FIRE = "FIRE KEY"; +TXT_KEY_EMERALD = "EMERALD KEY"; +TXT_KEY_DUNGEON = "DUNGEON KEY"; +TXT_KEY_SILVER = "SILVER KEY"; +TXT_KEY_RUSTED = "RUSTED KEY"; +TXT_KEY_HORN = "HORN KEY"; +TXT_KEY_SWAMP = "SWAMP KEY"; +TXT_KEY_CASTLE = "CASTLE KEY"; + +TXT_NEED_KEY_STEEL = "You need the STEEL KEY"; +TXT_NEED_KEY_CAVE = "You need the CAVE KEY"; +TXT_NEED_KEY_AXE = "You need the AXE KEY"; +TXT_NEED_KEY_FIRE = "You need the FIRE KEY"; +TXT_NEED_KEY_EMERALD = "You need the EMERALD KEY"; +TXT_NEED_KEY_DUNGEON = "You need the DUNGEON KEY"; +TXT_NEED_KEY_SILVER = "You need the SILVER KEY"; +TXT_NEED_KEY_RUSTED = "You need the RUSTED KEY"; +TXT_NEED_KEY_HORN = "You need the HORN KEY"; +TXT_NEED_KEY_SWAMP = "You need the SWAMP KEY"; +TXT_NEED_KEY_CASTLE = "You need the CASTLE KEY"; + +// Artifacts + +TXT_ARTIINVULNERABILITY2 = "ICON OF THE DEFENDER"; +TXT_ARTISUMMON = "DARK SERVANT"; +TXT_ARTIEGG2 = "PORKALATOR"; +TXT_ARTIPOISONBAG = "FLECHETTE"; +TXT_ARTITELEPORTOTHER = "BANISHMENT DEVICE"; +TXT_ARTISPEED = "BOOTS OF SPEED"; +TXT_ARTIBOOSTMANA = "KRATER OF MIGHT"; +TXT_ARTIBOOSTARMOR = "DRAGONSKIN BRACERS"; +TXT_ARTIBLASTRADIUS = "DISC OF REPULSION"; +TXT_ARTIHEALINGRADIUS = "MYSTIC AMBIT INCANT"; + +// Puzzle artifacts + +TXT_ARTIPUZZSKULL = "YORICK'S SKULL"; +TXT_ARTIPUZZGEMBIG = "HEART OF D'SPARIL"; +TXT_ARTIPUZZGEMRED = "RUBY PLANET"; +TXT_ARTIPUZZGEMGREEN1 = "EMERALD PLANET"; +TXT_ARTIPUZZGEMGREEN2 = "EMERALD PLANET"; +TXT_ARTIPUZZGEMBLUE1 = "SAPPHIRE PLANET"; +TXT_ARTIPUZZGEMBLUE2 = "SAPPHIRE PLANET"; +TXT_ARTIPUZZBOOK1 = "DAEMON CODEX"; +TXT_ARTIPUZZBOOK2 = "LIBER OSCURA"; +TXT_ARTIPUZZSKULL2 = "FLAME MASK"; +TXT_ARTIPUZZFWEAPON = "GLAIVE SEAL"; +TXT_ARTIPUZZCWEAPON = "HOLY RELIC"; +TXT_ARTIPUZZMWEAPON = "SIGIL OF THE MAGUS"; +TXT_ARTIPUZZGEAR = "CLOCK GEAR"; +TXT_USEPUZZLEFAILED = "YOU CANNOT USE THIS HERE"; + +// Items + +TXT_ARMOR1 = "MESH ARMOR"; +TXT_ARMOR2 = "FALCON SHIELD"; +TXT_ARMOR3 = "PLATINUM HELMET"; +TXT_ARMOR4 = "AMULET OF WARDING"; + +// Weapons + +TXT_WEAPON_F2 = "TIMON'S AXE"; +TXT_WEAPON_F3 = "HAMMER OF RETRIBUTION"; +TXT_WEAPON_F4 = "QUIETUS ASSEMBLED"; +TXT_WEAPON_C2 = "SERPENT STAFF"; +TXT_WEAPON_C3 = "FIRESTORM"; +TXT_WEAPON_C4 = "WRAITHVERGE ASSEMBLED"; +TXT_WEAPON_M2 = "FROST SHARDS"; +TXT_WEAPON_M3 = "ARC OF DEATH"; +TXT_WEAPON_M4 = "BLOODSCOURGE ASSEMBLED"; +TXT_WEAPONPIECE = "A weapon piece! This is your lucky day!"; +TXT_QUIETUS_PIECE = "SEGMENT OF QUIETUS"; +TXT_WRAITHVERGE_PIECE = "SEGMENT OF WRAITHVERGE"; +TXT_BLOODSCOURGE_PIECE = "SEGMENT OF BLOODSCOURGE"; + +// Strife locks + +TXT_NEEDKEY = "Voce nao possui a chave"; +TXT_NEED_PASSCARD = "Precisa do cartao de passe"; +TXT_NEED_PASSCARD_DOOR = "Precisa do cartao de passe para abrir a porta"; +TXT_NEED_IDCARD = "Precisa do cartao de indentificacao"; +TXT_NEED_PRISONKEY = "Voce nao tem a chave da prisao"; +TXT_NEED_HANDPRINT = "Biometria nao autorizada"; +TXT_NEED_GOLDKEY = "Voce precisa da chave dourada"; +TXT_NEED_IDBADGE = "Voce precisa do distintivo de identificacao"; +TXT_NEED_IDBADGE_DOOR = "Voce precisa do distintivo de identificacao para abrir a porta"; +TXT_NEED_SILVERKEY = "Voce precisa da chave de prata"; +TXT_NEED_BRASSKEY = "Voce precisa da chave de latao"; +TXT_NEED_REDCRYSTAL = "Voce precisa do cristal vermelho"; +TXT_NEED_BLUECRYSTAL = "Voce precisa do cristal azul"; +TXT_RETAIL_ONLY = "AREA DISPONIVEL NA VERSAO REGISTRADA"; +TXT_DOES_NOT_WORK = "Parece que nao funciona"; + +// Strife - mensagens de missoes + +TXT_QUEST_14 = "Voce explodiu o cristal"; +TXT_QUEST_16 = "Voce explodiu as portas"; +TXT_QUEST_27 = "Voce explodiu o computador"; + +TXT_KILLED_BISHOP = "Voce matou o bispo!"; +TXT_KILLED_ORACLE = "Voce matou o Oraculo!"; +TXT_KILLED_MACIL = "Voce matou Macil!"; +TXT_KILLED_LOREMASTER = "Voce matou o Loremaster!"; + +// Strife - mensagens gerais + +TXT_METALARMOR = "Voce pegou a armadura de metal."; +TXT_LEATHERARMOR = "Voce pegou a armadura leve."; +TXT_MEDPATCH = "Voce pegou um pequeno kit de primeiros socorros."; +TXT_MEDICALKIT = "Voce pegou um kit medio de primeiros socorros."; +TXT_SURGERYKIT = "Voce pegou um kit de primeiros socorros grande."; +TXT_STRIFEMAP = "Voce pegou o mapa."; +TXT_BELDINSRING = "Voce pegou o anel."; +TXT_OFFERINGCHALICE = "Voce pegou o calice de oferenda."; +TXT_EAR = "Voce pegou a orelha."; +TXT_BROKENCOUPLING = "Você pegou o acoplador de energia quebrado."; +TXT_SHADOWARMOR = "Voce pegou a armadura das sombras."; +TXT_ENVSUIT = "Voce pegou a roupa anti-radiacao."; +TXT_GUARDUNIFORM = "Voce pegou o uniforme - Guarda."; +TXT_OFFICERSUNIFORM = "Voce pegou o uniforme - Oficial."; +TXT_FTHROWERPARTS = "Voce pegou uma parte do lanca-chamas."; +TXT_REPORT = "Voce pegou o relatorio."; +TXT_INFO = "Voce pegou a informacao."; +TXT_TARGETER = "Voce pegou o Targeter."; +TXT_COMMUNICATOR = "Voce pegou o Transmissor."; +TXT_COIN = "Voce pegou a moeda."; +TXT_XGOLD = "Voce pegou %d moedas."; +TXT_BEACON = "Voce pegou o sinalizador de teletransporte."; +TXT_DEGNINORE = "Voce pegou o minerio Degnin."; +TXT_SCANNER = "Voce pegou o scanner."; +TXT_NEEDMAP = "O scanner nao funciona sem um mapa!\n"; +TXT_PRISONPASS = "Voce pegou o passe da prisao."; + +TXT_STRIFECROSSBOW = "Voce pegou a besta."; +TXT_ASSAULTGUN = "Voce pegou a metralhadora."; +TXT_MMLAUNCHER = "Voce pegou o mini lanca-misseis."; +TXT_FLAMER = "Voce pegou o lanca-chamas."; +TXT_MAULER = "Voce pegou o mauler."; +TXT_GLAUNCHER = "Voce pegou o lanca-granadas."; +TXT_SIGIL = "Voce pegou o SIGIL."; + + +TXT_BASEKEY = "Voce pegou a chave da Base."; +TXT_GOVSKEY = "Voce pegou a chave do Governador."; +TXT_PASSCARD = "Voce pegou o cartao de passe."; +TXT_IDBADGE = "Voce pegou o distintivo de identificacao."; +TXT_PRISONKEY = "Voce pegou a chave da prisao."; +TXT_SEVEREDHAND = "Voce pegou a mao decepada."; +TXT_POWER1KEY = "Voce pegou a chave Power1."; +TXT_POWER2KEY = "Voce pegou a chave Power2."; +TXT_POWER3KEY = "Voce pegou a chave Power3."; +TXT_GOLDKEY = "Voce pegu a chave de ouro."; +TXT_IDCARD = "Voce pegou o cartao de identificacao."; +TXT_SILVERKEY = "Voce pegou a chave de prata."; +TXT_ORACLEKEY = "Voce pegou a chave do Oraculo."; +TXT_MILITARYID = "Voce pegou a ID do militar."; +TXT_ORDERKEY = "Voce pegou a chave da Ordem."; +TXT_WAREHOUSEKEY = "Voce pegou a chave do barracao."; +TXT_BRASSKEY = "Voce pegou a chave de bronze."; +TXT_REDCRYSTAL = "Voce pegou a chave - Cristal Vermelho."; +TXT_BLUECRYSTAL = "Voce pegou a chave - Cristal Azul."; +TXT_CHAPELKEY = "Voce pegou a chave da capela."; +TXT_CATACOMBKEY = "Voce pegou a chave da catatumba."; +TXT_SECURITYKEY = "Voce pegou a chave de seguranca."; +TXT_COREKEY = "Voce pegou a chave principal."; +TXT_MAULERKEY = "Voce pegou a chave do Mauler."; +TXT_FACTORYKEY = "Voce pegou a chave da Fabrica."; +TXT_MINEKEY = "Voce pegou a chave da Mina."; +TXT_NEWKEY5 = "Voce pegou a Chave Nova5."; +TXT_ORACLEPASS = "Voce pegou o passe do Oraculo."; + +TXT_HEGRENADES = "Voce pegou granadas HE."; +TXT_PHGRENADES = "Voce pegou granadas PH."; +TXT_CLIPOFBULLETS = "Voce pegou um clipe."; +TXT_BOXOFBULLETS = "Voce pegou uma caixa de balas."; +TXT_MINIMISSILES = "Voce pegou mini-misseis."; +TXT_CRATEOFMISSILES = "Voce pegou uma caixa de misseis."; +TXT_ENERGYPOD = "Voce pegou uma celula de energia (pequeno)."; +TXT_ENERGYPACK = "Voce pegou uma celula de energia (grande)."; +TXT_POISONBOLTS = "Voce pegou flechas (venenosas)."; +TXT_ELECTRICBOLTS = "Voce pegou flechas (eletricas)."; +TXT_AMMOSATCHEL = "Voce pegou uma mochila de municao."; + +// Diálogos aleatórios + +TXT_RANDOM_PEASANT_01 = "Por favor, nao me machuque."; +TXT_RANDOM_PEASANT_02 = "Se pretende me machucar, te digo que nao valera a pena."; +TXT_RANDOM_PEASANT_03 = "Eu nao sei de nada."; +TXT_RANDOM_PEASANT_04 = "Va embora antes que eu chame os guardas!"; +TXT_RANDOM_PEASANT_05 = "Eu queria que esses rebeldes soubessem se por em seu lugar e parassem com essa maluquice."; +TXT_RANDOM_PEASANT_06 = "So me deixa em paz, OK?"; +TXT_RANDOM_PEASANT_07 = "Eu nao tenho certeza, mas acho que conheco alguns dos acolytes."; +TXT_RANDOM_PEASANT_08 = "A Ordem mantem tudo trancado a sete chaves."; +TXT_RANDOM_PEASANT_09 = "Nao tem como isso ser uma forca de seguranca."; +TXT_RANDOM_PEASANT_10 = "Eu soube que a Ordem esta bem nervosa por causa das acoes dos opositores por aqui."; + +TXT_RANDOM_REBEL_01 = "Nao ha como a Ordem ficar contra a gente."; +TXT_RANDOM_REBEL_02 = "Estamos quase prontos para o ataque. Os planos de Macil estao fazendo efeito."; +TXT_RANDOM_REBEL_03 = "Estamos todos lhe protegendo, nao se preocupe."; +TXT_RANDOM_REBEL_04 = "Nem pense em ficar perto daqueles robos. Te transformam em ferro velho em um instante!"; +TXT_RANDOM_REBEL_05 = "O dia de nossa gloria chegara, e aqueles que se opuserem serao esmagados!"; +TXT_RANDOM_REBEL_06 = "Nao fique muito confortavel. Ainda temos trabalho a fazer."; +TXT_RANDOM_REBEL_07 = "Macil disse que voce e a nova esperanca. Lembre-se disso."; +TXT_RANDOM_REBEL_08 = "Assim que derrubarmos esses charlatas, poderemos reconstruir o mundo do jeito certo."; +TXT_RANDOM_REBEL_09 = "Lembre-se que nao esta lutando por si mesmo, mas por todos nos."; +TXT_RANDOM_REBEL_10 = "Enquanto um de nos ainda estiver em pe, teremos chance de vencer."; + +TXT_RANDOM_AGUARD_01 = "Continue andando."; +TXT_RANDOM_AGUARD_02 = "Siga a verdadeirta fe e entao comecara a entender."; +TXT_RANDOM_AGUARD_03 = "Somente atraves da morte que alguem podera renascer."; +TXT_RANDOM_AGUARD_04 = "Nao estou interessado em lhe ouvir."; +TXT_RANDOM_AGUARD_05 = "Se quisesse falar com voce, eu mesmo teria te dito."; +TXT_RANDOM_AGUARD_06 = "Va pertubar outro!"; +TXT_RANDOM_AGUARD_07 = "Continue andando!"; +TXT_RANDOM_AGUARD_08 = "Se o alarme disparar, nao fique no nosso caminho!"; +TXT_RANDOM_AGUARD_09 = "A Ordem vai limpar o mundo e arruma-lo dentro da nova era."; +TXT_RANDOM_AGUARD_10 = "Algum problema? Eu acho que nao."; + +TXT_RANDOM_BEGGAR_01 = "Uma ajuda para os pobres?"; +TXT_RANDOM_BEGGAR_02 = "O que esta olhando, terrestre?"; +TXT_RANDOM_BEGGAR_03 = "Voce nao tem comida, tem?"; +TXT_RANDOM_BEGGAR_04 = "Voces da superficie nunca nos entenderao."; +TXT_RANDOM_BEGGAR_05 = "Ha, os guardas nunca nos acharao. Estes idiotas nem sabem que existimos."; +TXT_RANDOM_BEGGAR_06 = "Um dia todos, exceto aqueles que servem a Ordem, se juntarao a nos."; +TXT_RANDOM_BEGGAR_07 = "Observe, pois este sera seu futuro um dia."; +TXT_RANDOM_BEGGAR_08 = "Nao ha nada mais irritante do que um da superficie com atitude!"; +TXT_RANDOM_BEGGAR_09 = "A Ordem nao tera muito trabalho com voces, pateticos opositores."; +TXT_RANDOM_BEGGAR_10 = "Cuidado, o da superficie; Nos sabemos quem sao nossos inimigos!"; + +TXT_RANDOM_PGUARD_01 = "Nos somos as maos da fe. Se nos irritar, esqueca da gente!"; +TXT_RANDOM_PGUARD_02 = "A Ordem limpara o mundo dos fracos e corruptos!"; +TXT_RANDOM_PGUARD_03 = "Obedeca a lei dos mestres!"; +TXT_RANDOM_PGUARD_04 = "Vida longa aos irmaos da Ordem!"; +TXT_RANDOM_PGUARD_05 = "Livre arbitrio e uma ideia que passa em mentes fracas."; +TXT_RANDOM_PGUARD_06 = "O poder e o caminho para a gloria. Siga a Ordem e andara por esse caminho!"; +TXT_RANDOM_PGUARD_07 = "Tenha seu lugar junto com os certos, junte-se a nos!"; +TXT_RANDOM_PGUARD_08 = "A Ordem protege a si propria."; +TXT_RANDOM_PGUARD_09 = "Acolytes? Eles ainda tem de ver a gloria total da Ordem."; +TXT_RANDOM_PGUARD_10 = "Se ainda existe honra dentro de seu patetico corpo, voce vai entrar para os bracos da Ordem."; + +TXT_RANDOMGOODBYE_1 = "Fui!"; +TXT_RANDOMGOODBYE_2 = "Muito obrigado."; +TXT_RANDOMGOODBYE_3 = "Ate mais tarde!"; +TXT_HAVEENOUGH = "Voce parece ter bastante!"; +TXT_GOAWAY = "Va embora!"; + +// Skills: (partes não traduzidas ainda) + +SKILL_BABY = "Facil"; +SKILL_EASY = "Medio"; +SKILL_NORMAL = "Dificil"; +SKILL_HARD = "Desafio"; +SKILL_NIGHTMARE = "PESADELO!"; + +$ifgame(chex) SKILL_BABY = "Bem facil"; +$ifgame(chex) SKILL_EASY = "Ainda facil"; +$ifgame(chex) SKILL_NORMAL = "Muita gosma"; +$ifgame(chex) SKILL_HARD = "Gosma extrema"; +$ifgame(chex) SKILL_NIGHTMARE = "So para os melhores!"; + +SSKILL_BABY = "Treino"; +SSKILL_EASY = "Iniciante"; +SSKILL_NORMAL = "Veterano"; +SSKILL_HARD = "Elite"; +SSKILL_NIGHTMARE = "Banho de sangue"; + +// Menu +MNU_NEWGAME = "NOVO JOGO"; +MNU_OPTIONS = "OPCOES"; +MNU_GAMEFILES = "JOGOS SALVOS"; +MNU_INFO = "INFO"; +MNU_QUITGAME = "SAIR"; + +MNU_FIGHTER = "FIGHTER"; +MNU_CLERIC = "CLERIC"; +MNU_MAGE = "MAGE"; +MNU_RANDOM = "RANDOM"; + +MNU_LOADGAME = "CARREGAR"; +MNU_SAVEGAME = "SALVAR"; + +MNU_COTD = "CITY OF THE DAMNED"; +MNU_HELLSMAW = "HELL'S MAW"; +MNU_DOME = "THE DOME OF D'SPARIL"; +MNU_OSSUARY = "THE OSSUARY"; +MNU_DEMESNE = "THE STAGNANT DEMESNE"; + +MNU_WETNURSE = "THOU NEEDETH A WET-NURSE"; +MNU_YELLOWBELLIES = "YELLOWBELLIES-R-US"; +MNU_BRINGEST = "BRINGEST THEM ONETH"; +MNU_SMITE = "THOU ART A SMITE-MEISTER"; +MNU_BLACKPLAGUE = "BLACK PLAGUE POSSESSES THEE"; + +MNU_NOPICTURE = "No Picture"; +MNU_DIFFVERSION = "Versao\nDiferente"; +MNU_NOFILES = "Vazio"; + +MNU_CHOOSECLASS = "Escolha a classe:"; +MNU_CHOOSESKILL = "ESCOLHA A DIFICULDADE:"; + +MNU_SQUIRE = "SQUIRE"; +MNU_KNIGHT = "KNIGHT"; +MNU_WARRIOR = "WARRIOR"; +MNU_BERSERKER = "BERSERKER"; +MNU_TITAN = "TITAN"; + +MNU_ALTARBOY = "ALTAR BOY"; +MNU_ACOLYTE = "ACOLYTE"; +MNU_PRIEST = "PRIEST"; +MNU_CARDINAL = "CARDINAL"; +MNU_POPE = "POPE"; + +MNU_APPRENTICE = "APPRENTICE"; +MNU_ENCHANTER = "ENCHANTER"; +MNU_SORCERER = "SORCERER"; +MNU_WARLOCK = "WARLOCK"; +MNU_ARCHMAGE = "ARCHMAGE"; + +MNU_PLAYERSETUP = "PLAYER SETUP"; + +MNU_DELETESG = "Deseja mesmo deletar:\n"; + +$ifgame(heretic) SWSTRING = "SOMENTE DISPONIVEL NA VERSAO REGISTRADA"; + +MNU_EPISODE = "Selecione um episodio"; + +WI_FINISHED = "finalizado"; +WI_ENTERING = "Entrando:"; + +// Bloodbath announcer (não traduzido ainda) + +BBA_BONED = "%k boned %o like a fish"; +BBA_CASTRA = "%k castrated %o"; +BBA_CREAMED = "%k creamed %o"; +BBA_DECIMAT = "%k decimated %o"; +BBA_DESTRO = "%k destroyed %o"; +BBA_DICED = "%k diced %o"; +BBA_DISEMBO = "%k disembowled %o"; +BBA_FLATTE = "%k flattened %o"; +BBA_JUSTICE = "%k gave %o Anal Justice"; +BBA_MADNESS = "%k gave AnAl MaDnEsS to %o"; +BBA_KILLED = "%k killed %o"; +BBA_MINCMEAT = "%k made mincemeat out of %o"; +BBA_MASSACR = "%k massacred %o"; +BBA_MUTILA = "%k mutilated %o"; +BBA_REAMED = "%k reamed %o"; +BBA_RIPPED = "%k ripped %o a new orifice"; +BBA_SLAUGHT = "%k slaughtered %o"; +BBA_SMASHED = "%k smashed %o"; +BBA_SODOMIZ = "%k sodomized %o"; +BBA_SPLATT = "%k splattered %o"; +BBA_SQUASH = "%k squashed %o"; +BBA_THROTTL = "%k throttled %o"; +BBA_WASTED = "%k wasted %o"; +BBA_BODYBAG = "%k body bagged %o"; +BBA_HELL = "%k sent %o to Hell"; +BBA_TOAST = "%k toasted %o"; +BBA_SNUFF = "%k snuffed %o"; +BBA_HOSED = "%k hosed %o"; +BBA_SPRAYED = "%k sprayed %o"; +BBA_DOGMEAT = "%k made dog meat out of %o"; +BBA_BEATEN = "%k beat %o like a cur"; + +BBA_EXCREMENT = "%o is excrement"; +BBA_HAMBURGER = "%o is hamburger"; +BBA_SCROTUM = "%o suffered scrotum separation"; +BBA_POPULATION = "%o volunteered for population control"; +BBA_SUICIDE = "%o has suicided"; +BBA_DARWIN = "%o received the Darwin Award"; + +// Chex Quest Strings +$ifgame(chex) HUSTR_E1M1 = "E1M1: Landing Zone"; +$ifgame(chex) HUSTR_E1M2 = "E1M2: Storage Facility"; +$ifgame(chex) HUSTR_E1M3 = "E1M3: Experimental Lab"; +$ifgame(chex) HUSTR_E1M4 = "E1M4: Arboretum"; +$ifgame(chex) HUSTR_E1M5 = "E1M5: Caverns of Bazoik"; +$ifgame(chex) HUSTR_E2M1 = "E2M1: Spaceport"; +$ifgame(chex) HUSTR_E2M2 = "E2M2: Cinema"; +$ifgame(chex) HUSTR_E2M3 = "E2M3: Chex Museum"; +$ifgame(chex) HUSTR_E2M4 = "E2M4: City Streets"; +$ifgame(chex) HUSTR_E2M5 = "E2M5: Sewer System"; +$ifgame(chex) HUSTR_E3M1 = "E3M1: Central Command"; +$ifgame(chex) HUSTR_E3M2 = "E3M2: United Cereals"; +$ifgame(chex) HUSTR_E3M3 = "E3M3: Villa Chex"; +$ifgame(chex) HUSTR_E3M4 = "E3M4: Provincial Park"; +$ifgame(chex) HUSTR_E3M5 = "E3M5: Meteor Spaceship"; + +$ifgame(chex) E1TEXT = + "Apos mandar a monstruosa Flembrane.\n" + "de volta a sua dimensao, voce solta os\n" + "prisioneiros. Mas ainda ha flemoids por\n" + "todo canto! Voce e os cativos dao um\n" + "jeito de chegar na sua espaconave. Os\n" + "flemoids eslameiam sua nave, mas voce\n" + "consegue chegar a orbita, escapando deles.\n" + "Enquanto comemoram felizes o resgate\n" + "e fuga, voce percebe uma coisa:\n" + "ainda nao acabou! Chegando ao seu lar, voce\n" + "ve que os flemoids de algum jeito chegaram\n" + "aqui. Prepare-se para\n" + "Chex Quest 2: Terror in Chex City!"; + +$ifgame(chex) E2TEXT = + "Voce zorcheou os flemoids restantes\n" + "que ficaram nos esgotos. Chex City foi\n" + "salva. Parece que a invasao acabou.\n" + "\nDez anos se passam. Mas os flemoids nao\n" + "foram derrotados, e seu desejo de dominar a\n" + "dimensao do cereal rico em nutrientes nao \n" + "diminuiu. Eles juntam todas as suas forcas e \n" + "abrem um buraco inter-dimensional no espaco.\n" + "Agora uma grande bola de muco esta vindo direto\n" + "para o planeta dos cereais!" + "\n Prepare-se para\n" + "Chex Quest 3: Invasion!"; + +$ifgame(chex) E3TEXT = + "Com este ultimo tiro, voce zorcheia Lord\n" + "Snotfolus de volta a sua dimensao! Contra\n" + "tudo e todos, voce conseguiu conter a \n" + "invasao!\n" + "\n" + "Voce espera que esses flemoids tenham \n" + "aprendido a licao e nunca mais retornar ao\n" + "mundo dos cereais.\n" + "\n" + "Mas se eles voltarem, voce sabe que estara\n" + "pronto para lutar!\n"; + +CLOADNET = "impossivel carregar, porque \n esta em partida online!\naperte uma tecla."; +CNEWGAME = "impossivel iniciar um novo jogo\nenquanto se esta online.\n\naperte uma tecla."; + +CNIGHTMARE = "Cuidado, isto vai ser dificil.\nDeseja continuar?\n"; + +CSWSTRING = "este e Chex(R) Quest. procure\npor outros niveis na internet.\n"; + +$ifgame(chex) NETEND = "voce nao pode finalizar uma partida online!\npressione qualquer tecla."; +$ifgame(chex) ENDGAME = "deseja realmente sair?\n"; + +GOTCHEXARMOR = "Pegou a armadura Chex(R)."; +GOTSUPERCHEXARMOR = "Pegou a super armadura Chex(R)!"; +GOTWATER = "Pegou um copo de agua."; +GOTREPELLENT = "Pegou repelente de lesma."; +GOTBREAKFAST = "Um belo cafe da manha!"; +GOTCBLUEKEY = "Pegou a chave azul."; +GOTCYELLOWKEY = "Pegou a chave amarela."; +GOTCREDKEY = "Pegou a chave vermelha."; +GOTFRUIT = "Pegou uma bacia de frutas."; +GOTVEGETABLESNEED = "Vegetais sao REALMENTE bons para voce!"; +GOTVEGETABLES = "Pegou uma bacia de vegetais."; +GOTSLIMESUIT = "Achou roupa anti-radiacao"; +GOTCHEXMAP = "Achou um GPS"; + +GOTZORCHRECHARGE = "Pegou carga para mini zorcher."; +GOTMINIZORCHPACK = "Pegou mais cargas para mini zorcher."; +GOTPROPULSORRECHARGE = "Pegou carga para propulsor zorch."; +GOTPROPULSORPACK = "Pegou mais cargas para propulsor zorch."; +GOTPHASINGZORCHERRECHARGE = "Pegou carga para mega zorcher."; +GOTPHASINGZORCHERPACK = "Pegou mais carga para mega zorcher."; +GOTLARGEZORCHERRECHARGE = "Pegou carga para zorcher grande."; +GOTLARGEZORCHERPACK = "Pegou mais carga para zorcher grande."; +GOTZORCHPACK = "Pegou um Zorchpak!"; + +GOTLAZDEVICE = "Voce achou LAZ Device! Legal!"; +GOTRAPIDZORCHER = "Voce achou Zorcher automatico!"; +GOTSUPERBOOTSPORK = "Voce achou Super garfo!"; +GOTZORCHPROPULSOR = "Voce achou Propulsor Zorch!"; +GOTPHASINGZORCHER = "Voce achou Mega Zorcher!"; +GOTLARGEZORCHER = "Voce achou Zorcher grande!"; +GOTSUPERLARGEZORCHER = "Voce achou Super Zorcher!"; +GOTMINIZORCHER = "Voce achou Mini Zorcher."; + +$ifgame(chex) STSTR_DQDON = "Modo invencivel ATIVADO"; +$ifgame(chex) STSTR_DQDOFF = "Modo invencivel DESATIVADO"; +$ifgame(chex) STSTR_FAADDED = "Zorch adicionado"; +$ifgame(chex) STSTR_KFAADDED = "Super Zorch adicionado"; +$ifgame(chex) STSTR_CHOPPERS = "... coma Chex(R)!"; + +OB_MPSPOON = "%o foi comido de colher por %k."; +OB_MPBOOTSPORK = "%o foi misturado pelo garfo de %k."; +OB_MPZORCH = "%o foi zorcheado por %k."; +OB_MPMEGAZORCH = "%o foi pego pelo zorcher grande de %k."; +OB_MPRAPIDZORCH = "%o foi zorcheado rapidamente por %k."; +OB_MPPROPULSOR = "%o foi zorcheado pelo propulsor de %k."; +OB_MPP_SPLASH = "%o foi pego pelo propulsor de %k."; +OB_MPPHASEZORCH = "%o foi mega zorcheado por %k."; +OB_MPLAZ_BOOM = "%o foi vitima do LAZ device de %k."; +OB_MPLAZ_SPLASH = "%o foi pego pelo LAZ device de %k."; diff --git a/wadsrc/static/mapinfo/doom2.txt b/wadsrc/static/mapinfo/doom2.txt index 6dc9815b1..5d0d40517 100644 --- a/wadsrc/static/mapinfo/doom2.txt +++ b/wadsrc/static/mapinfo/doom2.txt @@ -8,6 +8,13 @@ episode map01 key = "h" } +episode level01 +{ + name = "No Rest for the Living" + key = "n" + optional +} + defaultmap { sucktime = 1 @@ -424,3 +431,113 @@ cluster 10 entertext = lookup, "C6TEXT" } +// No Rest for the Living + +map LEVEL01 lookup "NHUSTR_1" +{ + titlepatch = "NWILV00" + next = "LEVEL02" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 75 + music = "$MUSIC_MESSAG" +} + +map LEVEL02 lookup "NHUSTR_2" +{ + titlepatch = "NWILV01" + next = "LEVEL03" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 105 + music = "$MUSIC_DDTBLU" +} + +map LEVEL03 lookup "NHUSTR_3" +{ + titlepatch = "NWILV02" + next = "LEVEL04" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 120 + music = "$MUSIC_DOOM" +} + +map LEVEL04 lookup "NHUSTR_4" +{ + titlepatch = "NWILV03" + next = "LEVEL05" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 105 + music = "$MUSIC_SHAWN" +} + +map LEVEL05 lookup "NHUSTR_5" +{ + titlepatch = "NWILV04" + next = "LEVEL06" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 210 + music = "$MUSIC_IN_CIT" +} + +map LEVEL06 lookup "NHUSTR_6" +{ + titlepatch = "NWILV05" + next = "LEVEL07" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 105 + sucktime = 1 + music = "$MUSIC_THE_DA" +} + +map LEVEL07 lookup "NHUSTR_7" +{ + titlepatch = "NWILV06" + next = "LEVEL08" + secretnext = "LEVEL09" + sky1 = "SKY1" + cluster = 11 + par = 165 + map07special + music = "$MUSIC_IN_CIT" +} + +map LEVEL08 lookup "NHUSTR_8" +{ + titlepatch = "NWILV07" + next = "EndGameC" + secretnext = "EndGameC" + sky1 = "SKY1" + cluster = 11 + par = 105 + music = "$MUSIC_SHAWN" +} + +map LEVEL09 lookup "NHUSTR_9" +{ + titlepatch = "NWILV08" + next = "LEVEL05" + secretnext = "LEVEL05" + sky1 = "SKY1" + cluster = 11 + par = 135 + music = "$MUSIC_DDTBLU" +} + +cluster 11 +{ + flat = "RROCK19" + music = "$MUSIC_READ_M" + exittext = lookup, "NERVETEXT" +} + diff --git a/wadsrc/static/mapinfo/doom2bfg.txt b/wadsrc/static/mapinfo/doom2bfg.txt new file mode 100644 index 000000000..cc82c4e19 --- /dev/null +++ b/wadsrc/static/mapinfo/doom2bfg.txt @@ -0,0 +1,67 @@ +// MAPINFO for Doom 2: BFG Edition +include "mapinfo/doom2.txt" + +gameinfo +{ + titlepage = "INTERPIC" +} + +clearepisodes +episode map01 +{ + name = "Hell On Earth" + picname = "M_EPI1" + key = "h" +} + +episode level01 +{ + name = "No Rest for the Living" + picname = "M_EPI2" + key = "n" + optional +} + +map MAP02 lookup "HUSTR_2" +{ + titlepatch = "CWILV01" + next = "MAP03" + secretnext = "MAP33" + sky1 = "SKY1" + cluster = 5 + par = 90 + music = "$MUSIC_STALKS" +} + +map MAP33 lookup "HUSTR_33" +{ + titlepatch = "CWILV32" + next = "MAP03" + secretnext = "MAP03" + sky1 = "SKY3" + cluster = 5 + music = "$MUSIC_READ_M" +} + +// Wolfenstein 3D censorship +map MAP31 lookup "HUSTR_31B" +{ + titlepatch = "CWILV30" + next = "MAP16" + secretnext = "MAP32" + sky1 = "SKY3" + cluster = 9 + par = 120 + music = "$MUSIC_EVIL" +} + +map MAP32 lookup "HUSTR_32B" +{ + titlepatch = "CWILV31" + next = "MAP16" + secretnext = "MAP16" + sky1 = "SKY3" + cluster = 10 + par = 30 + music = "$MUSIC_ULTIMA" +} diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 38ac1bd74..f50f21a69 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -117,11 +117,11 @@ ListMenu "PlayerclassMenu" { IfGame(Doom, Heretic, Hexen, Strife) { - NetgameMessage "$NETGAME" + NetgameMessage "$NEWGAME" } IfGame(Chex) { - NetgameMessage "$CNETGAME" + NetgameMessage "$CNEWGAME" } IfGame(Doom, Strife, Chex) @@ -159,11 +159,11 @@ ListMenu "EpisodeMenu" { IfGame(Doom, Heretic, Hexen, Strife) { - NetgameMessage "$NETGAME" + NetgameMessage "$NEWGAME" } IfGame(Chex) { - NetgameMessage "$CNETGAME" + NetgameMessage "$CNEWGAME" } IfGame(Doom, Chex) @@ -465,6 +465,16 @@ OptionMenu "CustomizeControls" StaticText "Weapons", 1 Control "Next weapon", "weapnext" Control "Previous weapon", "weapprev" + Control "Weapon Slot 1", "slot 1" + Control "Weapon Slot 2", "slot 2" + Control "Weapon Slot 3", "slot 3" + Control "Weapon Slot 4", "slot 4" + Control "Weapon Slot 5", "slot 5" + Control "Weapon Slot 6", "slot 6" + Control "Weapon Slot 7", "slot 7" + Control "Weapon Slot 8", "slot 8" + Control "Weapon Slot 9", "slot 9" + Control "Weapon Slot 0", "slot 0" StaticText "" StaticText "Inventory", 1 Control "Activate item", "invuse" @@ -780,6 +790,20 @@ OptionValue "AltHUDScale" 2, "Pixel double" } +OptionValue "AltHUDTime" +{ + 0, "Off" + 1, "Level, milliseconds" + 2, "Level, seconds" + 3, "Level" + 4, "Hub, seconds" + 5, "Hub" + 6, "Total, seconds" + 7, "Total" + 8, "System, seconds" + 9, "System" +} + OptionMenu "AltHUDOptions" { Title "Alternative HUD" @@ -790,6 +814,9 @@ OptionMenu "AltHUDOptions" Option "Show monster count", "hud_showmonsters", "OnOff" Option "Show item count", "hud_showitems", "OnOff" Option "Show stamina and accuracy", "hud_showstats", "OnOff" + Option "Show berserk", "hud_berserk_health", "OnOff" + Option "Show time", "hud_showtime", "AltHUDTime" + Option "Time color", "hud_timecolor", "TextColors" Slider "Red ammo display below %", "hud_ammo_red", 0, 100, 1, 0 Slider "Yellow ammo display below %", "hud_ammo_yellow", 0, 100, 1, 0 Slider "Red health display below", "hud_health_red", 0, 100, 1, 0 @@ -801,12 +828,11 @@ OptionMenu "AltHUDOptions" StaticText " " StaticText "Alternative Automap HUD", 1 option "Map title color", "hudcolor_titl", "TextColors" - option "Hub time color", "hudcolor_time", "TextColors" option "Map time color", "hudcolor_ltim", "TextColors" - option "Total title color", "hudcolor_ttim", "TextColors" + option "Hub time color", "hudcolor_time", "TextColors" + option "Total time color", "hudcolor_ttim", "TextColors" option "Coordinate color", "hudcolor_xyco", "TextColors" option "Coordinate mode", "map_point_coordinates", "AMCoordinates" - option "Map title color", "hudcolor_titl", "TextColors" option "Statistics name color", "hudcolor_statnames", "TextColors" option "Statistics color", "hudcolor_stats", "TextColors" } @@ -1139,8 +1165,8 @@ OptionMenu GameplayOptions Option "Allow spying", "sv_disallowspying", "NoYes" Option "Chasecam cheat", "sv_chasecam", "YesNo" Option "Check ammo for weapon switch", "sv_dontcheckammo", "NoYes" - Option "Killing Romero kills all his spawns", "sv_killbossmonst", "YesNo" - Option "Count monsters in end level sectors", "sv_nocountendmonst", "NoYes" + Option "Icon's death kills its spawns", "sv_killbossmonst", "YesNo" + Option "End sector counts for kill %", "sv_nocountendmonst", "NoYes" StaticText " " StaticText "Deathmatch Settings",1 @@ -1400,6 +1426,13 @@ OptionValue GusMemory 4, "1024K" } +OptionValue OplCores +{ + 0, "MAME OPL2" + 1, "DOSBox OPL3" + 2, "Java OPL3" +} + OptionMenu AdvSoundOptions { Title "ADVANCED SOUND OPTIONS" @@ -1408,7 +1441,9 @@ OptionMenu AdvSoundOptions Option "Buffer count", "snd_buffercount", "BufferCounts" StaticText " " StaticText "OPL Synthesis", 1 - Option "Only emulate one OPL chip", "opl_onechip", "OnOff" + Slider "Number of emulated OPL chips", "opl_numchips", 1, 8, 1, 0 + Option "Full MIDI stereo panning", "opl_fullpan", "OnOff" + Option "OPL Emulator Core", "opl_core", "OplCores" StaticText " " StaticText "GUS Emulation", 1 Slider "MIDI voices", "midi_voices", 16, 256, 4, 0 diff --git a/wadsrc/static/textures.txt b/wadsrc/static/textures.txt index 7ae26ce2f..d54ef32d9 100644 --- a/wadsrc/static/textures.txt +++ b/wadsrc/static/textures.txt @@ -71,3 +71,9 @@ Graphic optional P_MWALK4, 112, 136 Graphic "M_MBOX", 0, 0 Graphic "M_MWALK4", 24, 12 { useoffsets } } + +Graphic optional STBAR, 320, 32 +{ + Graphic "STMBARL", 0, 0 + Graphic "STMBARR", 104, 0 +} diff --git a/wadsrc/static/xlat/base.txt b/wadsrc/static/xlat/base.txt index 44d5457b2..11b9fd315 100644 --- a/wadsrc/static/xlat/base.txt +++ b/wadsrc/static/xlat/base.txt @@ -54,8 +54,8 @@ include "xlat/defines.i" 52 = WALK, Exit_Normal (0) 53 = WALK, Plat_PerpetualRaiseLip (tag, P_SLOW, PLATWAIT, 0) 54 = WALK, Plat_Stop (tag) - 55 = USE, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) - 56 = WALK, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 55 = USE, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) + 56 = WALK, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 57 = WALK, Ceiling_CrushStop (tag) 58 = WALK, Floor_RaiseByValue (tag, F_SLOW, 24) 59 = WALK, Floor_RaiseByValueTxTy (tag, F_SLOW, 24) @@ -64,7 +64,7 @@ include "xlat/defines.i" 62 = USE|REP, Plat_DownWaitUpStayLip (tag, P_FAST, PLATWAIT, 0) 63 = USE|REP, Door_Raise (tag, D_SLOW, VDOORWAIT) 64 = USE|REP, Floor_RaiseToLowestCeiling (tag, F_SLOW) - 65 = USE|REP, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 65 = USE|REP, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 66 = USE|REP, Plat_UpByValueStayTx (tag, P_SLOW/2, 3) 67 = USE|REP, Plat_UpByValueStayTx (tag, P_SLOW/2, 4) 68 = USE|REP, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) @@ -93,7 +93,7 @@ include "xlat/defines.i" 91 = WALK|REP, Floor_RaiseToLowestCeiling (tag, F_SLOW) 92 = WALK|REP, Floor_RaiseByValue (tag, F_SLOW, 24) 93 = WALK|REP, Floor_RaiseByValueTxTy (tag, F_SLOW, 24) - 94 = WALK|REP, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 94 = WALK|REP, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 95 = WALK|REP, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) 96 = WALK|REP, Floor_RaiseByTexture (tag, F_SLOW) 97 = WALK|REP|MONST, Teleport (0, tag) diff --git a/wadsrc/static/xlat/doom_base.txt b/wadsrc/static/xlat/doom_base.txt index e75cd4109..351784da7 100644 --- a/wadsrc/static/xlat/doom_base.txt +++ b/wadsrc/static/xlat/doom_base.txt @@ -3,8 +3,6 @@ include "xlat/doom.txt" maxlinespecial = 272; -sector bitmask 0xffe0 clear; - sector 15 = 0; sector 17 = 0; sector 18 = 0; diff --git a/wadsrc/static/xlat/heretic.txt b/wadsrc/static/xlat/heretic.txt index 8fe344f5b..cfc106d09 100644 --- a/wadsrc/static/xlat/heretic.txt +++ b/wadsrc/static/xlat/heretic.txt @@ -5,7 +5,10 @@ include "xlat/base.txt" 10 = WALK, Plat_DownWaitUpStayLip (tag, P_FAST, PLATWAIT, 0) 36 = WALK, Floor_LowerToHighest (tag, F_FAST, 136, 1) 49 = USE, Ceiling_LowerAndCrush (tag, C_SLOW, 0, 2) + 70 = USE|REP, Floor_LowerToHighest (tag, F_FAST, 136, 1) + 71 = USE, Floor_LowerToHighest (tag, F_FAST, 136, 1) 88 = WALK|REP, Plat_DownWaitUpStayLip (tag, P_FAST, PLATWAIT, 0) + 98 = WALK|REP, Floor_LowerToHighest (tag, F_FAST, 136, 1) 99 = 0, Scroll_Texture_Right (SCROLL_UNIT) 100 = WALK|REP, Door_Raise (tag, D_SLOW*3, VDOORWAIT) 105 = WALK, Exit_Secret (0) diff --git a/wadsrc/static/xlat/strife.txt b/wadsrc/static/xlat/strife.txt index 68887d975..710fa1fad 100644 --- a/wadsrc/static/xlat/strife.txt +++ b/wadsrc/static/xlat/strife.txt @@ -99,7 +99,7 @@ RetailOnly = 121 17 = WALK, Light_StrobeDoom (tag, 5, 35) 19 = WALK, Floor_LowerToHighest (tag, F_SLOW, 128) 22 = WALK, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) - 25 = WALK, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 10) + 25 = WALK, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 0) 30 = WALK, Floor_RaiseByTexture (tag, F_SLOW) 35 = WALK, Light_ChangeToValue (tag, 35) 36 = WALK, Floor_LowerToHighest (tag, F_FAST, 128) @@ -113,7 +113,7 @@ RetailOnly = 121 52 = WALK|REP, ACS_ExecuteAlways (0, 0, 52, tag) 53 = WALK, Plat_PerpetualRaiseLip (tag, P_SLOW, PLATWAIT, 0) 54 = WALK, Plat_Stop (tag) - 56 = WALK, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 56 = WALK, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 57 = WALK, Ceiling_CrushStop (tag) 58 = WALK, Floor_RaiseByValue (tag, F_SLOW, 64) 59 = WALK, Floor_RaiseByValueTxTy (tag, F_SLOW, 24) @@ -121,21 +121,21 @@ RetailOnly = 121 108 = WALK, Door_Raise (tag, D_FAST, VDOORWAIT) 109 = WALK, Door_Open (tag, D_FAST) 100 = WALK, Stairs_BuildUpDoom (tag, ST_TURBO, 16, 0, 0) -197 = WALK, ACS_ExecuteAlways (0, 0, 197, tag) +197 = WALK|REP, ACS_ExecuteAlways (0, 0, 197, tag) 110 = WALK, Door_Close (tag, D_FAST) 119 = WALK, Floor_RaiseToNearest (tag, F_SLOW) 121 = WALK, Plat_DownWaitUpStayLip (tag, P_TURBO, PLATWAIT, 0) 124 = WALK, Teleport_EndGame () 125 = MONWALK, Teleport (0, tag) 130 = WALK, Floor_RaiseToNearest (tag, F_FAST) -141 = WALK, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 10) +141 = WALK, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 0) 174 = WALK, ACS_ExecuteAlways (0, 0, 174, tag) 183 = WALK, ACS_ExecuteAlways (0, 0, 183, tag) 178 = WALK, Generic_Stairs (tag, ST_FAST, 16, 0, 0) 179 = WALK, Ceiling_LowerToFloor (tag, C_SLOW) 187 = WALK|REP, ACS_ExecuteAlways (0, 0, 187, tag) 188 = WALK|REP, ACS_ExecuteAlways (0, 0, 188, tag) -200 = WALK, ACS_ExecuteAlways (0, 0, 200, tag) +200 = WALK|REP, ACS_ExecuteAlways (0, 0, 200, tag) 201 = WALK, SendToCommunicator (tag, 1, 0) 202 = WALK, SendToCommunicator (tag, 0, 0) 210 = WALK, SendToCommunicator (tag, 0, 1) @@ -146,7 +146,7 @@ RetailOnly = 121 216 = WALK|REP, ACS_ExecuteAlways (0, 0, 216, tag) 90 = WALK|REP, Door_Raise (tag, D_SLOW, VDOORWAIT) 72 = WALK|REP, Ceiling_LowerAndCrushDist (tag, C_SLOW, 10) - 73 = WALK|REP, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 10) + 73 = WALK|REP, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 0) 74 = WALK|REP, Ceiling_CrushStop (tag) 75 = WALK|REP, Door_Close (tag, D_SLOW) 76 = WALK|REP, Door_CloseWaitOpen (tag, D_SLOW, 240) @@ -164,7 +164,7 @@ RetailOnly = 121 91 = WALK|REP, Floor_RaiseToLowestCeiling (tag, F_SLOW) 92 = WALK|REP, Floor_RaiseByValue (tag, F_SLOW, 64) 93 = WALK|REP, Floor_RaiseByValueTxTy (tag, F_SLOW, 24) - 94 = WALK|REP, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 94 = WALK|REP, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 95 = WALK|REP, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) 96 = WALK|REP, Floor_RaiseByTexture (tag, F_SLOW) 97 = WALK|REP|MONST, Teleport (0, tag) @@ -247,7 +247,7 @@ RetailOnly = 121 49 = USE, Ceiling_CrushAndRaiseDist (tag, 8, C_SLOW, 0, 2) 50 = USE, Door_Close (tag, D_SLOW) 51 = USE, Teleport_EndGame (0) - 55 = USE, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 55 = USE, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 101 = USE, Floor_RaiseToLowestCeiling (tag, F_SLOW) 102 = USE, Floor_LowerToHighest (tag, F_SLOW, 128) 103 = USE, Door_Open (tag, D_SLOW) @@ -289,7 +289,7 @@ RetailOnly = 121 64 = USE|REP, Floor_RaiseToLowestCeiling (tag, F_SLOW) 66 = USE|REP, Plat_UpByValueStayTx (tag, P_SLOW/2, 3) 67 = USE|REP, Plat_UpByValueStayTx (tag, P_SLOW/2, 4) - 65 = USE|REP, Floor_RaiseAndCrush (tag, F_SLOW, 10, 2) + 65 = USE|REP, Floor_RaiseAndCrushDoom (tag, F_SLOW, 10, 2) 68 = USE|REP, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) 69 = USE|REP, Floor_RaiseToNearest (tag, F_SLOW) 70 = USE|REP, Floor_LowerToHighest (tag, F_FAST, 128) diff --git a/zdoom.vcproj b/zdoom.vcproj index b0c59a076..e75e4d434 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2602,10 +2602,6 @@ /> - - @@ -2618,6 +2614,14 @@ RelativePath=".\src\oplsynth\muslib.h" > + + + + @@ -2626,6 +2630,18 @@ RelativePath=".\src\oplsynth\opl_mus_player.h" > + + + + + + - - - - - - - - - - - - - - diff --git a/zlib/CMakeLists.txt b/zlib/CMakeLists.txt index c2607ce7c..7ac44940a 100644 --- a/zlib/CMakeLists.txt +++ b/zlib/CMakeLists.txt @@ -115,7 +115,7 @@ set(ZLIB_SRCS trees.c uncompr.c zutil.c - win32/zlib1.rc +# win32/zlib1.rc ) # parse the full version number from zlib.h and include in ZLIB_FULL_VERSION