From: Holger Schemel Date: Sun, 18 Feb 2024 13:34:28 +0000 (+0100) Subject: added optional button to restart game (door, panel and touch variants) X-Git-Tag: 4.3.8.2^0 X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=commitdiff_plain;h=HEAD;hp=3bb225da5092d0741870829d9f8f8642070d29c5 added optional button to restart game (door, panel and touch variants) --- diff --git a/.gitignore b/.gitignore index f0268317..2115c84b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ src/conf_cus.c src/conf_cus.h src/conf_grp.c src/conf_grp.h +src/conf_emp.c +src/conf_emp.h src/conf_e2g.c src/conf_esg.c src/conf_e2s.c diff --git a/CREDITS b/CREDITS index 37026546..db28dc63 100644 --- a/CREDITS +++ b/CREDITS @@ -14,7 +14,7 @@ Special thanks to Michael Stopp and Philip Jespersen for creating "Supaplex"! Special thanks to Hiroyuki Imabayashi for creating "Sokoban"! -Special thanks to Alan Bond and Jürgen Bonhagen for the continuous creation +Special thanks to Alan Bond and Jürgen Bonhagen for the continuous creation of outstanding level sets! Thanks to Peter Elzner for ideas and inspiration by Diamond Caves. @@ -32,9 +32,15 @@ based on the Supaplex Speed Fix by Herman Perk, which is based on the original Supaplex game by Michael Stopp and Philip Jespersen. Thanks a lot for this contribution! -Thanks to Karl Hörnell for some additional toon graphics taken from "Iceblox": +Thanks to Thomas Andrae for some additional toon graphics from Mirror Magic: +The walking dwarf, the blue balloon jumper and the dwarf with five balloons. + +Thanks to Karl Hörnell for some additional toon graphics taken from "Iceblox": The penguin, the mole, the pig and the dragon. +Thanks to Majid Katzer for some additional sounds and music from Mirror Magic: +The hall of fame fanfare and the info screen rhythm loop. + Thanks to the composers of the included music modules: "mod.apoplexy" by bee hunter/jazz, "mod.chiptune" by 4-mat/anarchy and "mod.cream_of_the_earth" by romeoknight. @@ -47,7 +53,7 @@ The networking code was derived from xtris. Thanks! Thanks to Francesco Carta for the comprehensive Rocks'n'Diamonds manual. -Thanks to Niko Böhm for the Rocks'n'Diamonds documentation wiki. +Thanks to Niko Böhm for the Rocks'n'Diamonds documentation wiki. Thanks to Simon Forsberg for being the moderator of the R'n'D forum. diff --git a/Makefile b/Makefile index 5dd17c24..a8213040 100644 --- a/Makefile +++ b/Makefile @@ -21,19 +21,12 @@ CC = gcc # (this must be set to "gmake" for some systems) MAKE = make -# directory for read-only game data (like graphics, sounds, levels) +# directory for (read-only) game data (like graphics, sounds, levels) # (this directory is usually the game's installation directory) # default is '.' to be able to run program without installation -# RO_GAME_DIR = . +# BASE_PATH = . # use the following setting for Debian / Ubuntu installations: -# RO_GAME_DIR = /usr/share/games/rocksndiamonds - -# directory for writable game data (like highscore files) -# (if no "scores" directory exists, scores are saved in user data directory) -# default is '.' to be able to run program without installation -# RW_GAME_DIR = . -# use the following setting for Debian / Ubuntu installations: -# RW_GAME_DIR = /var/games/rocksndiamonds +# BASE_PATH = /usr/share/games/rocksndiamonds # uncomment if system has no joystick include file # JOYSTICK = -DNO_JOYSTICK @@ -96,7 +89,7 @@ clean-android: android-clean # development targets # ----------------------------------------------------------------------------- -MAKE_ENGINETEST = ./Scripts/make_enginetest.sh +MAKE_ENGINETEST = ./tests/enginetest/enginetest.sh MAKE_LEVELSKETCH = ./Scripts/make_levelsketch_images.sh auto-conf: @@ -126,18 +119,6 @@ depend dep: enginetest: all $(MAKE_ENGINETEST) -enginetestcustom: all - $(MAKE_ENGINETEST) custom - -enginetestfast: all - $(MAKE_ENGINETEST) fast - -enginetestnew: all - $(MAKE_ENGINETEST) new - -leveltest: all - $(MAKE_ENGINETEST) leveltest - levelsketch_images: all $(MAKE_LEVELSKETCH) @@ -187,6 +168,9 @@ dist-package-mac: dist-package-android: $(MAKE_DIST) package android +dist-package-emscripten: + $(MAKE_DIST) package emscripten + dist-copy-package-linux: $(MAKE_DIST) copy-package linux @@ -202,6 +186,9 @@ dist-copy-package-mac: dist-copy-package-android: $(MAKE_DIST) copy-package android +dist-copy-package-emscripten: + $(MAKE_DIST) copy-package emscripten + dist-upload-linux: $(MAKE_DIST) upload linux @@ -217,12 +204,19 @@ dist-upload-mac: dist-upload-android: $(MAKE_DIST) upload android +dist-upload-emscripten: + $(MAKE_DIST) upload emscripten + +dist-deploy-emscripten: + $(MAKE_DIST) deploy emscripten + dist-package-all: $(MAKE) dist-package-linux $(MAKE) dist-package-win32 $(MAKE) dist-package-win64 $(MAKE) dist-package-mac $(MAKE) dist-package-android + $(MAKE) dist-package-emscripten dist-copy-package-all: $(MAKE) dist-copy-package-linux @@ -230,6 +224,7 @@ dist-copy-package-all: $(MAKE) dist-copy-package-win64 $(MAKE) dist-copy-package-mac $(MAKE) dist-copy-package-android + $(MAKE) dist-copy-package-emscripten dist-upload-all: $(MAKE) dist-upload-linux @@ -237,8 +232,12 @@ dist-upload-all: $(MAKE) dist-upload-win64 $(MAKE) dist-upload-mac $(MAKE) dist-upload-android + $(MAKE) dist-upload-emscripten + +dist-deploy-all: + $(MAKE) dist-deploy-emscripten -dist-release-all: dist-package-all dist-copy-package-all dist-upload-all +dist-release-all: dist-package-all dist-copy-package-all dist-upload-all dist-deploy-all package-all: dist-package-all @@ -246,4 +245,6 @@ copy-package-all: dist-copy-package-all upload-all: dist-upload-all +deploy-all: dist-deploy-all + release-all: dist-release-all diff --git a/build-projects/android/SDL_VERSIONS b/build-projects/android/SDL_VERSIONS index b0613a71..f4fd7015 100644 --- a/build-projects/android/SDL_VERSIONS +++ b/build-projects/android/SDL_VERSIONS @@ -1,4 +1,4 @@ -SDL2-2.0.12 +SDL2-2.0.20 SDL2_image-2.0.5 SDL2_mixer-2.0.4 SDL2_net-2.0.1 diff --git a/build-projects/android/app/build.gradle.tmpl b/build-projects/android/app/build.gradle.tmpl index 68c234b6..5eb4854d 100644 --- a/build-projects/android/app/build.gradle.tmpl +++ b/build-projects/android/app/build.gradle.tmpl @@ -8,7 +8,7 @@ else { } android { - compileSdkVersion 26 + compileSdkVersion 31 defaultConfig { if (buildAsApplication) { @@ -16,7 +16,7 @@ android { } minSdkVersion 17 - targetSdkVersion 26 + targetSdkVersion 31 versionCode __VERSION_CODE__ versionName "__VERSION_NAME__" diff --git a/build-projects/android/app/src/main/AndroidManifest.xml.tmpl b/build-projects/android/app/src/main/AndroidManifest.xml.tmpl index 6e01acf9..8331804d 100644 --- a/build-projects/android/app/src/main/AndroidManifest.xml.tmpl +++ b/build-projects/android/app/src/main/AndroidManifest.xml.tmpl @@ -42,11 +42,14 @@ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:hardwareAccelerated="true"> - diff --git a/build-projects/android/app/src/main/java/org/artsoft/rocksndiamonds/RocksNDiamonds.java b/build-projects/android/app/src/main/java/org/artsoft/rocksndiamonds/RocksNDiamonds.java deleted file mode 100644 index 1415095b..00000000 --- a/build-projects/android/app/src/main/java/org/artsoft/rocksndiamonds/RocksNDiamonds.java +++ /dev/null @@ -1,6 +0,0 @@ - -package org.artsoft.rocksndiamonds; - -import org.libsdl.app.SDLActivity; - -public class RocksNDiamonds extends SDLActivity { } diff --git a/build-projects/android/app/src/main/java/org/artsoft/rocksndiamonds/rocksndiamonds.java b/build-projects/android/app/src/main/java/org/artsoft/rocksndiamonds/rocksndiamonds.java new file mode 100644 index 00000000..eeec2ea4 --- /dev/null +++ b/build-projects/android/app/src/main/java/org/artsoft/rocksndiamonds/rocksndiamonds.java @@ -0,0 +1,6 @@ + +package org.artsoft.rocksndiamonds; + +import org.libsdl.app.SDLActivity; + +public class rocksndiamonds extends SDLActivity { } diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java index 94a28189..65c5a423 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java @@ -564,10 +564,10 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe return "Steam Controller"; } - @Override + @Override public UsbDevice getDevice() { - return null; - } + return null; + } @Override public boolean open() { diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index 56f677e6..802c7254 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -7,6 +7,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; +import android.os.Build; import android.util.Log; import android.content.BroadcastReceiver; import android.content.Context; @@ -104,36 +105,6 @@ public class HIDDeviceManager { private HIDDeviceManager(final Context context) { mContext = context; - // Make sure we have the HIDAPI library loaded with the native functions - try { - SDL.loadLibrary("hidapi"); - } catch (Throwable e) { - Log.w(TAG, "Couldn't load hidapi: " + e.toString()); - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setCancelable(false); - builder.setTitle("SDL HIDAPI Error"); - builder.setMessage("Please report the following error to the SDL maintainers: " + e.getMessage()); - builder.setNegativeButton("Quit", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try { - // If our context is an activity, exit rather than crashing when we can't - // call our native functions. - Activity activity = (Activity)context; - - activity.finish(); - } - catch (ClassCastException cce) { - // Context wasn't an activity, there's nothing we can do. Give up and return. - } - } - }); - builder.show(); - - return; - } - HIDDeviceRegisterCallback(); mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); @@ -148,9 +119,6 @@ public class HIDDeviceManager { { mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); } - - initializeUSB(); - initializeBluetooth(); } public Context getContext() { @@ -173,6 +141,9 @@ public class HIDDeviceManager { private void initializeUSB() { mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); + if (mUsbManager == null) { + return; + } /* // Logging @@ -275,6 +246,7 @@ public class HIDDeviceManager { 0x15e4, // Numark 0x162e, // Joytech 0x1689, // Razer Onza + 0x1949, // Lab126, Inc. 0x1bad, // Harmonix 0x24c6, // PowerA }; @@ -353,9 +325,18 @@ public class HIDDeviceManager { private void connectHIDDeviceUSB(UsbDevice usbDevice) { synchronized (this) { + int interface_mask = 0; for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { UsbInterface usbInterface = usbDevice.getInterface(interface_index); if (isHIDDeviceInterface(usbDevice, usbInterface)) { + // Check to see if we've already added this interface + // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive + int interface_id = usbInterface.getId(); + if ((interface_mask & (1 << interface_id)) != 0) { + continue; + } + interface_mask |= (1 << interface_id); + HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); int id = device.getId(); mDevicesById.put(id, device); @@ -368,11 +349,17 @@ public class HIDDeviceManager { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); - if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT <= 30 && + mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); return; } + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) { + Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); + return; + } + // Find bonded bluetooth controllers and create SteamControllers for them mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { @@ -555,6 +542,18 @@ public class HIDDeviceManager { ////////// JNI interface functions ////////////////////////////////////////////////////////////////////////////////////////////////////// + public boolean initialize(boolean usb, boolean bluetooth) { + Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); + + if (usb) { + initializeUSB(); + } + if (bluetooth) { + initializeBluetooth(); + } + return true; + } + public boolean openDevice(int deviceID) { Log.v(TAG, "openDevice deviceID=" + deviceID); HIDDevice device = getDevice(deviceID); @@ -568,7 +567,14 @@ public class HIDDeviceManager { if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { HIDDeviceOpenPending(deviceID); try { - mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0)); + final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 + int flags; + if (Build.VERSION.SDK_INT >= 31) { + flags = FLAG_MUTABLE; + } else { + flags = 0; + } + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); } catch (Exception e) { Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); HIDDeviceOpenResult(deviceID, false); diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java index 33816e34..d20fe80b 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java @@ -53,7 +53,12 @@ class HIDDeviceUSB implements HIDDevice { public String getSerialNumber() { String result = null; if (Build.VERSION.SDK_INT >= 21) { - result = mDevice.getSerialNumber(); + try { + result = mDevice.getSerialNumber(); + } + catch (SecurityException exception) { + //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); + } } if (result == null) { result = ""; diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/SDL.java b/build-projects/android/app/src/main/java/org/libsdl/app/SDL.java index fb7f7319..dafc0cb8 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/SDL.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/SDL.java @@ -2,7 +2,8 @@ package org.libsdl.app; import android.content.Context; -import java.lang.reflect.*; +import java.lang.Class; +import java.lang.reflect.Method; /** SDL library initialization @@ -51,16 +52,16 @@ public class SDL { // To use ReLinker, just add it as a dependency. For more information, see // https://github.com/KeepSafe/ReLinker for ReLinker's repository. // - Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); - Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); - Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); - Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); + Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); + Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); + Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); + Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if // they've changed during updates. Method forceMethod = relinkClass.getDeclaredMethod("force"); Object relinkInstance = forceMethod.invoke(null); - Class relinkInstanceClass = relinkInstance.getClass(); + Class relinkInstanceClass = relinkInstance.getClass(); // Actually load the library! Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); @@ -77,7 +78,7 @@ public class SDL { catch (final SecurityException se) { throw se; } - } + } } protected static Context mContext; diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/build-projects/android/app/src/main/java/org/libsdl/app/SDLActivity.java index a61dd6db..f8b3e1eb 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -1,35 +1,62 @@ package org.libsdl.app; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Hashtable; -import java.lang.reflect.Method; -import java.lang.Math; - -import android.app.*; -import android.content.*; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.UiModeManager; +import android.content.ClipboardManager; +import android.content.ClipData; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.text.InputType; -import android.view.*; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.PointerIcon; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.widget.RelativeLayout; import android.widget.Button; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; -import android.os.*; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.SparseArray; -import android.graphics.*; -import android.graphics.drawable.Drawable; -import android.hardware.*; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ApplicationInfo; +import android.widget.Toast; + +import java.util.Hashtable; +import java.util.Locale; + /** SDL Activity @@ -41,7 +68,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); // Cursor types - private static final int SDL_SYSTEM_CURSOR_NONE = -1; + // private static final int SDL_SYSTEM_CURSOR_NONE = -1; private static final int SDL_SYSTEM_CURSOR_ARROW = 0; private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; private static final int SDL_SYSTEM_CURSOR_WAIT = 2; @@ -62,6 +89,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; protected static int mCurrentOrientation; + protected static Locale mCurrentLocale; // Handle the state of the native layer public enum NativeState { @@ -72,7 +100,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static NativeState mCurrentNativeState; /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ - public static boolean mBrokenLibraries; + public static boolean mBrokenLibraries = true; // Main components protected static SDLActivity mSingleton; @@ -93,8 +121,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if (mMotionListener == null) { if (Build.VERSION.SDK_INT >= 26) { mMotionListener = new SDLGenericMotionListener_API26(); - } else - if (Build.VERSION.SDK_INT >= 24) { + } else if (Build.VERSION.SDK_INT >= 24) { mMotionListener = new SDLGenericMotionListener_API24(); } else { mMotionListener = new SDLGenericMotionListener_API12(); @@ -137,7 +164,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh */ protected String[] getLibraries() { return new String[] { - "hidapi", "SDL2", "SDL2_image", "SDL2_mixer", @@ -175,7 +201,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mCursors = new Hashtable(); mLastCursorID = 0; mSDLThread = null; - mBrokenLibraries = false; mIsResumedCalled = false; mHasFocus = true; mNextNativeState = NativeState.INIT; @@ -200,6 +225,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh String errorMsgBrokenLib = ""; try { loadLibraries(); + mBrokenLibraries = false; /* success */ } catch(UnsatisfiedLinkError e) { System.err.println(e.getMessage()); mBrokenLibraries = true; @@ -243,7 +269,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mSingleton = this; SDL.setContext(this); - mClipboardHandler = new SDLClipboardHandler_API11(); + mClipboardHandler = new SDLClipboardHandler(); mHIDDeviceManager = HIDDeviceManager.acquire(this); @@ -258,6 +284,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // Only record current orientation SDLActivity.onNativeOrientationChanged(mCurrentOrientation); + try { + if (Build.VERSION.SDK_INT < 24) { + mCurrentLocale = getContext().getResources().getConfiguration().locale; + } else { + mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); + } + } catch(Exception ignored) { + } + setContentView(mLayout); setWindowStyle(false); @@ -343,11 +378,14 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } public static int getCurrentOrientation() { - final Context context = SDLActivity.getContext(); - final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - int result = SDL_ORIENTATION_UNKNOWN; + Activity activity = (Activity)getContext(); + if (activity == null) { + return result; + } + Display display = activity.getWindowManager().getDefaultDisplay(); + switch (display.getRotation()) { case Surface.ROTATION_0: result = SDL_ORIENTATION_PORTRAIT; @@ -407,6 +445,21 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh SDLActivity.nativeLowMemory(); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + Log.v(TAG, "onConfigurationChanged()"); + super.onConfigurationChanged(newConfig); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { + mCurrentLocale = newConfig.locale; + SDLActivity.onNativeLocaleChanged(); + } + } + @Override protected void onDestroy() { Log.v(TAG, "onDestroy()"); @@ -446,8 +499,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // If we do, the normal hardware back button will no longer work and people have to use home, // but the mouse right click will work. // - String trapBack = SDLActivity.nativeGetHint("SDL_ANDROID_TRAP_BACK_BUTTON"); - if ((trapBack != null) && trapBack.equals("1")) { + boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); + if (trapBack) { // Exit and let the mouse handler handle this button (if appropriate) return; } @@ -540,11 +593,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh mSDLThread.start(); // No nativeResume(), don't signal Android_ResumeSem - mSurface.handleResume(); } else { nativeResume(); - mSurface.handleResume(); } + mSurface.handleResume(); mCurrentNativeState = mNextNativeState; } @@ -555,7 +607,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_CHANGE_WINDOW_STYLE = 2; static final int COMMAND_TEXTEDIT_HIDE = 3; - static final int COMMAND_CHANGE_SURFACEVIEW_FORMAT = 4; static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; @@ -596,34 +647,32 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } break; case COMMAND_CHANGE_WINDOW_STYLE: - if (Build.VERSION.SDK_INT < 19) { - // This version of Android doesn't support the immersive fullscreen mode - break; - } - if (context instanceof Activity) { - Window window = ((Activity) context).getWindow(); - if (window != null) { - if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { - int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | + if (Build.VERSION.SDK_INT >= 19) { + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { + int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; - window.getDecorView().setSystemUiVisibility(flags); - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - SDLActivity.mFullscreenModeActive = true; - } else { - int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; - window.getDecorView().setSystemUiVisibility(flags); - window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - SDLActivity.mFullscreenModeActive = false; + window.getDecorView().setSystemUiVisibility(flags); + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + SDLActivity.mFullscreenModeActive = true; + } else { + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; + window.getDecorView().setSystemUiVisibility(flags); + window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + SDLActivity.mFullscreenModeActive = false; + } } + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); } - } else { - Log.e(TAG, "error handling message, getContext() returned no Activity"); } break; case COMMAND_TEXTEDIT_HIDE: @@ -646,7 +695,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { - if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -655,32 +704,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } break; } - case COMMAND_CHANGE_SURFACEVIEW_FORMAT: - { - int format = (Integer) msg.obj; - int pf; - - if (SDLActivity.mSurface == null) { - return; - } - - SurfaceHolder holder = SDLActivity.mSurface.getHolder(); - if (holder == null) { - return; - } - - if (format == 1) { - pf = PixelFormat.RGBA_8888; - } else if (format == 2) { - pf = PixelFormat.RGBX_8888; - } else { - pf = PixelFormat.RGB_565; - } - - holder.setFormat(pf); - - break; - } default: if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { Log.e(TAG, "error handling message, command is " + msg.arg1); @@ -699,53 +722,53 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh msg.obj = data; boolean result = commandHandler.sendMessage(msg); - if ((Build.VERSION.SDK_INT >= 19) && (command == COMMAND_CHANGE_WINDOW_STYLE)) { - // Ensure we don't return until the resize has actually happened, - // or 500ms have passed. - - boolean bShouldWait = false; - - if (data instanceof Integer) { - // Let's figure out if we're already laid out fullscreen or not. - Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); - display.getRealMetrics( realMetrics ); - - boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && - (realMetrics.heightPixels == mSurface.getHeight())); - - if (((Integer)data).intValue() == 1) { - // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going - // to change size and should wait for surfaceChanged() before we return, so the size - // is right back in native code. If we're already laid out fullscreen, though, we're - // not going to change size even if we change decor modes, so we shouldn't wait for - // surfaceChanged() -- which may not even happen -- and should return immediately. - bShouldWait = !bFullscreenLayout; - } - else { - // If we're laid out fullscreen (even if the status bar and nav bar are present), - // or are actively in fullscreen, we're going to change size and should wait for - // surfaceChanged before we return, so the size is right back in native code. - bShouldWait = bFullscreenLayout; + if (Build.VERSION.SDK_INT >= 19) { + if (command == COMMAND_CHANGE_WINDOW_STYLE) { + // Ensure we don't return until the resize has actually happened, + // or 500ms have passed. + + boolean bShouldWait = false; + + if (data instanceof Integer) { + // Let's figure out if we're already laid out fullscreen or not. + Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + DisplayMetrics realMetrics = new DisplayMetrics(); + display.getRealMetrics(realMetrics); + + boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && + (realMetrics.heightPixels == mSurface.getHeight())); + + if ((Integer) data == 1) { + // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going + // to change size and should wait for surfaceChanged() before we return, so the size + // is right back in native code. If we're already laid out fullscreen, though, we're + // not going to change size even if we change decor modes, so we shouldn't wait for + // surfaceChanged() -- which may not even happen -- and should return immediately. + bShouldWait = !bFullscreenLayout; + } else { + // If we're laid out fullscreen (even if the status bar and nav bar are present), + // or are actively in fullscreen, we're going to change size and should wait for + // surfaceChanged before we return, so the size is right back in native code. + bShouldWait = bFullscreenLayout; + } } - } - if (bShouldWait && (SDLActivity.getContext() != null)) { - // We'll wait for the surfaceChanged() method, which will notify us - // when called. That way, we know our current size is really the - // size we need, instead of grabbing a size that's still got - // the navigation and/or status bars before they're hidden. - // - // We'll wait for up to half a second, because some devices - // take a surprisingly long time for the surface resize, but - // then we'll just give up and return. - // - synchronized(SDLActivity.getContext()) { - try { - SDLActivity.getContext().wait(500); - } - catch (InterruptedException ie) { - ie.printStackTrace(); + if (bShouldWait && (SDLActivity.getContext() != null)) { + // We'll wait for the surfaceChanged() method, which will notify us + // when called. That way, we know our current size is really the + // size we need, instead of grabbing a size that's still got + // the navigation and/or status bars before they're hidden. + // + // We'll wait for up to half a second, because some devices + // take a surprisingly long time for the surface resize, but + // then we'll just give up and return. + // + synchronized (SDLActivity.getContext()) { + try { + SDLActivity.getContext().wait(500); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } } } } @@ -764,7 +787,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static native void nativeResume(); public static native void nativeFocusChanged(boolean hasFocus); public static native void onNativeDropFile(String filename); - public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate); + public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); public static native void onNativeResize(); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); @@ -780,10 +803,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static native void onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); public static native String nativeGetHint(String name); + public static native boolean nativeGetHintBoolean(String name, boolean default_value); public static native void nativeSetenv(String name, String value); public static native void onNativeOrientationChanged(int orientation); public static native void nativeAddTouch(int touchId, String name); public static native void nativePermissionResult(int requestCode, boolean result); + public static native void onNativeLocaleChanged(); /** * This method is called by SDL using JNI. @@ -838,15 +863,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; } - boolean is_landscape_allowed = (orientation_landscape == -1 ? false : true); - boolean is_portrait_allowed = (orientation_portrait == -1 ? false : true); - int req = -1; /* Requested orientation */ + boolean is_landscape_allowed = (orientation_landscape != -1); + boolean is_portrait_allowed = (orientation_portrait != -1); + int req; /* Requested orientation */ /* No valid hint, nothing is explicitly allowed */ if (!is_portrait_allowed && !is_landscape_allowed) { if (resizable) { /* All orientations are allowed */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Fixed window and nothing specified. Get orientation from w/h of created window */ req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); @@ -856,7 +881,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if (resizable) { if (is_portrait_allowed && is_landscape_allowed) { /* hint allows both landscape and portrait, promote to full sensor */ - req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); @@ -872,7 +897,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } } - Log.v("SDL", "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); + Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); mSingleton.setRequestedOrientation(req); } @@ -938,11 +963,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh */ public static boolean supportsRelativeMouse() { - // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs - if (isChromebook()) { - return false; - } - // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under // Android 7 APIs, and simply returns no data under Android 8 APIs. // @@ -976,7 +996,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if (mSingleton == null) { return false; } - return mSingleton.sendCommand(command, Integer.valueOf(param)); + return mSingleton.sendCommand(command, param); } /** @@ -1000,30 +1020,30 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { return true; } - if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV")) { - return true; - } - return false; + return Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV"); } - /** - * This method is called by SDL using JNI. - */ - public static boolean isTablet() { + public static double getDiagonal() + { DisplayMetrics metrics = new DisplayMetrics(); Activity activity = (Activity)getContext(); if (activity == null) { - return false; + return 0.0; } activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; - double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); + return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); + } + /** + * This method is called by SDL using JNI. + */ + public static boolean isTablet() { // If our diagonal size is seven inches or greater, we consider ourselves a tablet. - return (dDiagonal >= 7.0); + return (getDiagonal() >= 7.0); } /** @@ -1045,7 +1065,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } try { final Configuration config = getContext().getResources().getConfiguration(); - final Class configClass = config.getClass(); + final Class configClass = config.getClass(); return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) == configClass.getField("semDesktopModeEnabled").getInt(config); } catch(Exception ignored) { @@ -1065,6 +1085,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh */ public static boolean getManifestEnvironmentVariables() { try { + if (getContext() == null) { + return false; + } + ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = applicationInfo.metaData; if (bundle == null) { @@ -1082,7 +1106,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh /* environment variables set! */ return true; } catch (Exception e) { - Log.v("SDL", "exception " + e.toString()); + Log.v(TAG, "exception " + e.toString()); } return false; } @@ -1090,7 +1114,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // This method is called by SDLControllerManager's API 26 Generic Motion Handler. public static View getContentView() { - return mSingleton.mLayout; + return mLayout; } static class ShowTextInputTask implements Runnable { @@ -1170,14 +1194,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return SDLActivity.mSurface.getNativeSurface(); } - /** - * This method is called by SDL using JNI. - */ - public static void setSurfaceViewFormat(int format) { - mSingleton.sendCommand(COMMAND_CHANGE_SURFACEVIEW_FORMAT, format); - return; - } - // Input /** @@ -1186,92 +1202,19 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh public static void initTouch() { int[] ids = InputDevice.getDeviceIds(); - for (int i = 0; i < ids.length; ++i) { - InputDevice device = InputDevice.getDevice(ids[i]); + for (int id : ids) { + InputDevice device = InputDevice.getDevice(id); if (device != null && (device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) != 0) { nativeAddTouch(device.getId(), device.getName()); } } } - // APK expansion files support - - /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ - private static Object expansionFile; - - /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ - private static Method expansionFileMethod; - - /** - * This method is called by SDL using JNI. - * @return an InputStream on success or null if no expansion file was used. - * @throws IOException on errors. Message is set for the SDL error message. - */ - public static InputStream openAPKExpansionInputStream(String fileName) throws IOException { - // Get a ZipResourceFile representing a merger of both the main and patch files - if (expansionFile == null) { - String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"); - if (mainHint == null) { - return null; // no expansion use if no main version was set - } - String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"); - if (patchHint == null) { - return null; // no expansion use if no patch version was set - } - - Integer mainVersion; - Integer patchVersion; - try { - mainVersion = Integer.valueOf(mainHint); - patchVersion = Integer.valueOf(patchHint); - } catch (NumberFormatException ex) { - ex.printStackTrace(); - throw new IOException("No valid file versions set for APK expansion files", ex); - } - - try { - // To avoid direct dependency on Google APK expansion library that is - // not a part of Android SDK we access it using reflection - expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") - .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) - .invoke(null, SDL.getContext(), mainVersion, patchVersion); - - expansionFileMethod = expansionFile.getClass() - .getMethod("getInputStream", String.class); - } catch (Exception ex) { - ex.printStackTrace(); - expansionFile = null; - expansionFileMethod = null; - throw new IOException("Could not access APK expansion support library", ex); - } - } - - // Get an input stream for a known file inside the expansion file ZIPs - InputStream fileStream; - try { - fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); - } catch (Exception ex) { - // calling "getInputStream" failed - ex.printStackTrace(); - throw new IOException("Could not open stream from APK expansion file", ex); - } - - if (fileStream == null) { - // calling "getInputStream" was successful but null was returned - throw new IOException("Could not find path in APK expansion file"); - } - - return fileStream; - } - // Messagebox /** Result of current messagebox. Also used for blocking the calling thread. */ protected final int[] messageboxSelection = new int[1]; - /** Id of current dialog. */ - protected int dialogs = 0; - /** * This method is called by SDL using JNI. * Shows the messagebox from UI thread and block calling thread. @@ -1315,7 +1258,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh runOnUiThread(new Runnable() { @Override public void run() { - showDialog(dialogs++, args); + messageboxCreateAndShow(args); } }); @@ -1335,8 +1278,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return messageboxSelection[0]; } - @Override - protected Dialog onCreateDialog(int ignore, Bundle args) { + protected void messageboxCreateAndShow(Bundle args) { // TODO set values from "flags" to messagebox dialog @@ -1365,7 +1307,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // create dialog with title and a listener to wake up calling thread - final Dialog dialog = new Dialog(this); + final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(args.getString("title")); dialog.setCancelable(false); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @@ -1451,7 +1393,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh // add content to dialog and return - dialog.setContentView(content); + dialog.setView(content); dialog.setOnKeyListener(new Dialog.OnKeyListener() { @Override public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) { @@ -1466,20 +1408,22 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh } }); - return dialog; + dialog.show(); } private final Runnable rehideSystemUi = new Runnable() { @Override public void run() { - int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | + if (Build.VERSION.SDK_INT >= 19) { + int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; - SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags); + SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags); + } } }; @@ -1535,6 +1479,19 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh return mLastCursorID; } + /** + * This method is called by SDL using JNI. + */ + public static void destroyCustomCursor(int cursorID) { + if (Build.VERSION.SDK_INT >= 24) { + try { + mCursors.remove(cursorID); + } catch (Exception e) { + } + } + return; + } + /** * This method is called by SDL using JNI. */ @@ -1624,11 +1581,78 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - nativePermissionResult(requestCode, true); - } else { - nativePermissionResult(requestCode, false); + boolean result = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED); + nativePermissionResult(requestCode, result); + } + + /** + * This method is called by SDL using JNI. + */ + public static int openURL(String url) + { + try { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + + int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK; + if (Build.VERSION.SDK_INT >= 21) { + flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + } else { + flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET; + } + i.addFlags(flags); + + mSingleton.startActivity(i); + } catch (Exception ex) { + return -1; + } + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static int showToast(String message, int duration, int gravity, int xOffset, int yOffset) + { + if(null == mSingleton) { + return - 1; + } + + try + { + class OneShotTask implements Runnable { + String mMessage; + int mDuration; + int mGravity; + int mXOffset; + int mYOffset; + + OneShotTask(String message, int duration, int gravity, int xOffset, int yOffset) { + mMessage = message; + mDuration = duration; + mGravity = gravity; + mXOffset = xOffset; + mYOffset = yOffset; + } + + public void run() { + try + { + Toast toast = Toast.makeText(mSingleton, mMessage, mDuration); + if (mGravity >= 0) { + toast.setGravity(mGravity, mXOffset, mYOffset); + } + toast.show(); + } catch(Exception ex) { + Log.e(TAG, ex.getMessage()); + } + } + } + mSingleton.runOnUiThread(new OneShotTask(message, duration, gravity, xOffset, yOffset)); + } catch(Exception ex) { + return -1; } + return 0; } } @@ -1655,13 +1679,12 @@ class SDLMain implements Runnable { Log.v("SDL", "Finished main function"); - if (SDLActivity.mSingleton == null || SDLActivity.mSingleton.isFinishing()) { - // Activity is already being destroyed - } else { + if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) { // Let's finish the Activity SDLActivity.mSDLThread = null; SDLActivity.mSingleton.finish(); - } + } // else: Activity is already being destroyed + } } @@ -1755,30 +1778,6 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, return; } - int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default - switch (format) { - case PixelFormat.RGBA_8888: - Log.v("SDL", "pixel format RGBA_8888"); - sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888 - break; - case PixelFormat.RGBX_8888: - Log.v("SDL", "pixel format RGBX_8888"); - sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888 - break; - case PixelFormat.RGB_565: - Log.v("SDL", "pixel format RGB_565"); - sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 - break; - case PixelFormat.RGB_888: - Log.v("SDL", "pixel format RGB_888"); - // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead? - sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888 - break; - default: - Log.v("SDL", "pixel format unknown " + format); - break; - } - mWidth = width; mHeight = height; int nDeviceWidth = width; @@ -1786,13 +1785,13 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, try { if (Build.VERSION.SDK_INT >= 17) { - android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); + DisplayMetrics realMetrics = new DisplayMetrics(); mDisplay.getRealMetrics( realMetrics ); nDeviceWidth = realMetrics.widthPixels; nDeviceHeight = realMetrics.heightPixels; } + } catch(Exception ignored) { } - catch ( java.lang.Throwable throwable ) {} synchronized(SDLActivity.getContext()) { // In case we're waiting on a size change after going fullscreen, send a notification. @@ -1801,7 +1800,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, Log.v("SDL", "Window size: " + width + "x" + height); Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); - SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, sdlFormat, mDisplay.getRefreshRate()); + SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate()); SDLActivity.onNativeResize(); // Prevent a screen distortion glitch, @@ -1809,12 +1808,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, boolean skip = false; int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); - if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) - { - // Accept any - } - else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) - { + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { if (mWidth > mHeight) { skip = true; } @@ -1868,6 +1862,19 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, int deviceId = event.getDeviceId(); int source = event.getSource(); + if (source == InputDevice.SOURCE_UNKNOWN) { + InputDevice device = InputDevice.getDevice(deviceId); + if (device != null) { + source = device.getSources(); + } + } + +// if (event.getAction() == KeyEvent.ACTION_DOWN) { +// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); +// } else if (event.getAction() == KeyEvent.ACTION_UP) { +// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); +// } + // Dispatch the different events depending on where they come from // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD @@ -1888,24 +1895,14 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, } } - if (source == InputDevice.SOURCE_UNKNOWN) { - InputDevice device = InputDevice.getDevice(deviceId); - if (device != null) { - source = device.getSources(); - } - } - if ((source & InputDevice.SOURCE_KEYBOARD) != 0) { if (event.getAction() == KeyEvent.ACTION_DOWN) { - //Log.v("SDL", "key down: " + keyCode); if (SDLActivity.isTextInputEvent(event)) { SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); } SDLActivity.onNativeKeyDown(keyCode); return true; - } - else if (event.getAction() == KeyEvent.ACTION_UP) { - //Log.v("SDL", "key up: " + keyCode); + } else if (event.getAction() == KeyEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(keyCode); return true; } @@ -1932,22 +1929,34 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, @Override public boolean onTouch(View v, MotionEvent event) { /* Ref: http://developer.android.com/training/gestures/multi.html */ - final int touchDevId = event.getDeviceId(); + int touchDevId = event.getDeviceId(); final int pointerCount = event.getPointerCount(); int action = event.getActionMasked(); int pointerFingerId; - int mouseButton; int i = -1; float x,y,p; + /* + * Prevent id to be -1, since it's used in SDL internal for synthetic events + * Appears when using Android emulator, eg: + * adb shell input mouse tap 100 100 + * adb shell input touchscreen tap 100 100 + */ + if (touchDevId < 0) { + touchDevId -= 1; + } + // 12290 = Samsung DeX mode desktop mouse // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN // 0x2 = SOURCE_CLASS_POINTER if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { + int mouseButton = 1; try { - mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event); - } catch(Exception e) { - mouseButton = 1; // oh well. + Object object = event.getClass().getMethod("getButtonState").invoke(event); + if (object != null) { + mouseButton = (Integer) object; + } + } catch(Exception ignored) { } // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values @@ -1978,6 +1987,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, case MotionEvent.ACTION_DOWN: // Primary pointer up/down, the index is always zero i = 0; + /* fallthrough */ case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_DOWN: // Non primary pointer up/down @@ -2044,7 +2054,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, // Since we may have an orientation set, we won't receive onConfigurationChanged events. // We thus should check here. - int newOrientation = SDLActivity.SDL_ORIENTATION_UNKNOWN; + int newOrientation; float x, y; switch (mDisplay.getRotation()) { @@ -2063,6 +2073,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, y = -event.values[1]; newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED; break; + case Surface.ROTATION_0: default: x = event.values[0]; y = event.values[1]; @@ -2109,8 +2120,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, // Change our action value to what SDL's code expects. if (action == MotionEvent.ACTION_BUTTON_PRESS) { action = MotionEvent.ACTION_DOWN; - } - else if (action == MotionEvent.ACTION_BUTTON_RELEASE) { + } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ action = MotionEvent.ACTION_UP; } @@ -2275,45 +2285,38 @@ class SDLInputConnection extends BaseInputConnection { } } -interface SDLClipboardHandler { - - public boolean clipboardHasText(); - public String clipboardGetText(); - public void clipboardSetText(String string); - -} - - -class SDLClipboardHandler_API11 implements - SDLClipboardHandler, - android.content.ClipboardManager.OnPrimaryClipChangedListener { +class SDLClipboardHandler implements + ClipboardManager.OnPrimaryClipChangedListener { - protected android.content.ClipboardManager mClipMgr; + protected ClipboardManager mClipMgr; - SDLClipboardHandler_API11() { - mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + SDLClipboardHandler() { + mClipMgr = (ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); mClipMgr.addPrimaryClipChangedListener(this); } - @Override public boolean clipboardHasText() { - return mClipMgr.hasText(); + return mClipMgr.hasPrimaryClip(); } - @Override public String clipboardGetText() { - CharSequence text; - text = mClipMgr.getText(); - if (text != null) { - return text.toString(); + ClipData clip = mClipMgr.getPrimaryClip(); + if (clip != null) { + ClipData.Item item = clip.getItemAt(0); + if (item != null) { + CharSequence text = item.getText(); + if (text != null) { + return text.toString(); + } + } } return null; } - @Override public void clipboardSetText(String string) { mClipMgr.removePrimaryClipChangedListener(this); - mClipMgr.setText(string); + ClipData clip = ClipData.newPlainText(null, string); + mClipMgr.setPrimaryClip(clip); mClipMgr.addPrimaryClipChangedListener(this); } @@ -2321,6 +2324,5 @@ class SDLClipboardHandler_API11 implements public void onPrimaryClipChanged() { SDLActivity.onNativeClipboardChanged(); } - } diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java b/build-projects/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java index 0714419c..2bfc7186 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -1,6 +1,10 @@ package org.libsdl.app; -import android.media.*; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.media.AudioTrack; +import android.media.MediaRecorder; import android.os.Build; import android.util.Log; @@ -43,6 +47,10 @@ public class SDLAudioManager if (desiredChannels > 2) { desiredChannels = 2; } + } + + /* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */ + if (Build.VERSION.SDK_INT < 22) { if (sampleRate < 8000) { sampleRate = 8000; } else if (sampleRate > 48000) { @@ -199,7 +207,6 @@ public class SDLAudioManager results[0] = mAudioRecord.getSampleRate(); results[1] = mAudioRecord.getAudioFormat(); results[2] = mAudioRecord.getChannelCount(); - results[3] = desiredFrames; } else { if (mAudioTrack == null) { @@ -223,8 +230,8 @@ public class SDLAudioManager results[0] = mAudioTrack.getSampleRate(); results[1] = mAudioTrack.getAudioFormat(); results[2] = mAudioTrack.getChannelCount(); - results[3] = desiredFrames; } + results[3] = desiredFrames; Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz"); diff --git a/build-projects/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/build-projects/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java index a81e97be..05e0f0ca 100644 --- a/build-projects/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/build-projects/android/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -6,9 +6,14 @@ import java.util.Comparator; import java.util.List; import android.content.Context; -import android.os.*; -import android.view.*; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.util.Log; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; public class SDLControllerManager @@ -98,7 +103,7 @@ public class SDLControllerManager int sources = device.getSources(); /* This is called for every button press, so let's not spam the logs */ - /** + /* if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { Log.v(TAG, "Input device " + device.getName() + " has class joystick."); } @@ -108,7 +113,7 @@ public class SDLControllerManager if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { Log.v(TAG, "Input device " + device.getName() + " is a gamepad."); } - **/ + */ return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 || ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || @@ -167,7 +172,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { } } - private ArrayList mJoysticks; + private final ArrayList mJoysticks; public SDLJoystickHandler_API16() { @@ -177,13 +182,14 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { @Override public void pollInputDevices() { int[] deviceIds = InputDevice.getDeviceIds(); - for(int i=0; i < deviceIds.length; ++i) { - SDLJoystick joystick = getJoystick(deviceIds[i]); - if (joystick == null) { - joystick = new SDLJoystick(); - InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); - if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) { - joystick.device_id = deviceIds[i]; + + for (int device_id : deviceIds) { + if (SDLControllerManager.isDeviceSDLJoystick(device_id)) { + SDLJoystick joystick = getJoystick(device_id); + if (joystick == null) { + InputDevice joystickDevice = InputDevice.getDevice(device_id); + joystick = new SDLJoystick(); + joystick.device_id = device_id; joystick.name = joystickDevice.getName(); joystick.desc = getJoystickDescriptor(joystickDevice); joystick.axes = new ArrayList(); @@ -191,53 +197,57 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { List ranges = joystickDevice.getMotionRanges(); Collections.sort(ranges, new RangeComparator()); - for (InputDevice.MotionRange range : ranges ) { + for (InputDevice.MotionRange range : ranges) { if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || - range.getAxis() == MotionEvent.AXIS_HAT_Y) { + if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { joystick.hats.add(range); - } - else { + } else { joystick.axes.add(range); } } } mJoysticks.add(joystick); - SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, getVendorId(joystickDevice), getProductId(joystickDevice), false, getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0); + SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, + getVendorId(joystickDevice), getProductId(joystickDevice), false, + getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0); } } } /* Check removed devices */ - ArrayList removedDevices = new ArrayList(); - for(int i=0; i < mJoysticks.size(); i++) { - int device_id = mJoysticks.get(i).device_id; - int j; - for (j=0; j < deviceIds.length; j++) { - if (device_id == deviceIds[j]) break; + ArrayList removedDevices = null; + for (SDLJoystick joystick : mJoysticks) { + int device_id = joystick.device_id; + int i; + for (i = 0; i < deviceIds.length; i++) { + if (device_id == deviceIds[i]) break; } - if (j == deviceIds.length) { - removedDevices.add(Integer.valueOf(device_id)); + if (i == deviceIds.length) { + if (removedDevices == null) { + removedDevices = new ArrayList(); + } + removedDevices.add(device_id); } } - for(int i=0; i < removedDevices.size(); i++) { - int device_id = removedDevices.get(i).intValue(); - SDLControllerManager.nativeRemoveJoystick(device_id); - for (int j=0; j < mJoysticks.size(); j++) { - if (mJoysticks.get(j).device_id == device_id) { - mJoysticks.remove(j); - break; + if (removedDevices != null) { + for (int device_id : removedDevices) { + SDLControllerManager.nativeRemoveJoystick(device_id); + for (int i = 0; i < mJoysticks.size(); i++) { + if (mJoysticks.get(i).device_id == device_id) { + mJoysticks.remove(i); + break; + } } } } } protected SDLJoystick getJoystick(int device_id) { - for(int i=0; i < mJoysticks.size(); i++) { - if (mJoysticks.get(i).device_id == device_id) { - return mJoysticks.get(i); + for (SDLJoystick joystick : mJoysticks) { + if (joystick.device_id == device_id) { + return joystick; } } return null; @@ -248,25 +258,21 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { int actionPointerIndex = event.getActionIndex(); int action = event.getActionMasked(); - switch(action) { - case MotionEvent.ACTION_MOVE: - SDLJoystick joystick = getJoystick(event.getDeviceId()); - if ( joystick != null ) { - for (int i = 0; i < joystick.axes.size(); i++) { - InputDevice.MotionRange range = joystick.axes.get(i); - /* Normalize the value to -1...1 */ - float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; - SDLControllerManager.onNativeJoy(joystick.device_id, i, value ); - } - for (int i = 0; i < joystick.hats.size(); i+=2) { - int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); - int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); - SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY ); - } + if (action == MotionEvent.ACTION_MOVE) { + SDLJoystick joystick = getJoystick(event.getDeviceId()); + if (joystick != null) { + for (int i = 0; i < joystick.axes.size(); i++) { + InputDevice.MotionRange range = joystick.axes.get(i); + /* Normalize the value to -1...1 */ + float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f; + SDLControllerManager.onNativeJoy(joystick.device_id, i, value); + } + for (int i = 0; i < joystick.hats.size() / 2; i++) { + int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex)); + int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex)); + SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY); } - break; - default: - break; + } } } return true; @@ -432,13 +438,13 @@ class SDLHapticHandler_API26 extends SDLHapticHandler { class SDLHapticHandler { - class SDLHaptic { + static class SDLHaptic { public int device_id; public String name; public Vibrator vib; } - private ArrayList mHaptics; + private final ArrayList mHaptics; public SDLHapticHandler() { mHaptics = new ArrayList(); @@ -504,37 +510,41 @@ class SDLHapticHandler { } /* Check removed devices */ - ArrayList removedDevices = new ArrayList(); - for(int i=0; i < mHaptics.size(); i++) { - int device_id = mHaptics.get(i).device_id; - int j; - for (j=0; j < deviceIds.length; j++) { - if (device_id == deviceIds[j]) break; + ArrayList removedDevices = null; + for (SDLHaptic haptic : mHaptics) { + int device_id = haptic.device_id; + int i; + for (i = 0; i < deviceIds.length; i++) { + if (device_id == deviceIds[i]) break; } - if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) { - // don't remove the vibrator if it is still present - } else if (j == deviceIds.length) { - removedDevices.add(device_id); - } + if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) { + if (i == deviceIds.length) { + if (removedDevices == null) { + removedDevices = new ArrayList(); + } + removedDevices.add(device_id); + } + } // else: don't remove the vibrator if it is still present } - for(int i=0; i < removedDevices.size(); i++) { - int device_id = removedDevices.get(i); - SDLControllerManager.nativeRemoveHaptic(device_id); - for (int j=0; j < mHaptics.size(); j++) { - if (mHaptics.get(j).device_id == device_id) { - mHaptics.remove(j); - break; + if (removedDevices != null) { + for (int device_id : removedDevices) { + SDLControllerManager.nativeRemoveHaptic(device_id); + for (int i = 0; i < mHaptics.size(); i++) { + if (mHaptics.get(i).device_id == device_id) { + mHaptics.remove(i); + break; + } } } } } protected SDLHaptic getHaptic(int device_id) { - for(int i=0; i < mHaptics.size(); i++) { - if (mHaptics.get(i).device_id == device_id) { - return mHaptics.get(i); + for (SDLHaptic haptic : mHaptics) { + if (haptic.device_id == device_id) { + return haptic; } } return null; @@ -655,8 +665,7 @@ class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 { public float getEventX(MotionEvent event) { if (mRelativeModeEnabled) { return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); - } - else { + } else { return event.getX(0); } } @@ -665,14 +674,12 @@ class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 { public float getEventY(MotionEvent event) { if (mRelativeModeEnabled) { return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); - } - else { + } else { return event.getY(0); } } } - class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { // Generic Motion (mouse hover, joystick...) events go here private boolean mRelativeModeEnabled; @@ -753,15 +760,12 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) { if (enabled) { SDLActivity.getContentView().requestPointerCapture(); - } - else { + } else { SDLActivity.getContentView().releasePointerCapture(); } mRelativeModeEnabled = enabled; return true; - } - else - { + } else { return false; } } diff --git a/build-projects/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/build-projects/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 9ed04d0f..e69de29b 100644 Binary files a/build-projects/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/build-projects/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/build-projects/android/app/src/main/res/mipmap-ldpi/ic_launcher.png b/build-projects/android/app/src/main/res/mipmap-ldpi/ic_launcher.png index bf733ec5..e69de29b 100644 Binary files a/build-projects/android/app/src/main/res/mipmap-ldpi/ic_launcher.png and b/build-projects/android/app/src/main/res/mipmap-ldpi/ic_launcher.png differ diff --git a/build-projects/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/build-projects/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 560306ac..e69de29b 100644 Binary files a/build-projects/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/build-projects/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/build-projects/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/build-projects/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 4c31ddeb..e69de29b 100644 Binary files a/build-projects/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/build-projects/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/build-projects/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/build-projects/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 132d3c13..e69de29b 100644 Binary files a/build-projects/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/build-projects/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/build-projects/android/build-scripts/create_sdl.sh b/build-projects/android/build-scripts/create_sdl.sh index 901d150d..5ec679e1 100755 --- a/build-projects/android/build-scripts/create_sdl.sh +++ b/build-projects/android/build-scripts/create_sdl.sh @@ -5,7 +5,8 @@ JNI_DIR="app/jni" ANDROID_MK_SDL_IMAGE="$JNI_DIR/SDL2_image/Android.mk" ANDROID_MK_SDL_MIXER="$JNI_DIR/SDL2_mixer/Android.mk" -SDL_BASE_URL="https://www.libsdl.org" +SDL_BASE_URL_ORIGINAL="https://www.libsdl.org" +SDL_BASE_URL_FALLBACK="https://www.artsoft.org" SDL_VERSIONS=`cat SDL_VERSIONS` for i in $SDL_VERSIONS; do @@ -22,13 +23,21 @@ for i in $SDL_VERSIONS; do SDL_RELEASE_DIR="projects/$SDL_SUBURL/release" fi - SDL_URL="$SDL_BASE_URL/$SDL_RELEASE_DIR/$i.tar.gz" + SDL_URL="$SDL_BASE_URL_ORIGINAL/$SDL_RELEASE_DIR/$i.tar.gz" - wget -O - "$SDL_URL" | (cd "$JNI_DIR" && tar xzf -) + wget --timeout=10 -O - "$SDL_URL" | (cd "$JNI_DIR" && tar xzf -) if [ "$?" != "0" ]; then - echo "ERROR: Installing '$i' failed!" - exit 10 + echo "ERROR: Installing '$i' from main site failed -- trying fallback!" + + SDL_URL="$SDL_BASE_URL_FALLBACK/RELEASES/sdl/$i.tar.gz" + + wget --timeout=10 -O - "$SDL_URL" | (cd "$JNI_DIR" && tar xzf -) + + if [ "$?" != "0" ]; then + echo "ERROR: Installing '$i' from fallback site failed!" + exit 10 + fi fi mv "$JNI_DIR/$i" "$JNI_DIR/$SDL_SUBDIR" diff --git a/build-projects/android/build.gradle b/build-projects/android/build.gradle index f6f90b25..6f629c8a 100644 --- a/build-projects/android/build.gradle +++ b/build-projects/android/build.gradle @@ -2,11 +2,11 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.0' + classpath 'com.android.tools.build:gradle:7.0.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() google() } } diff --git a/build-projects/android/gradle/wrapper/gradle-wrapper.properties b/build-projects/android/gradle/wrapper/gradle-wrapper.properties index f9b3be2f..67458931 100644 --- a/build-projects/android/gradle/wrapper/gradle-wrapper.properties +++ b/build-projects/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/build-projects/emscripten/favicon-16x16.png b/build-projects/emscripten/favicon-16x16.png new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/emscripten/favicon-32x32.png b/build-projects/emscripten/favicon-32x32.png new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/emscripten/index.html b/build-projects/emscripten/index.html index cdf397f5..a4d0d975 100644 --- a/build-projects/emscripten/index.html +++ b/build-projects/emscripten/index.html @@ -3,16 +3,47 @@ Loading Rocks'n'Diamonds + + + - + +
+ +
+Loading Rocks'n'Diamonds ... +
+ + diff --git a/build-projects/emscripten/loading.svg b/build-projects/emscripten/loading.svg new file mode 100644 index 00000000..5ca45caa --- /dev/null +++ b/build-projects/emscripten/loading.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build-projects/mac/Rocks'n'Diamonds.app/Contents/Frameworks/.gitkeep b/build-projects/mac/Rocks'n'Diamonds.app/Contents/Frameworks/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/mac/Rocks'n'Diamonds.app/Contents/Info.plist.template b/build-projects/mac/Rocks'n'Diamonds.app/Contents/Info.plist.template new file mode 100644 index 00000000..9f98db35 --- /dev/null +++ b/build-projects/mac/Rocks'n'Diamonds.app/Contents/Info.plist.template @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + rocksndiamonds + CFBundleIconFile + rocksndiamonds.icns + CFBundleIdentifier + org.artsoft.rocksndiamonds + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Rocks'n'Diamonds + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + __VERSION__ + NSHumanReadableCopyright + Copyright (c) 1995-__YEAR__ by Artsoft Entertainment + + diff --git a/build-projects/mac/Rocks'n'Diamonds.app/Contents/MacOS/.gitkeep b/build-projects/mac/Rocks'n'Diamonds.app/Contents/MacOS/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/mac/Rocks'n'Diamonds.app/Contents/PkgInfo b/build-projects/mac/Rocks'n'Diamonds.app/Contents/PkgInfo new file mode 100644 index 00000000..bd04210f --- /dev/null +++ b/build-projects/mac/Rocks'n'Diamonds.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/build-projects/mac/Rocks'n'Diamonds.app/Contents/Resources/rocksndiamonds.icns b/build-projects/mac/Rocks'n'Diamonds.app/Contents/Resources/rocksndiamonds.icns new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/windows/icons/icon-128x128.png b/build-projects/windows/icons/icon-128x128.png new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/windows/icons/icon-16x16.png b/build-projects/windows/icons/icon-16x16.png new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/windows/icons/icon-32x32.png b/build-projects/windows/icons/icon-32x32.png new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/windows/icons/icon-48x48.png b/build-projects/windows/icons/icon-48x48.png new file mode 100644 index 00000000..e69de29b diff --git a/build-projects/windows/rocksndiamonds.url b/build-projects/windows/rocksndiamonds.url new file mode 100644 index 00000000..6eb96358 --- /dev/null +++ b/build-projects/windows/rocksndiamonds.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://www.artsoft.org/rocksndiamonds/ diff --git a/build-projects/windows/template.iss b/build-projects/windows/template.iss new file mode 100644 index 00000000..5de94766 --- /dev/null +++ b/build-projects/windows/template.iss @@ -0,0 +1,53 @@ +; ============================================================================= +; template.iss +; ----------------------------------------------------------------------------- +; configuration template for Inno Setup installation project +; +; 2020-06-30 info@artsoft.org +; ============================================================================= + +[Setup] +AppName=_PRG_NAME_ +AppVerName=_PRG_NAME_ _PRG_VERSION_ +AppPublisher=Artsoft Entertainment +AppPublisherURL=https://www.artsoft.org/ +AppSupportURL=https://www.artsoft.org/_PRG_BASENAME_/ +AppUpdatesURL=https://www.artsoft.org/_PRG_BASENAME_/ + +ArchitecturesInstallIn64BitMode=_PRG_ARCH_ +ArchitecturesAllowed=_PRG_ARCH_ + +DefaultDirName={pf}\_PRG_NAME_ +DefaultGroupName=_PRG_NAME_ +;LicenseFile="_PRG_DIR_\COPYING.txt" +;InfoBeforeFile="_PRG_DIR_\INSTALL.txt" +;InfoAfterFile="_PRG_DIR_\README.txt" +UninstallDisplayIcon={app}\_PRG_EXE_ +Compression=lzma +SolidCompression=yes + +OutputBaseFilename=_SETUP_EXE_ +OutputDir=. + +[Files] +Source: "_PRG_DIR_\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs ignoreversion + +[Tasks] +Name: "desktopicon"; Description: "Create a &Desktop icon"; GroupDescription: "Additional icons:" +Name: "quicklaunchicon"; Description: "Create a &Quick Launch icon"; GroupDescription: "Additional icons:" + +[Icons] +Name: "{group}\_PRG_NAME_"; Filename: "{app}\_PRG_EXE_" +Name: "{group}\_PRG_NAME_ on the Web"; Filename: "{app}\_PRG_BASENAME_.url" +Name: "{userdesktop}\_PRG_NAME_"; Filename: "{app}\_PRG_EXE_"; Tasks: desktopicon +Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\_PRG_NAME_"; Filename: "{app}\_PRG_EXE_"; Tasks: quicklaunchicon + +; This dynamically generates a Windows internet shortcut file. Unfortunately, +; this file is not removed when the package is uninstalled, leaving an empty +; program directory with just that internet shortcut file. Using a static file +; does not cause this problem. +;[INI] +;Filename: "{app}\_PRG_BASENAME_.url"; Section: "InternetShortcut"; Key: "URL"; String: "https://www.artsoft.org/_PRG_BASENAME_/" + +[Run] +Filename: "{app}\_PRG_EXE_"; Description: "Launch _PRG_NAME_"; Flags: nowait postinstall skipifsilent diff --git a/build-scripts/create_element_defs.pl b/build-scripts/create_element_defs.pl index 0966816a..b75d86ac 100755 --- a/build-scripts/create_element_defs.pl +++ b/build-scripts/create_element_defs.pl @@ -42,6 +42,8 @@ my $filename_conf_cus_c = 'conf_cus.c'; my $filename_conf_cus_h = 'conf_cus.h'; my $filename_conf_grp_c = 'conf_grp.c'; my $filename_conf_grp_h = 'conf_grp.h'; +my $filename_conf_emp_c = 'conf_emp.c'; +my $filename_conf_emp_h = 'conf_emp.h'; my $filename_conf_e2g_c = 'conf_e2g.c'; my $filename_conf_esg_c = 'conf_esg.c'; my $filename_conf_e2s_c = 'conf_e2s.c'; @@ -61,6 +63,8 @@ my $text_cus_c = 'values for graphics configuration (custom elements)'; my $text_cus_h = 'values for elements configuration (custom elements)'; my $text_grp_c = 'values for graphics configuration (group elements)'; my $text_grp_h = 'values for elements configuration (group elements)'; +my $text_emp_c = 'values for graphics configuration (empty elements)'; +my $text_emp_h = 'values for elements configuration (empty elements)'; my $text_e2g_c = 'values for element/graphics mapping configuration (normal)'; my $text_esg_c = 'values for element/graphics mapping configuration (special)'; my $text_e2s_c = 'values for element/sounds mapping configuration'; @@ -72,6 +76,7 @@ my $text_act_c = 'values for active states of elements and fonts'; my $num_custom_elements = 256; my $num_group_elements = 32; +my $num_empty_elements = 16; my $char_skip = '---[SKIP]---'; @@ -393,6 +398,28 @@ sub print_graphics_list } } + if (/^\#include "conf_emp.c"/) # dump list of empty elements + { + for (my $nr = 0; $nr < $num_empty_elements; $nr++) + { + my $line = sprintf("#define IMG_EMPTY_SPACE_%d", $nr + 1); + + my $tabs = get_tabs($line, $max_num_tabs); + + print "$line$tabs$i\n"; + + $i++; + + $line = sprintf("#define IMG_EMPTY_SPACE_%d_EDITOR", $nr + 1); + + $tabs = get_tabs($line, $max_num_tabs); + + print "$line$tabs$i\n"; + + $i++; + } + } + if (!contains_image_file($_)) # skip all lines without image file { next; @@ -473,6 +500,11 @@ sub print_sounds_list $sound =~ s/^/CLASS_/; # add class identifier } + # dirty hack for making "ABC[DEF]" work as a "special" suffix + $sound =~ s/([^_])\[/$1_/; + $sound =~ s/\[//; + $sound =~ s/\]//; + $sound = "SND_$sound"; my $define_text = "#define $sound"; @@ -531,6 +563,11 @@ sub print_music_list my $music = $_; + # dirty hack for making "ABC[DEF]" work as a "special" suffix + $music =~ s/([^_])\[/$1_/; + $music =~ s/\[//; + $music =~ s/\]//; + $music = "MUS_$music"; my $define_text = "#define $music"; @@ -727,6 +764,24 @@ sub print_group_elements_list print_file_footer($filename_conf_grp_c); } +sub print_empty_elements_list +{ + print_file_header($filename_conf_emp_h, $text_emp_h); + + for (my $i = 0; $i < $num_empty_elements; $i++) + { + my $left = sprintf("#define EL_EMPTY_SPACE_%d", $i + 1); + + my $tabs_left = get_tabs($left, 5); + + my $right = "(EL_EMPTY_SPACE_START + $i)"; + + print "$left$tabs_left$right\n"; + } + + print_file_footer($filename_conf_emp_c); +} + sub print_custom_graphics_list { my @extensions1 = @@ -889,6 +944,89 @@ sub print_group_graphics_list print_file_footer($filename_conf_grp_c); } +sub print_empty_graphics_list +{ + my @extensions1 = + ( + '', + '.xpos', + '.ypos', + '.frames', + ); + my @extensions2 = + ( + '', + '.xpos', + '.ypos', + ); + + my $num_non_empty_elements = $num_custom_elements + $num_group_elements; + + print_file_header($filename_conf_emp_c, $text_emp_c); + + for (my $i = 0; $i < $num_empty_elements; $i++) + { + foreach my $ext (@extensions1) + { + my $left = sprintf(" \{ \"empty_space_%d$ext\",", $i + 1); + + my $tabs_left = get_tabs($left, 6); + + # my $right = ($ext eq '' ? 'RocksDC.png' : + my $right = ($ext eq '' ? 'RocksCE.png' : + $ext eq '.frames' ? '1' : '0'); + + if ($ext eq '.xpos') + { + # $right = 4; + $right = int($i % 16); + } + elsif ($ext eq '.ypos') + { + # $right = 15; + $right = int($i / 16) + int($num_non_empty_elements / 16); + } + + $right = "\"$right\""; + + my $tabs_right = get_tabs($right, 3); + + print "$left$tabs_left$right$tabs_right},\n"; + } + + foreach my $ext (@extensions2) + { + my $left = sprintf(" \{ \"empty_space_%d.EDITOR$ext\",", $i + 1); + + my $tabs_left = get_tabs($left, 6); + + # my $right = ($ext eq '' ? 'RocksDC.png' : '0'); + my $right = ($ext eq '' ? 'RocksCE.png' : '0'); + + if ($ext eq '.xpos') + { + # $right = 14; + $right = int($i % 16) + 16; + } + elsif ($ext eq '.ypos') + { + # $right = 15; + $right = int($i / 16) + int($num_non_empty_elements / 16); + } + + $right = "\"$right\""; + + my $tabs_right = get_tabs($right, 3); + + print "$left$tabs_left$right$tabs_right},\n"; + } + + print "\n"; + } + + print_file_footer($filename_conf_emp_c); +} + sub get_known_element_definitions_ALTERNATIVE { my %known_element = (); @@ -1644,6 +1782,15 @@ sub print_element_to_graphic_list print_element_to_graphic_entry($element, '-1', '-1', '-1', $graphic); } + # dump list of empty elements + for (my $i = 0; $i < $num_empty_elements; $i++) + { + my $element = sprintf("EL_EMPTY_SPACE_%d", $i + 1); + my $graphic = sprintf("IMG_EMPTY_SPACE_%d", $i + 1); + + print_element_to_graphic_entry($element, '-1', '-1', '-1', $graphic); + } + print_element_to_graphic_entry('-1', '-1', '-1', '-1', '-1'); print "};\n"; @@ -1815,6 +1962,17 @@ sub print_element_to_special_graphic_list $graphic); } + # dump list of empty element editor graphics + for (my $i = 0; $i < $num_empty_elements; $i++) + { + my $element = sprintf("EL_EMPTY_SPACE_%d", $i + 1); + my $graphic = sprintf("IMG_EMPTY_SPACE_%d_EDITOR", $i + 1); + + print_element_to_special_graphic_entry($element, + 'GFX_SPECIAL_ARG_EDITOR', + $graphic); + } + # dump other special editor graphics foreach my $token (@elements_with_editor_graphic) { @@ -2361,6 +2519,7 @@ sub print_image_config_vars $var =~ s/^main\./menu.main./; $var =~ s/^setup\./menu.setup./; + $var =~ s/^scores\./menu.scores./; $var =~ s/^\[player\]\./game.player_/; $var =~ s/^\[title_initial\]/title_initial_default/; $var =~ s/^\[title\]/title_default/; @@ -2557,6 +2716,8 @@ sub main print "- '$filename_conf_cus_h'\n"; print "- '$filename_conf_grp_c'\n"; print "- '$filename_conf_grp_h'\n"; + print "- '$filename_conf_emp_c'\n"; + print "- '$filename_conf_emp_h'\n"; print "- '$filename_conf_e2g_c'\n"; print "- '$filename_conf_esg_c'\n"; print "- '$filename_conf_fnt_c'\n"; @@ -2604,6 +2765,14 @@ sub main { print_group_elements_list(); } + elsif ($ARGV[0] eq $filename_conf_emp_c) + { + print_empty_graphics_list(); + } + elsif ($ARGV[0] eq $filename_conf_emp_h) + { + print_empty_elements_list(); + } elsif ($ARGV[0] eq $filename_conf_e2g_c) { print_element_to_graphic_list(); diff --git a/docs/credits/credits_1.txt b/docs/credits/credits_1.txt new file mode 100644 index 00000000..52c26539 --- /dev/null +++ b/docs/credits/credits_1.txt @@ -0,0 +1,23 @@ +# .font: font.text_2 +Special thanks to + +# .font: font.text_3 +Peter Liepa + +# .font: font.text_2 +for creating + +# .font: font.text_3 +"Boulder Dash" + +# .font: font.text_2 +in the year + +# .font: font.text_3 +1984 + +# .font: font.text_2 +published by + +# .font: font.text_3 +First Star Software diff --git a/docs/credits/credits_2.txt b/docs/credits/credits_2.txt new file mode 100644 index 00000000..801241ab --- /dev/null +++ b/docs/credits/credits_2.txt @@ -0,0 +1,23 @@ +# .font: font.text_2 +Special thanks to + +# .font: font.text_3 +Klaus Heinz & Volker Wertich + +# .font: font.text_2 +for creating + +# .font: font.text_3 +"Emerald Mine" + +# .font: font.text_2 +in the year + +# .font: font.text_3 +1987 + +# .font: font.text_2 +published by + +# .font: font.text_3 +Kingsoft diff --git a/docs/credits/credits_3.txt b/docs/credits/credits_3.txt new file mode 100644 index 00000000..53b61271 --- /dev/null +++ b/docs/credits/credits_3.txt @@ -0,0 +1,23 @@ +# .font: font.text_2 +Special thanks to + +# .font: font.text_3 +Michael Stopp & Philip Jespersen + +# .font: font.text_2 +for creating + +# .font: font.text_3 +"Supaplex" + +# .font: font.text_2 +in the year + +# .font: font.text_3 +1991 + +# .font: font.text_2 +published by + +# .font: font.text_3 +Digital Integration diff --git a/docs/credits/credits_4.txt b/docs/credits/credits_4.txt new file mode 100644 index 00000000..33ca68d5 --- /dev/null +++ b/docs/credits/credits_4.txt @@ -0,0 +1,23 @@ +# .font: font.text_2 +Special thanks to + +# .font: font.text_3 +Hiroyuki Imabayashi + +# .font: font.text_2 +for creating + +# .font: font.text_3 +"Sokoban" + +# .font: font.text_2 +in the year + +# .font: font.text_3 +1982 + +# .font: font.text_2 +published by + +# .font: font.text_3 +Thinking Rabbit diff --git a/docs/credits/credits_5.txt b/docs/credits/credits_5.txt new file mode 100644 index 00000000..8fd61c3d --- /dev/null +++ b/docs/credits/credits_5.txt @@ -0,0 +1,15 @@ +# .font: font.text_2 +Special thanks to + +# .font: font.text_3 +Alan Bond + +# .font: font.text_2 +and + +# .font: font.text_3 +Jürgen Bonhagen + +# .font: font.text_2 +for the continuous creation +of outstanding level sets diff --git a/docs/credits/credits_6.txt b/docs/credits/credits_6.txt new file mode 100644 index 00000000..7c366cb2 --- /dev/null +++ b/docs/credits/credits_6.txt @@ -0,0 +1,25 @@ +# .font: font.text_2 +Thanks to + +# .font: font.text_3 +Peter Elzner + +# .font: font.text_2 +for ideas and inspiration by + +# .font: font.text_3 +"Diamond Caves" + + + +# .font: font.text_2 +Thanks to + +# .font: font.text_3 +Steffest + +# .font: font.text_2 +for ideas and inspiration by + +# .font: font.text_3 +"DX-Boulderdash" diff --git a/docs/credits/credits_7.txt b/docs/credits/credits_7.txt new file mode 100644 index 00000000..27d4e658 --- /dev/null +++ b/docs/credits/credits_7.txt @@ -0,0 +1,20 @@ +# .font: font.text_2 +Thanks to + +# .font: font.text_3 +David Tritscher + +# .font: font.text_2 +for the code base used for the +native Emerald Mine engine + + + +# .font: font.text_2 +Thanks to + +# .font: font.text_3 +Guido Schulz + +# .font: font.text_2 +for the initial DOS/Windows port diff --git a/docs/credits/credits_8.txt b/docs/credits/credits_8.txt new file mode 100644 index 00000000..8482f066 --- /dev/null +++ b/docs/credits/credits_8.txt @@ -0,0 +1,25 @@ +# .font: font.text_2 +Thanks to + +# .font: font.text_3 +Thomas Andrae + +# .font: font.text_2 +and + +# .font: font.text_3 +Karl Hörnell + +# .font: font.text_2 +for additional toon animations + + + +# .font: font.text_2 +Thanks to + +# .font: font.text_3 +Majid Katzer + +# .font: font.text_2 +for additional sounds and music diff --git a/docs/credits/credits_9.txt b/docs/credits/credits_9.txt new file mode 100644 index 00000000..d14c2fcb --- /dev/null +++ b/docs/credits/credits_9.txt @@ -0,0 +1,9 @@ +# .font: font.text_2 +And not to forget: + +Many thanks to + +# .font: font.text_3 +All those who contributed +levels to this game +since 1995 diff --git a/docs/elements/bd_magic_wall.txt b/docs/elements/bd_magic_wall.txt index 3800be9e..475d1103 100644 --- a/docs/elements/bd_magic_wall.txt +++ b/docs/elements/bd_magic_wall.txt @@ -1,7 +1,8 @@ -This is a (BD style) magic wall. It gets activated for about 10 seconds by -rocks or gems that fall on it. Rocks that fall through it become BD style -diamonds, and gems that fall through it become BD style rocks. After it has -stopped running, it cannot be activated again. +This is a BD style magic wall. It gets activated for a limited duration by +rocks or gems that fall on it. While activated, they can fall through it, and +rocks turn into BD style diamonds, and gems turn into BD style rocks. +After the magic wall has stopped running, it cannot be activated again. -All BD magic walls run on the same timer; however, regular magic walls run -on a seperate timer. +The duration in seconds for which magic walls are active is configurable. +A duration of zero will let the wall run forever. +All magic walls share the same timer. diff --git a/docs/elements/dc_magic_wall.txt b/docs/elements/dc_magic_wall.txt index 084d69df..eafe7d4a 100644 --- a/docs/elements/dc_magic_wall.txt +++ b/docs/elements/dc_magic_wall.txt @@ -1,8 +1,9 @@ -This is a (DC style) magic wall. It gets activated for a limited -time by rocks or gems that fall on it. Objects falling though -it will be changed to other objects. After it has stopped running, -it cannot be activated again. +This is a DC style magic wall. It gets activated for a limited duration by +rocks or gems that fall on it. While activated, they can fall through it, and +rocks turn into emeralds, emeralds turn into diamonds, diamonds turn into +rocks, pearls turn into bombs, and crystals do not change. +After the magic wall has stopped running, it cannot be activated again. -The duration is expressed in seconds. A duration of zero will let the wall -run forever. All regular magic walls run together; however, BD style magic -walls have a separate counter. +The duration in seconds for which magic walls are active is configurable. +A duration of zero will let the wall run forever. +All magic walls share the same timer. diff --git a/docs/elements/df_mirror_fixed.txt b/docs/elements/df_mirror_fixed.txt new file mode 100644 index 00000000..2d1c31a5 --- /dev/null +++ b/docs/elements/df_mirror_fixed.txt @@ -0,0 +1 @@ +This fixed mirror reflects the laser beam into one direction only. diff --git a/docs/elements/df_slope.txt b/docs/elements/df_slope.txt new file mode 100644 index 00000000..ac49f185 --- /dev/null +++ b/docs/elements/df_slope.txt @@ -0,0 +1 @@ +Steel slopes reflect the laser beam similar to steel walls. diff --git a/docs/elements/magic_wall.txt b/docs/elements/magic_wall.txt index c17b45de..3bd49118 100644 --- a/docs/elements/magic_wall.txt +++ b/docs/elements/magic_wall.txt @@ -1,8 +1,9 @@ -This is a (EM style) magic wall. It gets activated for a limited -time by rocks or gems that fall on it. Objects falling though -it will be changed to other objects. After it has stopped running, -it cannot be activated again. +This is an EM style magic wall. It gets activated for a limited duration by +rocks or gems that fall on it. While activated, they can fall through it, and +rocks turn into emeralds, emeralds turn into diamonds, and diamonds turn into +rocks. +After the magic wall has stopped running, it cannot be activated again. -The duration is expressed in seconds. A duration of zero will let the wall -run forever. All regular magic walls run together; however, BD style magic -walls have a separate counter. +The duration in seconds for which magic walls are active is configurable. +A duration of zero will let the wall run forever. +All magic walls share the same timer. diff --git a/docs/elements/mm_envelope.txt b/docs/elements/mm_envelope.txt new file mode 100644 index 00000000..041804fc --- /dev/null +++ b/docs/elements/mm_envelope.txt @@ -0,0 +1,2 @@ +Envelopes can be configured to contain a text message for the player. +All envelopes of the same color contain the same message. diff --git a/docs/elements/mm_exit.txt b/docs/elements/mm_exit.txt index ef11ee09..0f362b4b 100644 --- a/docs/elements/mm_exit.txt +++ b/docs/elements/mm_exit.txt @@ -1,2 +1,2 @@ -This magic exit door opens once all magic kettles have been collected. +This magic exit door opens once all magic cauldrons have been collected. Redirect the magic ray of light into the exit door to finish the game. diff --git a/docs/elements/mm_kettle.txt b/docs/elements/mm_kettle.txt index ee2ffa60..3f9b844a 100644 --- a/docs/elements/mm_kettle.txt +++ b/docs/elements/mm_kettle.txt @@ -1,2 +1,2 @@ -Magic kettles contain ingredients for Gregor MacDuffin's magic spells and must +Magic cauldrons contain ingredients for Gregor MacDuffin's magic spells and must all be collected before the magic exit door opens to finish the level. diff --git a/docs/elements/mm_mcduffin.txt b/docs/elements/mm_mcduffin.txt index 8c248dab..cb1cef29 100644 --- a/docs/elements/mm_mcduffin.txt +++ b/docs/elements/mm_mcduffin.txt @@ -1,3 +1,3 @@ This is our hero and magician Gregor McDuffin, who casts spells in the form -of magic rays of light to collect all magic kettles (containing magic spell +of magic rays of light to collect all magic cauldrons (containing magic spell ingredients). diff --git a/docs/elements/mm_pacman.txt b/docs/elements/mm_pacman.txt index 4d40566d..b6bf1044 100644 --- a/docs/elements/mm_pacman.txt +++ b/docs/elements/mm_pacman.txt @@ -1 +1 @@ -The pacmen move around, trying to eat all kettles and all amoeba walls. +The pacmen move around, trying to eat all cauldrons and all amoeba walls. diff --git a/docs/program/program_1.txt b/docs/program/program_1.txt new file mode 100644 index 00000000..396a5665 --- /dev/null +++ b/docs/program/program_1.txt @@ -0,0 +1,22 @@ +# .font: font.text_2 +This game is Freeware! +If you like it, send e-mail to: + +# .font: font.text_3 +info@artsoft.org + + + +# .font: font.text_2 +More information and levels: + +# .font: font.text_3 +https://www.artsoft.org/ + + + +# .font: font.text_2 +If you have created new levels, +send them to me to include them! + +:-) diff --git a/graphics/gfx_classic/RocksCE.png b/graphics/gfx_classic/RocksCE.png index 72bbb0a4..5a5f1812 100644 Binary files a/graphics/gfx_classic/RocksCE.png and b/graphics/gfx_classic/RocksCE.png differ diff --git a/graphics/gfx_classic/RocksCollect.png b/graphics/gfx_classic/RocksCollect.png new file mode 100644 index 00000000..710c556c Binary files /dev/null and b/graphics/gfx_classic/RocksCollect.png differ diff --git a/graphics/gfx_classic/RocksDC.png b/graphics/gfx_classic/RocksDC.png index dcc2e6ef..20fb45ee 100644 Binary files a/graphics/gfx_classic/RocksDC.png and b/graphics/gfx_classic/RocksDC.png differ diff --git a/graphics/gfx_classic/RocksDC2.png b/graphics/gfx_classic/RocksDC2.png index a33c2a48..f20cafa6 100644 Binary files a/graphics/gfx_classic/RocksDC2.png and b/graphics/gfx_classic/RocksDC2.png differ diff --git a/graphics/gfx_classic/RocksDF.png b/graphics/gfx_classic/RocksDF.png index e8ea18d2..55051220 100644 Binary files a/graphics/gfx_classic/RocksDF.png and b/graphics/gfx_classic/RocksDF.png differ diff --git a/graphics/gfx_classic/RocksDoor.png b/graphics/gfx_classic/RocksDoor.png index 3d045d6f..c0feaa6c 100644 Binary files a/graphics/gfx_classic/RocksDoor.png and b/graphics/gfx_classic/RocksDoor.png differ diff --git a/graphics/gfx_classic/RocksDoor2.png b/graphics/gfx_classic/RocksDoor2.png index 73efd72d..7d150be0 100644 Binary files a/graphics/gfx_classic/RocksDoor2.png and b/graphics/gfx_classic/RocksDoor2.png differ diff --git a/graphics/gfx_classic/RocksEMC.png b/graphics/gfx_classic/RocksEMC.png index 6406fcb1..a765204f 100644 Binary files a/graphics/gfx_classic/RocksEMC.png and b/graphics/gfx_classic/RocksEMC.png differ diff --git a/graphics/gfx_classic/RocksElements.png b/graphics/gfx_classic/RocksElements.png index 8b0d40fb..f6eb6080 100644 Binary files a/graphics/gfx_classic/RocksElements.png and b/graphics/gfx_classic/RocksElements.png differ diff --git a/graphics/gfx_classic/RocksIcon32x32.png b/graphics/gfx_classic/RocksIcon32x32.png deleted file mode 100644 index dcd51125..00000000 Binary files a/graphics/gfx_classic/RocksIcon32x32.png and /dev/null differ diff --git a/graphics/gfx_classic/RocksMM.png b/graphics/gfx_classic/RocksMM.png index b2b816ea..ac97f952 100644 Binary files a/graphics/gfx_classic/RocksMM.png and b/graphics/gfx_classic/RocksMM.png differ diff --git a/graphics/gfx_classic/RocksSP.png b/graphics/gfx_classic/RocksSP.png index 3ec70838..b9daca31 100644 Binary files a/graphics/gfx_classic/RocksSP.png and b/graphics/gfx_classic/RocksSP.png differ diff --git a/graphics/gfx_classic/RocksScreen.png b/graphics/gfx_classic/RocksScreen.png index c42cacc4..2a25840d 100644 Binary files a/graphics/gfx_classic/RocksScreen.png and b/graphics/gfx_classic/RocksScreen.png differ diff --git a/graphics/gfx_classic/RocksTouch.png b/graphics/gfx_classic/RocksTouch.png index 4cb63e9c..919207ce 100644 Binary files a/graphics/gfx_classic/RocksTouch.png and b/graphics/gfx_classic/RocksTouch.png differ diff --git a/graphics/gfx_classic/icons/icon.png b/graphics/gfx_classic/icons/icon.png new file mode 100644 index 00000000..8a4ae9c5 Binary files /dev/null and b/graphics/gfx_classic/icons/icon.png differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/000.level b/levels/Tutorials/rnd_tutorial_ncrecc/000.level new file mode 100644 index 00000000..64aff8dd Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/000.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/001.level b/levels/Tutorials/rnd_tutorial_ncrecc/001.level new file mode 100644 index 00000000..83e03830 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/001.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/002.level b/levels/Tutorials/rnd_tutorial_ncrecc/002.level new file mode 100644 index 00000000..16e3a055 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/002.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/003.level b/levels/Tutorials/rnd_tutorial_ncrecc/003.level new file mode 100644 index 00000000..f351dc56 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/003.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/004.level b/levels/Tutorials/rnd_tutorial_ncrecc/004.level new file mode 100644 index 00000000..e71240e7 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/004.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/005.level b/levels/Tutorials/rnd_tutorial_ncrecc/005.level new file mode 100644 index 00000000..ae269cbf Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/005.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/006.level b/levels/Tutorials/rnd_tutorial_ncrecc/006.level new file mode 100644 index 00000000..e73c5f3f Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/006.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/007.level b/levels/Tutorials/rnd_tutorial_ncrecc/007.level new file mode 100644 index 00000000..4d69fdf5 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/007.level differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/README.txt b/levels/Tutorials/rnd_tutorial_ncrecc/README.txt new file mode 100644 index 00000000..2eb50c34 --- /dev/null +++ b/levels/Tutorials/rnd_tutorial_ncrecc/README.txt @@ -0,0 +1,18 @@ +a while ago i went... "man, some of these rnd elements could use tutorials!" +specifically, things like emerald mine club and diamond caves 2 elements that +aren't in the current built-in tutorials. so i sat down and made some catchy, +fun tutorial levels for them. + +that "while ago" was 2018-2019. 3 years later, in 2022, i return to unearth this +tutorial levelset. only 8 levels were finished despite plans for more, but it's +better to have these available than not at all. + +the original version of this levelset included custom music from some modules +i liked. this music has been removed in the interest of keeping it copyright +friendly. + +i hope to update this in the future (or make a separare tutorial) to cover more +advanced topics like player inventories, multiplayer, custom elements, and +group elements. + +released in 2022 by ncrecc \ No newline at end of file diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/graphics/RocksTooMuchBars.png b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/RocksTooMuchBars.png new file mode 100644 index 00000000..af420bd6 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/RocksTooMuchBars.png differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/graphics/RocksTooMuchPanel.png b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/RocksTooMuchPanel.png new file mode 100644 index 00000000..2596ec58 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/RocksTooMuchPanel.png differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/graphics/graphicsinfo.conf b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/graphicsinfo.conf new file mode 100644 index 00000000..8e8066ae --- /dev/null +++ b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/graphicsinfo.conf @@ -0,0 +1,161 @@ +name: ncrtorial + +titlescreen_1: ncrtorial_title_screen.png +titlescreen_1.scale_up_factor: 2 +background.PANEL:RocksTooMuchPanel.png +background.PANEL.x:0 +background.PANEL.y:0 +background.PANEL.width:100 +background.PANEL.height:280 +game.panel.level_number.x:50 +game.panel.level_number.y:4 +game.panel.gems.y:31 +game.panel.inventory_count.y:58 +game.panel.inventory_last_1.x:5 +game.panel.inventory_last_1.y:57 +game.panel.inventory_last_1.draw_order:-2 +game.panel.inventory_last_1.tile_size:16 +game.panel.inventory_last_2.x:79 +game.panel.inventory_last_2.y:57 +game.panel.inventory_last_2.draw_order:-2 +game.panel.inventory_last_2.tile_size:16 +game.panel.graphic_1.x:3 +game.panel.graphic_1.y:64 +game.panel.graphic_1.draw_order:-1 +game.panel.graphic_1.draw_masked:true +game.panel.graphic_2.x:77 +game.panel.graphic_2.y:64 +game.panel.graphic_2.draw_order:-1 +game.panel.graphic_2.draw_masked:true +game.panel.element_1_count.x:25 +game.panel.element_1_count.y:85 +game.panel.element_1_count.align:left +game.panel.element_1_count.digits:2 +game.panel.element_1_count.draw_order:-1 +game.panel.element_1_count.font:font.level_number +game.panel.element_1_count.element:dynabomb_player_1.active +game.panel.dynabomb_number.x:55 +game.panel.dynabomb_number.y:85 +game.panel.dynabomb_number.align:left +game.panel.dynabomb_number.digits:2 +game.panel.dynabomb_number.draw_order:-1 +game.panel.dynabomb_number.font:font.level_number +game.panel.dynabomb_size.x:50 +game.panel.dynabomb_size.y:103 +game.panel.dynabomb_size.align:center +game.panel.dynabomb_size.draw_order:-1 +game.panel.dynabomb_size.digits:3 +game.panel.dynabomb_power.x:5 +game.panel.dynabomb_power.y:84 +game.panel.dynabomb_power.draw_order:-1 +game.panel.dynabomb_power.tile_size:16 +game.panel.time_anim.x:3 +game.panel.time_anim.y:46 +game.panel.health_anim.x:3 +game.panel.health_anim.y:82 +game.panel.key_1.x:3 +game.panel.key_1.y:129 +game.panel.key_1.draw_masked:true +game.panel.key_2.x:18 +game.panel.key_2.y:129 +game.panel.key_2.draw_masked:true +game.panel.key_3.x:33 +game.panel.key_3.y:129 +game.panel.key_3.draw_masked:true +game.panel.key_4.x:48 +game.panel.key_4.y:129 +game.panel.key_4.draw_masked:true +game.panel.key_5.x:64 +game.panel.key_5.y:129 +game.panel.key_5.draw_masked:true +game.panel.key_6.x:80 +game.panel.key_6.y:129 +game.panel.key_6.draw_masked:true +game.panel.key_7.x:4 +game.panel.key_7.y:142 +game.panel.key_7.draw_masked:true +game.panel.key_8.x:20 +game.panel.key_8.y:142 +game.panel.key_8.draw_masked:true +game.panel.key_white.x:80 +game.panel.key_white.y:142 +game.panel.key_white.draw_masked:true +game.panel.key_white_count.x:79 +game.panel.key_white_count.y:143 +game.panel.key_white_count.align:right +game.panel.key_white_count.digits:-1 +game.panel.key_white_count.font:font.level_number +game.panel.score.y:170 +game.panel.time.y:197 +dynabomb_increase_power.PANEL:RocksTooMuchPanel.png +dynabomb_increase_power.PANEL.xoffset:6 +dynabomb_increase_power.PANEL.yoffset:216 +dynabomb_increase_power.PANEL.frames:2 +dynabomb_increase_power.PANEL.anim_mode:linear +dynabomb_increase_power.PANEL.start_frame:1 +#dynabomb_player_1.PANEL:RocksTooMuchPanel.png +#dynabomb_player_1.PANEL.xoffset:38 +#dynabomb_player_1.PANEL.yoffset:216 +#dynabomb_player_1.PANEL.frames:2 +#dynabomb_player_1.PANEL.anim_mode:linear +#dynabomb_player_1.PANEL.start_frame:1 +emc_key_5.PANEL:RocksTooMuchPanel.png +emc_key_5.PANEL.xoffset:38 +emc_key_5.PANEL.yoffset:216 +emc_key_5.PANEL.frames:2 +emc_key_5.PANEL.anim_mode:linear +emc_key_5.PANEL.start_frame:1 +emc_key_6.PANEL:RocksTooMuchPanel.png +emc_key_6.PANEL.xoffset:38 +emc_key_6.PANEL.yoffset:242 +emc_key_6.PANEL.frames:2 +emc_key_6.PANEL.anim_mode:linear +emc_key_6.PANEL.start_frame:1 +emc_key_7.PANEL:RocksTooMuchPanel.png +emc_key_7.PANEL.xpos:0 +emc_key_7.PANEL.ypos:9 +emc_key_7.PANEL.frames:1 +emc_key_8.PANEL:RocksTooMuchPanel.png +emc_key_8.PANEL.xpos:1 +emc_key_8.PANEL.ypos:9 +emc_key_8.PANEL.frames:1 +dc_key_white.PANEL:RocksTooMuchPanel.png +dc_key_white.PANEL.xpos:2 +dc_key_white.PANEL.ypos:9 +dc_key_white.PANEL.frames:1 +graphic_1:RocksTooMuchPanel.png +graphic_1.x:5 +graphic_1.y:265 +graphic_1.width:8 +graphic_1.height:10 +graphic_1.frames:1 +graphic_2:RocksTooMuchPanel.png +graphic_2.x:13 +graphic_2.y:265 +graphic_2.width:8 +graphic_2.height:10 +graphic_2.frames:1 +gfx.game.panel.time_anim:RocksTooMuchBars.png +gfx.game.panel.time_anim.x:0 +gfx.game.panel.time_anim.y:0 +gfx.game.panel.time_anim.width:94 +gfx.game.panel.time_anim.height:36 +gfx.game.panel.time_anim.frames:1 +gfx.game.panel.time_anim.active:RocksTooMuchBars.png +gfx.game.panel.time_anim.active.x:94 +gfx.game.panel.time_anim.active.y:0 +gfx.game.panel.time_anim.active.width:94 +gfx.game.panel.time_anim.active.height:36 +gfx.game.panel.time_anim.active.frames:1 +gfx.game.panel.health_anim:RocksTooMuchBars.png +gfx.game.panel.health_anim.x:0 +gfx.game.panel.health_anim.y:36 +gfx.game.panel.health_anim.width:94 +gfx.game.panel.health_anim.height:38 +gfx.game.panel.health_anim.frames:1 +gfx.game.panel.health_anim.active:RocksTooMuchBars.png +gfx.game.panel.health_anim.active.x:94 +gfx.game.panel.health_anim.active.y:36 +gfx.game.panel.health_anim.active.width:94 +gfx.game.panel.health_anim.active.height:38 +gfx.game.panel.health_anim.active.frames:1 \ No newline at end of file diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/graphics/ncrtorial_title_screen.png b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/ncrtorial_title_screen.png new file mode 100644 index 00000000..5da222c1 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/graphics/ncrtorial_title_screen.png differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/levelinfo.conf b/levels/Tutorials/rnd_tutorial_ncrecc/levelinfo.conf new file mode 100644 index 00000000..c02b8e0e --- /dev/null +++ b/levels/Tutorials/rnd_tutorial_ncrecc/levelinfo.conf @@ -0,0 +1,11 @@ +# ============================================================================= +# levelinfo.conf +# ============================================================================= + +name: Ncrecc's Tutorial - ncrtorial +author: ncrecc + +levels: 8 +first_level: 0 + +sort_priority: 20 diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000.tape new file mode 100644 index 00000000..a75f9c20 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000.tape.BROKEN b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000.tape.BROKEN new file mode 100644 index 00000000..010a768f Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000.tape.BROKEN differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000_TAS.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000_TAS.tape new file mode 100644 index 00000000..94d496c8 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/000_TAS.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/001.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/001.tape new file mode 100644 index 00000000..0a8e3ff7 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/001.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/002.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/002.tape new file mode 100644 index 00000000..1165b7da Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/002.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/003.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/003.tape new file mode 100644 index 00000000..d9dc3fdc Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/003.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/004.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/004.tape new file mode 100644 index 00000000..fc2c0ec8 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/004.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/005.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/005.tape new file mode 100644 index 00000000..72a32e8f Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/005.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/006.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/006.tape new file mode 100644 index 00000000..8739b961 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/006.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/tapes/007.tape b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/007.tape new file mode 100644 index 00000000..6cca1846 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/tapes/007.tape differ diff --git a/levels/Tutorials/rnd_tutorial_ncrecc/unused.level b/levels/Tutorials/rnd_tutorial_ncrecc/unused.level new file mode 100644 index 00000000..be21c479 Binary files /dev/null and b/levels/Tutorials/rnd_tutorial_ncrecc/unused.level differ diff --git a/levels/Tutorials/rnd_tutorial_niko_boehm/README b/levels/Tutorials/rnd_tutorial_niko_boehm/README index 7cd4daa8..7b4fdc92 100644 --- a/levels/Tutorials/rnd_tutorial_niko_boehm/README +++ b/levels/Tutorials/rnd_tutorial_niko_boehm/README @@ -1,26 +1,30 @@ +# .font: font.text_2 NB's Introduction Levels -~~~~~~~~~~~~~~~~~~~~~~~~ +# .font: font.text_1 About ------- + +# .font: font.info.levelset These levels are meant as an advanced tutorial, where (nearly) all of the Rocks'n'Diamonds elements are introduced (with one exception; see below). Each level contains either one or more associated new elements. Mostly there is also an envelope explaining the new elements. -The only elements not present are gravitiy ports. Because gravity is +The only elements not present are gravity ports. Because gravity is introduced not until the last level and they originally belong to Supaplex, they simply didn't fit in anywhere ;) - +# .font: font.text_1 Legal notes ------------- + +# .font: font.info.levelset You may do anything with these levels as long as they or any derived work of them are made available freely. If you however want to charge money for derived work, please contact the author first. +# .font: font.text_1 +Contact -Contact: ---------- -Niko Böhm +# .font: font.info.levelset +Niko Böhm diff --git a/levels/Tutorials/rnd_tutorial_niko_boehm/README.orig b/levels/Tutorials/rnd_tutorial_niko_boehm/README.orig new file mode 100644 index 00000000..7cd4daa8 --- /dev/null +++ b/levels/Tutorials/rnd_tutorial_niko_boehm/README.orig @@ -0,0 +1,26 @@ +NB's Introduction Levels +~~~~~~~~~~~~~~~~~~~~~~~~ + +About +------ +These levels are meant as an advanced tutorial, where (nearly) all of the +Rocks'n'Diamonds elements are introduced (with one exception; see below). + +Each level contains either one or more associated new elements. Mostly there +is also an envelope explaining the new elements. + +The only elements not present are gravitiy ports. Because gravity is +introduced not until the last level and they originally belong to Supaplex, +they simply didn't fit in anywhere ;) + + +Legal notes +------------ +You may do anything with these levels as long as they or any derived work +of them are made available freely. If you however want to charge money for +derived work, please contact the author first. + + +Contact: +--------- +Niko Böhm diff --git a/levels/Tutorials/rnd_tutorial_niko_boehm/README.txt b/levels/Tutorials/rnd_tutorial_niko_boehm/README.txt new file mode 100644 index 00000000..56ee097a --- /dev/null +++ b/levels/Tutorials/rnd_tutorial_niko_boehm/README.txt @@ -0,0 +1,23 @@ +NB's Introduction Levels + +About +----- +These levels are meant as an advanced tutorial, where (nearly) all of the +Rocks'n'Diamonds elements are introduced (with one exception; see below). + +Each level contains either one or more associated new elements. Mostly there +is also an envelope explaining the new elements. + +The only elements not present are gravity ports. Because gravity is +introduced not until the last level and they originally belong to Supaplex, +they simply didn't fit in anywhere ;) + +Legal notes +----------- +You may do anything with these levels as long as they or any derived work +of them are made available freely. If you however want to charge money for +derived work, please contact the author first. + +Contact +------- +Niko Böhm diff --git a/music/mus_classic/rhythmloop.wav b/music/mus_classic/rhythmloop.wav index d9ebce2c..32217cc2 100644 Binary files a/music/mus_classic/rhythmloop.wav and b/music/mus_classic/rhythmloop.wav differ diff --git a/sounds/snd_classic/autsch.wav b/sounds/snd_classic/autsch.wav index e691946f..0e041dae 100644 Binary files a/sounds/snd_classic/autsch.wav and b/sounds/snd_classic/autsch.wav differ diff --git a/sounds/snd_classic/bong.wav b/sounds/snd_classic/bong.wav index 9808fc1a..d917ae87 100644 Binary files a/sounds/snd_classic/bong.wav and b/sounds/snd_classic/bong.wav differ diff --git a/sounds/snd_classic/fuel.wav b/sounds/snd_classic/fuel.wav index cc9463c9..667e9b20 100644 Binary files a/sounds/snd_classic/fuel.wav and b/sounds/snd_classic/fuel.wav differ diff --git a/sounds/snd_classic/halloffame.wav b/sounds/snd_classic/halloffame.wav index a09ea5c0..7e9f84bf 100644 Binary files a/sounds/snd_classic/halloffame.wav and b/sounds/snd_classic/halloffame.wav differ diff --git a/src/Android.mk b/src/Android.mk index 0652311f..a644d93a 100644 --- a/src/Android.mk +++ b/src/Android.mk @@ -46,6 +46,8 @@ LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ libgame/image.c \ libgame/random.c \ libgame/hash.c \ + libgame/http.c \ + libgame/base64.c \ libgame/setup.c \ libgame/misc.c \ libgame/sdl.c \ @@ -103,6 +105,7 @@ LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ files.c \ tape.c \ anim.c \ + api.c \ network.c \ netserv.c diff --git a/src/Makefile b/src/Makefile index f40ada29..b7289d01 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,8 +44,6 @@ DEBUGGER = gdb -batch -ex "run" -ex "bt" PROGBASE = rocksndiamonds PROGNAME = ../$(PROGBASE) -EDITION ?= default - # ----------------------------------------------------------------------------- # configuring platform @@ -62,14 +60,17 @@ endif ifeq ($(PLATFORM),emscripten) # compiling with Emscripten PROGNAME = ../$(PROGBASE).js +DATA_FILE = $(PROGBASE).data CC = emcc AR = emar RANLIB = emranlib STRIP = true +FILE_PACKAGER = file_packager endif ifeq ($(shell uname -s),Darwin) # compiling on Mac OS X DEBUGGER = lldb --batch -o "run" -k "bt" -k "quit" +SANITIZING_FLAGS = -fsanitize=undefined ifdef BUILD_DIST # distribution build MAC_TARGET_VERSION_MIN = 10.7 EXTRA_FLAGS_MAC = -mmacosx-version-min=$(MAC_TARGET_VERSION_MIN) @@ -79,6 +80,11 @@ MACOSX_DEPLOYMENT_TARGET = $MAC_TARGET_VERSION_MIN endif endif +ifeq ($(shell uname -s),OS/2) # compiling on OS/2 +PROGNAME = ../$(PROGBASE).exe +EXTRA_LDFLAGS = -Zomf -Zbin-files -Zmap -lcx -Zhigh-mem +endif + # ----------------------------------------------------------------------------- # configuring target @@ -97,10 +103,12 @@ endif ifeq ($(TARGET),sdl2) # compiling for SDL2 target ifeq ($(PLATFORM),emscripten) -SDL_LIBS = -s USE_SDL_IMAGE=2 -s USE_SDL_MIXER=2 -s USE_SDL_NET=2 -s USE_ZLIB=1 -SDL_FMTS = -s SDL2_IMAGE_FORMATS='["bmp","png","pcx","xpm"]' +SDL_LIBS = -s USE_SDL_IMAGE=2 -s USE_SDL_MIXER=2 -s USE_SDL_NET=2 -s USE_MODPLUG=1 -s USE_MPG123=1 -s USE_ZLIB=1 +SDL_FMTS = -s SDL2_IMAGE_FORMATS='["bmp","png","pcx","xpm"]' -s SDL2_MIXER_FORMATS='["mod","mp3"]' EXTRA_CFLAGS = $(SDL_LIBS) -EXTRA_LDFLAGS = $(SDL_FMTS) -s INITIAL_MEMORY=81920000 -s ALLOW_MEMORY_GROWTH=1 --preload-file ../graphics/ --preload-file ../sounds/ --preload-file ../levels/ --preload-file ../music/ -s NO_EXIT_RUNTIME=0 -s ASYNCIFY -O2 -lidbfs.js +EXTRA_LDFLAGS = $(SDL_FMTS) -s INITIAL_MEMORY=81920000 -s ALLOW_MEMORY_GROWTH=1 -s FORCE_FILESYSTEM -s NO_EXIT_RUNTIME=0 -s ASYNCIFY -O2 -lidbfs.js +DATA_DIRS = conf docs levels graphics sounds music +FILE_PACKAGER_ARGS = --preload $(DATA_DIRS) --js-output=$(DATA_FILE).js else SDL_LIBS = -lSDL2_image -lSDL2_mixer -lSDL2_net endif @@ -114,37 +122,39 @@ endif # configuring compile-time definitions # ----------------------------------------------------------------------------- -ifdef RO_GAME_DIR # path to read-only game data specified -CONFIG_RO_GAME_DIR = -DRO_GAME_DIR="\"$(RO_GAME_DIR)\"" -endif - -ifdef RW_GAME_DIR # path to writable game data specified -CONFIG_RW_GAME_DIR = -DRW_GAME_DIR="\"$(RW_GAME_DIR)\"" +ifdef BASE_PATH # path to read-only game data +CONFIG_BASE_PATH = -DBASE_PATH="\"$(BASE_PATH)\"" endif -CONFIG = $(CONFIG_RO_GAME_DIR) $(CONFIG_RW_GAME_DIR) $(JOYSTICK) +CONFIG = $(CONFIG_BASE_PATH) $(JOYSTICK) DEBUG = -DDEBUG -g -# PROFILING = $(PROFILING_FLAGS) +# ANALYZE = $(PROFILING_FLAGS) +# ANALYZE = $(SANITIZING_FLAGS) # OPTIONS = $(DEBUG) -Wall # only for debugging purposes # OPTIONS = $(DEBUG) -O2 -Wall # only for debugging purposes # OPTIONS = $(DEBUG) -Wall # only for debugging purposes OPTIONS = $(DEBUG) -Wall -Wstrict-prototypes -Wmissing-prototypes +# OPTIONS = $(DEBUG) -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes # OPTIONS = $(DEBUG) -Wall -ansi -pedantic # only for debugging purposes # OPTIONS = -O2 -Wall -ansi -pedantic # OPTIONS = -O2 -Wall # OPTIONS = -O2 +ifdef BUILD_TEST # test build +OPTIONS := $(OPTIONS) -DTESTING +endif + ifdef BUILD_DIST # distribution build SYS_LDFLAGS := $(shell echo $(SYS_LDFLAGS) | \ sed -e "s%-rpath,[^ ]*%-rpath,'\$$ORIGIN/lib'%") OPTIONS = -O2 -Wall endif -CFLAGS = $(OPTIONS) $(SYS_CFLAGS) $(EXTRA_CFLAGS) $(CONFIG) -LDFLAGS = $(SYS_LDFLAGS) $(EXTRA_LDFLAGS) +CFLAGS = $(OPTIONS) $(ANALYZE) $(SYS_CFLAGS) $(EXTRA_CFLAGS) $(CONFIG) +LDFLAGS = $(ANALYZE) $(SYS_LDFLAGS) $(EXTRA_LDFLAGS) SRCS = main.c \ @@ -162,6 +172,7 @@ SRCS = main.c \ files.c \ tape.c \ anim.c \ + api.c \ network.c \ netserv.c @@ -180,6 +191,7 @@ OBJS = main.o \ files.o \ tape.o \ anim.o \ + api.o \ network.o \ netserv.o @@ -192,6 +204,8 @@ CNFS = conf_gfx.h \ conf_cus.h \ conf_grp.c \ conf_grp.h \ + conf_emp.c \ + conf_emp.h \ conf_e2g.c \ conf_esg.c \ conf_e2s.c \ @@ -234,10 +248,10 @@ RNDLIBS = $(LIBGAME) $(GAME_EM) $(GAME_SP) $(GAME_MM) AUTOCONF = conf_gfx.h conf_snd.h conf_mus.h ICONBASE = windows_icon -ICON_BASEPATH = ../Special/Icons/windows_icons +ICON_BASEPATH = ../build-projects/windows/icons ifeq ($(PLATFORM_BASE),cross-win) -ICON_PATH = $(ICON_BASEPATH)/$(EDITION) +ICON_PATH = $(ICON_BASEPATH) ICON = $(ICONBASE).o endif @@ -251,10 +265,13 @@ GRAPHICS_DIR = ../graphics all: $(AUTOCONF) libgame_dir game_em_dir game_sp_dir game_mm_dir $(PROGNAME) graphics_dir $(PROGNAME): $(RNDLIBS) $(TIMESTAMP_FILE) $(COMMIT_HASH_FILE) $(OBJS) $(ICON) - $(CC) $(PROFILING) $(OBJS) $(ICON) $(RNDLIBS) $(LDFLAGS) -o $(PROGNAME) + $(CC) $(OBJS) $(ICON) $(RNDLIBS) $(LDFLAGS) -o $(PROGNAME) ifdef BUILD_DIST $(STRIP) $(PROGNAME) endif +ifeq ($(PLATFORM),emscripten) + (cd .. ; $(FILE_PACKAGER) $(DATA_FILE) $(FILE_PACKAGER_ARGS)) +endif libgame_dir: @$(MAKE) -C $(LIBGAME_DIR) @@ -305,6 +322,8 @@ conf-hash: @echo '#define SOURCE_HASH_STRING "$(SOURCE_HASH_STRING)"' \ > $(COMMIT_HASH_FILE) +config.o: config.c $(TIMESTAMP_FILE) + $(TIMESTAMP_FILE): $(SRCS) $(RNDLIBS) @$(MAKE) conf-time @@ -312,12 +331,11 @@ $(COMMIT_HASH_FILE): $(SRCS) $(RNDLIBS) @$(MAKE) conf-hash $(ICON): -# $(CONVERT) $(ICON32X32) $(CONVERT_ICON_ARGS) $(ICONBASE).ico $(CONVERT) $(ICON_PATH)/*.png $(CONVERT_ICON_ARGS) $(ICONBASE).ico echo "$(ICONBASE) ICON $(ICONBASE).ico" | $(WINDRES) -o $(ICON) .c.o: - $(CC) $(PROFILING) $(CFLAGS) -c $*.c + $(CC) $(CFLAGS) -c $*.c graphics_dir: @test -f $(GRAPHICS_DIR)/Makefile && $(MAKE) -C $(GRAPHICS_DIR) || true diff --git a/src/anim.c b/src/anim.c index 2e6280ce..1389dc4e 100644 --- a/src/anim.c +++ b/src/anim.c @@ -17,6 +17,7 @@ #include "files.h" #include "events.h" #include "screens.h" +#include "tape.h" #define DEBUG_ANIM_DELAY 0 @@ -33,22 +34,30 @@ #define NUM_GLOBAL_ANIM_PARTS_AND_TOONS MAX(NUM_GLOBAL_ANIM_PARTS_ALL, \ NUM_GLOBAL_TOON_PARTS) +#define MAX_GLOBAL_ANIM_LIST (NUM_GAME_MODES * \ + NUM_GLOBAL_ANIMS_AND_TOONS * \ + NUM_GLOBAL_ANIM_PARTS_AND_TOONS) + #define ANIM_CLASS_BIT_TITLE_INITIAL 0 #define ANIM_CLASS_BIT_TITLE 1 #define ANIM_CLASS_BIT_MAIN 2 -#define ANIM_CLASS_BIT_SCORES 3 -#define ANIM_CLASS_BIT_SUBMENU 4 -#define ANIM_CLASS_BIT_MENU 5 -#define ANIM_CLASS_BIT_TOONS 6 -#define ANIM_CLASS_BIT_NO_TITLE 7 +#define ANIM_CLASS_BIT_NAMES 3 +#define ANIM_CLASS_BIT_SCORES 4 +#define ANIM_CLASS_BIT_SCORESONLY 5 +#define ANIM_CLASS_BIT_SUBMENU 6 +#define ANIM_CLASS_BIT_MENU 7 +#define ANIM_CLASS_BIT_TOONS 8 +#define ANIM_CLASS_BIT_NO_TITLE 9 -#define NUM_ANIM_CLASSES 8 +#define NUM_ANIM_CLASSES 10 #define ANIM_CLASS_NONE 0 #define ANIM_CLASS_TITLE_INITIAL (1 << ANIM_CLASS_BIT_TITLE_INITIAL) #define ANIM_CLASS_TITLE (1 << ANIM_CLASS_BIT_TITLE) #define ANIM_CLASS_MAIN (1 << ANIM_CLASS_BIT_MAIN) +#define ANIM_CLASS_NAMES (1 << ANIM_CLASS_BIT_NAMES) #define ANIM_CLASS_SCORES (1 << ANIM_CLASS_BIT_SCORES) +#define ANIM_CLASS_SCORESONLY (1 << ANIM_CLASS_BIT_SCORESONLY) #define ANIM_CLASS_SUBMENU (1 << ANIM_CLASS_BIT_SUBMENU) #define ANIM_CLASS_MENU (1 << ANIM_CLASS_BIT_MENU) #define ANIM_CLASS_TOONS (1 << ANIM_CLASS_BIT_TOONS) @@ -58,6 +67,11 @@ ANIM_CLASS_SCORES | \ ANIM_CLASS_NO_TITLE) +#define ANIM_CLASS_TOONS_SCORESONLY (ANIM_CLASS_TOONS | \ + ANIM_CLASS_SCORES | \ + ANIM_CLASS_SCORESONLY | \ + ANIM_CLASS_NO_TITLE) + #define ANIM_CLASS_TOONS_MENU_MAIN (ANIM_CLASS_TOONS | \ ANIM_CLASS_MENU | \ ANIM_CLASS_MAIN | \ @@ -68,6 +82,12 @@ ANIM_CLASS_SUBMENU | \ ANIM_CLASS_NO_TITLE) +#define ANIM_CLASS_TOONS_MENU_SUBMENU_2 (ANIM_CLASS_TOONS | \ + ANIM_CLASS_MENU | \ + ANIM_CLASS_SUBMENU | \ + ANIM_CLASS_NAMES | \ + ANIM_CLASS_NO_TITLE) + // values for global animation states #define ANIM_STATE_INACTIVE 0 #define ANIM_STATE_RESTART (1 << 0) @@ -99,6 +119,8 @@ struct GlobalAnimPartControlInfo struct GraphicInfo graphic_info; struct GraphicInfo control_info; + boolean class_playfield_or_door; + int viewport_x; int viewport_y; int viewport_width; @@ -107,14 +129,21 @@ struct GlobalAnimPartControlInfo int x, y; int step_xoffset, step_yoffset; + int tile_x, tile_y; + int tile_xoffset, tile_yoffset; + unsigned int initial_anim_sync_frame; unsigned int anim_random_frame; - unsigned int step_delay, step_delay_value; + + DelayCounter step_delay; int init_delay_counter; int anim_delay_counter; int post_delay_counter; + int fade_delay_counter; + int fade_alpha; + boolean init_event_state; boolean anim_event_state; @@ -183,12 +212,13 @@ struct GameModeAnimClass { GAME_MODE_LEVELNR, ANIM_CLASS_TOONS_MENU_SUBMENU }, { GAME_MODE_INFO, ANIM_CLASS_TOONS_MENU_SUBMENU }, { GAME_MODE_SETUP, ANIM_CLASS_TOONS_MENU_SUBMENU }, - { GAME_MODE_PSEUDO_NAMESONLY, ANIM_CLASS_TOONS_MENU_SUBMENU }, - { GAME_MODE_PSEUDO_TYPENAMES, ANIM_CLASS_TOONS_MENU_SUBMENU }, + { GAME_MODE_PSEUDO_NAMESONLY, ANIM_CLASS_TOONS_MENU_SUBMENU_2 }, + { GAME_MODE_PSEUDO_TYPENAMES, ANIM_CLASS_TOONS_MENU_SUBMENU_2 }, { GAME_MODE_PSEUDO_MAINONLY, ANIM_CLASS_TOONS_MENU_MAIN }, { GAME_MODE_PSEUDO_TYPENAME, ANIM_CLASS_TOONS_MENU_MAIN }, - { GAME_MODE_PSEUDO_SCORESOLD, ANIM_CLASS_TOONS_SCORES }, - { GAME_MODE_PSEUDO_SCORESNEW, ANIM_CLASS_TOONS_SCORES }, + { GAME_MODE_PSEUDO_SCORESOLD, ANIM_CLASS_TOONS_SCORESONLY }, + { GAME_MODE_PSEUDO_SCORESNEW, ANIM_CLASS_TOONS_SCORESONLY }, + { GAME_MODE_SCOREINFO, ANIM_CLASS_TOONS_SCORES }, { GAME_MODE_EDITOR, ANIM_CLASS_NO_TITLE }, { GAME_MODE_PLAYING, ANIM_CLASS_NO_TITLE }, @@ -204,7 +234,9 @@ struct AnimClassGameMode { ANIM_CLASS_BIT_TITLE_INITIAL, GAME_MODE_TITLE_INITIAL }, { ANIM_CLASS_BIT_TITLE, GAME_MODE_TITLE }, { ANIM_CLASS_BIT_MAIN, GAME_MODE_MAIN }, + { ANIM_CLASS_BIT_NAMES, GAME_MODE_NAMES }, { ANIM_CLASS_BIT_SCORES, GAME_MODE_SCORES }, + { ANIM_CLASS_BIT_SCORESONLY, GAME_MODE_PSEUDO_SCORESONLY }, { ANIM_CLASS_BIT_SUBMENU, GAME_MODE_PSEUDO_SUBMENU }, { ANIM_CLASS_BIT_MENU, GAME_MODE_PSEUDO_MENU }, { ANIM_CLASS_BIT_TOONS, GAME_MODE_PSEUDO_TOONS }, @@ -222,6 +254,8 @@ static void ResetGlobalAnim_Clickable(void); static void ResetGlobalAnim_Clicked(void); static struct GlobalAnimControlInfo global_anim_ctrl[NUM_GAME_MODES]; +static struct GlobalAnimPartControlInfo *global_anim_list[MAX_GLOBAL_ANIM_LIST]; +static int num_global_anim_list = 0; static unsigned int anim_sync_frame = 0; @@ -285,6 +319,12 @@ int getAnimationFrame(int num_frames, int delay, int mode, int start_frame, else frame = gfx.anim_random_frame % num_frames; } + else if (mode & ANIM_LEVEL_NR) // play frames by level number + { + int level_pos = level_nr - gfx.anim_first_level; + + frame = level_pos % num_frames; + } else if (mode & (ANIM_CE_VALUE | ANIM_CE_SCORE | ANIM_CE_DELAY)) { frame = sync_frame % num_frames; @@ -321,33 +361,42 @@ static int getGlobalAnimationPart(struct GlobalAnimMainControlInfo *anim) static int compareGlobalAnimPartControlInfo(const void *obj1, const void *obj2) { const struct GlobalAnimPartControlInfo *o1 = - (struct GlobalAnimPartControlInfo *)obj1; + *(struct GlobalAnimPartControlInfo **)obj1; const struct GlobalAnimPartControlInfo *o2 = - (struct GlobalAnimPartControlInfo *)obj2; + *(struct GlobalAnimPartControlInfo **)obj2; int compare_result; if (o1->control_info.draw_order != o2->control_info.draw_order) compare_result = o1->control_info.draw_order - o2->control_info.draw_order; + else if (o1->mode_nr != o2->mode_nr) + compare_result = o1->mode_nr - o2->mode_nr; + else if (o1->anim_nr != o2->anim_nr) + compare_result = o1->anim_nr - o2->anim_nr; else compare_result = o1->nr - o2->nr; return compare_result; } -static int compareGlobalAnimMainControlInfo(const void *obj1, const void *obj2) +static boolean isPausedOnPlayfieldOrDoor(struct GlobalAnimPartControlInfo *part) { - const struct GlobalAnimMainControlInfo *o1 = - (struct GlobalAnimMainControlInfo *)obj1; - const struct GlobalAnimMainControlInfo *o2 = - (struct GlobalAnimMainControlInfo *)obj2; - int compare_result; + // only pause playfield and door animations when playing + if (game_status != GAME_MODE_PLAYING) + return FALSE; - if (o1->control_info.draw_order != o2->control_info.draw_order) - compare_result = o1->control_info.draw_order - o2->control_info.draw_order; - else - compare_result = o1->nr - o2->nr; + // do not pause animations when game ended (and engine is running) + if (checkGameEnded()) + return FALSE; - return compare_result; + // only pause animations on playfield and doors + if (!part->class_playfield_or_door) + return FALSE; + + // only pause animations when engine is paused or request dialog is active + if (!tape.pausing && !game.request_active) + return FALSE; + + return TRUE; } static void InitToonControls(void) @@ -392,7 +441,8 @@ static void InitToonControls(void) int sound = SND_UNDEFINED; int music = MUS_UNDEFINED; int graphic = IMG_TOON_1 + i; - int control = graphic; + + control = graphic; part->nr = part_nr; part->anim_nr = anim_nr; @@ -418,8 +468,8 @@ static void InitToonControls(void) part->initial_anim_sync_frame = 0; part->anim_random_frame = -1; - part->step_delay = 0; - part->step_delay_value = graphic_info[control].step_delay; + part->step_delay.count = 0; + part->step_delay.value = graphic_info[control].step_delay; part->state = ANIM_STATE_INACTIVE; part->last_anim_status = -1; @@ -525,8 +575,8 @@ static void InitGlobalAnimControls(void) part->initial_anim_sync_frame = 0; part->anim_random_frame = -1; - part->step_delay = 0; - part->step_delay_value = graphic_info[control].step_delay; + part->step_delay.count = 0; + part->step_delay.value = graphic_info[control].step_delay; part->state = ANIM_STATE_INACTIVE; part->last_anim_status = -1; @@ -548,18 +598,14 @@ static void InitGlobalAnimControls(void) anim->has_base = TRUE; } - // apply special settings for pointer-style animations + // apply special settings to pointer-style animations if (part->control_info.class == get_hash_from_key("pointer")) { - // force animation to be on top (must set anim and part control) - if (anim->control_info.draw_order == 0) - anim->control_info.draw_order = 1000000; - if (part->control_info.draw_order == 0) - part->control_info.draw_order = 1000000; - - // force animation to pass-through clicks (must set part control) - if (part->control_info.style == STYLE_DEFAULT) - part->control_info.style |= STYLE_PASSTHROUGH; + // force pointer-style animations to be checked for clicks first + part->control_info.draw_order = 1000000; + + // force pointer-style animations to pass-through clicks + part->control_info.style |= STYLE_PASSTHROUGH; } } @@ -573,26 +619,18 @@ static void InitGlobalAnimControls(void) InitToonControls(); - // sort all animations according to draw_order and animation number + // create list of all animation parts + num_global_anim_list = 0; for (m = 0; m < NUM_GAME_MODES; m++) - { - struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[m]; - - // sort all main animations for this game mode - qsort(ctrl->anim, ctrl->num_anims, - sizeof(struct GlobalAnimMainControlInfo), - compareGlobalAnimMainControlInfo); + for (a = 0; a < global_anim_ctrl[m].num_anims; a++) + for (p = 0; p < global_anim_ctrl[m].anim[a].num_parts_all; p++) + global_anim_list[num_global_anim_list++] = + &global_anim_ctrl[m].anim[a].part[p]; - for (a = 0; a < ctrl->num_anims; a++) - { - struct GlobalAnimMainControlInfo *anim = &ctrl->anim[a]; - - // sort all animation parts for this main animation - qsort(anim->part, anim->num_parts, - sizeof(struct GlobalAnimPartControlInfo), - compareGlobalAnimPartControlInfo); - } - } + // sort list of all animation parts according to draw_order and number + qsort(global_anim_list, num_global_anim_list, + sizeof(struct GlobalAnimPartControlInfo *), + compareGlobalAnimPartControlInfo); for (i = 0; i < NUM_GAME_MODES; i++) game_mode_anim_classes[i] = ANIM_CLASS_NONE; @@ -611,20 +649,143 @@ static void InitGlobalAnimControls(void) anim_classes_last = ANIM_CLASS_NONE; } +static void SetGlobalAnimEventsForCustomElements(int list_pos) +{ + int num_events = GetGlobalAnimEventValueCount(list_pos); + int i; + + for (i = 0; i < num_events; i++) + { + int event = GetGlobalAnimEventValue(list_pos, i); + + if (event & ANIM_EVENT_CE_CHANGE) + { + int nr = (event >> ANIM_EVENT_CE_BIT) & 0xff; + + if (nr >= 0 && nr < NUM_CUSTOM_ELEMENTS) + element_info[EL_CUSTOM_START + nr].has_anim_event = TRUE; + } + } +} + +void InitGlobalAnimEventsForCustomElements(void) +{ + int m, a, p; + int control; + + // custom element events for global animations only relevant while playing + m = GAME_MODE_PLAYING; + + for (a = 0; a < NUM_GLOBAL_ANIMS; a++) + { + int ctrl_id = GLOBAL_ANIM_ID_CONTROL_FIRST + a; + + control = global_anim_info[ctrl_id].graphic[GLOBAL_ANIM_ID_PART_BASE][m]; + + // if no base animation parameters defined, use default values + if (control == IMG_UNDEFINED) + control = IMG_INTERNAL_GLOBAL_ANIM_DEFAULT; + + SetGlobalAnimEventsForCustomElements(graphic_info[control].init_event); + SetGlobalAnimEventsForCustomElements(graphic_info[control].anim_event); + + for (p = 0; p < NUM_GLOBAL_ANIM_PARTS_ALL; p++) + { + control = global_anim_info[ctrl_id].graphic[p][m]; + + if (control == IMG_UNDEFINED) + continue; + + SetGlobalAnimEventsForCustomElements(graphic_info[control].init_event); + SetGlobalAnimEventsForCustomElements(graphic_info[control].anim_event); + } + } +} + void InitGlobalAnimations(void) { InitGlobalAnimControls(); } -static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage) +static void BlitGlobalAnimation(struct GlobalAnimPartControlInfo *part, + Bitmap *src_bitmap, int src_x0, int src_y0, + int drawing_target) { + struct GraphicInfo *g = &part->graphic_info; + struct GraphicInfo *c = &part->control_info; + void (*blit_bitmap)(Bitmap *, Bitmap *, int, int, int, int, int, int) = + (g->draw_masked ? BlitBitmapMasked : BlitBitmap); + void (*blit_screen)(Bitmap *, int, int, int, int, int, int) = + (g->draw_masked ? BlitToScreenMasked : BlitToScreen); Bitmap *fade_bitmap = (drawing_target == DRAW_TO_FADE_SOURCE ? gfx.fade_bitmap_source : drawing_target == DRAW_TO_FADE_TARGET ? gfx.fade_bitmap_target : NULL); + int alpha = (c->fade_mode & FADE_MODE_FADE ? part->fade_alpha : g->alpha); + int x, y; + + for (y = 0; y < c->stacked_yfactor; y++) + { + for (x = 0; x < c->stacked_xfactor; x++) + { + int src_x = src_x0; + int src_y = src_y0; + int dst_x = part->x + x * (g->width + c->stacked_xoffset); + int dst_y = part->y + y * (g->height + c->stacked_yoffset); + int cut_x = 0; + int cut_y = 0; + int width = g->width; + int height = g->height; + + if (dst_x < 0) + { + width += dst_x; + cut_x = -dst_x; + dst_x = 0; + } + else if (dst_x > part->viewport_width - g->width) + { + width -= (dst_x - (part->viewport_width - g->width)); + } + + if (dst_y < 0) + { + height += dst_y; + cut_y = -dst_y; + dst_y = 0; + } + else if (dst_y > part->viewport_height - g->height) + { + height -= (dst_y - (part->viewport_height - g->height)); + } + + if (width <= 0 || height <= 0) + continue; + + src_x += cut_x; + src_y += cut_y; + + dst_x += part->viewport_x; + dst_y += part->viewport_y; + + SetBitmapAlphaNextBlit(src_bitmap, alpha); + + if (drawing_target == DRAW_TO_SCREEN) + blit_screen(src_bitmap, src_x, src_y, width, height, + dst_x, dst_y); + else + blit_bitmap(src_bitmap, fade_bitmap, src_x, src_y, width, height, + dst_x, dst_y); + } + } +} + +static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage) +{ int game_mode_anim_action[NUM_GAME_MODES]; int mode_nr; + int i; - if (!setup.toons) + if (!setup.global_animations) return; if (drawing_stage == DRAW_GLOBAL_ANIM_STAGE_1 && @@ -715,130 +876,73 @@ static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage) } } + // when restarting global animations, do not redraw them, but stop here + if (drawing_stage == DRAW_GLOBAL_ANIM_STAGE_RESTART) + return; + if (global.anim_status == GAME_MODE_LOADING) return; - for (mode_nr = 0; mode_nr < NUM_GAME_MODES; mode_nr++) + for (i = 0; i < num_global_anim_list; i++) { - struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[mode_nr]; - int anim_nr; + struct GlobalAnimPartControlInfo *part = global_anim_list[i]; + struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[part->mode_nr]; + struct GlobalAnimMainControlInfo *anim = &ctrl->anim[part->anim_nr]; + struct GraphicInfo *g = &part->graphic_info; + Bitmap *src_bitmap; + int src_x, src_y; + int sync_frame; + int frame; + int last_anim_random_frame = gfx.anim_random_frame; + + if (!setup.toons && + part->graphic >= IMG_TOON_1 && + part->graphic <= IMG_TOON_20) + continue; // when preparing source fading buffer, only draw animations to be stopped if (drawing_target == DRAW_TO_FADE_SOURCE && - game_mode_anim_action[mode_nr] != ANIM_STOP) + game_mode_anim_action[part->mode_nr] != ANIM_STOP) continue; // when preparing target fading buffer, only draw animations to be started if (drawing_target == DRAW_TO_FADE_TARGET && - game_mode_anim_action[mode_nr] != ANIM_START) + game_mode_anim_action[part->mode_nr] != ANIM_START) continue; -#if 0 - if (mode_nr != GFX_SPECIAL_ARG_DEFAULT && - mode_nr != game_status) + if (!(anim->state & ANIM_STATE_RUNNING)) continue; -#endif - - for (anim_nr = 0; anim_nr < ctrl->num_anims; anim_nr++) - { - struct GlobalAnimMainControlInfo *anim = &ctrl->anim[anim_nr]; - struct GraphicInfo *c = &anim->control_info; - int part_first, part_last; - int part_nr; - - if (!(anim->state & ANIM_STATE_RUNNING)) - continue; - - part_first = part_last = anim->active_part_nr; - - if (c->anim_mode & ANIM_ALL || anim->num_parts == 0) - { - int num_parts = anim->num_parts + (anim->has_base ? 1 : 0); - - part_first = 0; - part_last = num_parts - 1; - } - - for (part_nr = part_first; part_nr <= part_last; part_nr++) - { - struct GlobalAnimPartControlInfo *part = &anim->part[part_nr]; - struct GraphicInfo *g = &part->graphic_info; - Bitmap *src_bitmap; - int src_x, src_y; - int width = g->width; - int height = g->height; - int dst_x = part->x; - int dst_y = part->y; - int cut_x = 0; - int cut_y = 0; - int sync_frame; - int frame; - void (*blit_bitmap)(Bitmap *, Bitmap *, int, int, int, int, int, int) = - (g->draw_masked ? BlitBitmapMasked : BlitBitmap); - void (*blit_screen)(Bitmap *, int, int, int, int, int, int) = - (g->draw_masked ? BlitToScreenMasked : BlitToScreen); - int last_anim_random_frame = gfx.anim_random_frame; - - if (!(part->state & ANIM_STATE_RUNNING)) - continue; - - if (part->drawing_stage != drawing_stage) - continue; - if (part->x < 0) - { - dst_x = 0; - width += part->x; - cut_x = -part->x; - } - else if (part->x > part->viewport_width - g->width) - width -= (part->x - (part->viewport_width - g->width)); - - if (part->y < 0) - { - dst_y = 0; - height += part->y; - cut_y = -part->y; - } - else if (part->y > part->viewport_height - g->height) - height -= (part->y - (part->viewport_height - g->height)); - - if (width <= 0 || height <= 0) - continue; + if (!(part->state & ANIM_STATE_RUNNING)) + continue; - dst_x += part->viewport_x; - dst_y += part->viewport_y; + if (part->drawing_stage != drawing_stage) + continue; - sync_frame = anim_sync_frame - part->initial_anim_sync_frame; + // if game is paused, also pause playfield and door animations + if (isPausedOnPlayfieldOrDoor(part)) + part->initial_anim_sync_frame++; - // re-initialize random animation frame after animation delay - if (g->anim_mode == ANIM_RANDOM && - sync_frame % g->anim_delay == 0 && - sync_frame > 0) - part->anim_random_frame = GetSimpleRandom(g->anim_frames); + sync_frame = anim_sync_frame - part->initial_anim_sync_frame; - gfx.anim_random_frame = part->anim_random_frame; + // re-initialize random animation frame after animation delay + if (g->anim_mode == ANIM_RANDOM && + sync_frame % g->anim_delay == 0 && + sync_frame > 0) + part->anim_random_frame = GetSimpleRandom(g->anim_frames); - frame = getAnimationFrame(g->anim_frames, g->anim_delay, - g->anim_mode, g->anim_start_frame, - sync_frame); + gfx.anim_random_frame = part->anim_random_frame; - gfx.anim_random_frame = last_anim_random_frame; + frame = getAnimationFrame(g->anim_frames, g->anim_delay, + g->anim_mode, g->anim_start_frame, + sync_frame); - getFixedGraphicSource(part->graphic, frame, &src_bitmap, - &src_x, &src_y); + gfx.anim_random_frame = last_anim_random_frame; - src_x += cut_x; - src_y += cut_y; + getGlobalAnimGraphicSource(part->graphic, frame, &src_bitmap, + &src_x, &src_y); - if (drawing_target == DRAW_TO_SCREEN) - blit_screen(src_bitmap, src_x, src_y, width, height, - dst_x, dst_y); - else - blit_bitmap(src_bitmap, fade_bitmap, src_x, src_y, width, height, - dst_x, dst_y); - } - } + BlitGlobalAnimation(part, src_bitmap, src_x, src_y, drawing_target); } if (drawing_target == DRAW_TO_FADE_TARGET) @@ -870,8 +974,6 @@ void DrawGlobalAnimations(int drawing_target, int drawing_stage) ResetGlobalAnim_Clicked(); } - DrawEnvelopeRequestToScreen(drawing_target, drawing_stage); - if (gfx.cursor_mode_override != last_cursor_mode_override) SetMouseCursor(gfx.cursor_mode); } @@ -892,6 +994,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part part->drawing_stage = DRAW_GLOBAL_ANIM_STAGE_1; + part->class_playfield_or_door = FALSE; + if (part->control_info.class == get_hash_from_key("window") || part->control_info.class == get_hash_from_key("border")) { @@ -917,7 +1021,7 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part viewport_width = part->graphic_info.width; viewport_height = part->graphic_info.height; - part->drawing_stage = DRAW_GLOBAL_ANIM_STAGE_2; + part->drawing_stage = DRAW_GLOBAL_ANIM_STAGE_3; // do not use global animation mouse pointer when reloading artwork if (global.anim_status != GAME_MODE_LOADING) @@ -929,6 +1033,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part viewport_y = DY; viewport_width = DXSIZE; viewport_height = DYSIZE; + + part->class_playfield_or_door = TRUE; } else if (part->control_info.class == get_hash_from_key("door_2")) { @@ -946,6 +1052,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part viewport_width = VXSIZE; viewport_height = VYSIZE; } + + part->class_playfield_or_door = TRUE; } else // default: "playfield" { @@ -953,6 +1061,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part viewport_y = REAL_SY; viewport_width = FULL_SXSIZE; viewport_height = FULL_SYSIZE; + + part->class_playfield_or_door = TRUE; } if (viewport_x != part->viewport_x || @@ -1038,6 +1148,13 @@ static void StopGlobalAnimMusic(struct GlobalAnimPartControlInfo *part) if (music == MUS_UNDEFINED) return; + char *anim_music = getMusicInfoEntryFilename(music); + char *curr_music = getCurrentlyPlayingMusicFilename(); + + // do not stop music if global anim music differs from current music + if (!strEqual(curr_music, anim_music)) + return; + StopMusic(); #if 0 @@ -1087,6 +1204,7 @@ static void PlayGlobalAnimSoundIfLoop(struct GlobalAnimPartControlInfo *part) static boolean checkGlobalAnimEvent(int anim_event, int mask) { int mask_anim_only = mask & ~ANIM_EVENT_PART_MASK; + int mask_ce_only = mask & ~ANIM_EVENT_PAGE_MASK; if (mask & ANIM_EVENT_ANY) return (anim_event & ANIM_EVENT_ANY); @@ -1094,6 +1212,9 @@ static boolean checkGlobalAnimEvent(int anim_event, int mask) return (anim_event & ANIM_EVENT_SELF); else if (mask & ANIM_EVENT_UNCLICK_ANY) return (anim_event & ANIM_EVENT_UNCLICK_ANY); + else if (mask & ANIM_EVENT_CE_CHANGE) + return (anim_event == mask || + anim_event == mask_ce_only); else return (anim_event == mask || anim_event == mask_anim_only); @@ -1102,67 +1223,116 @@ static boolean checkGlobalAnimEvent(int anim_event, int mask) static boolean isClickablePart(struct GlobalAnimPartControlInfo *part, int mask) { struct GraphicInfo *c = &part->control_info; - int num_init_events = GetGlobalAnimEventValueCount(c->init_event); - int num_anim_events = GetGlobalAnimEventValueCount(c->anim_event); int i; - for (i = 0; i < num_init_events; i++) + if (part->init_event_state) { - int init_event = GetGlobalAnimEventValue(c->init_event, i); + int num_init_events = GetGlobalAnimEventValueCount(c->init_event); - if (checkGlobalAnimEvent(init_event, mask)) - return TRUE; - } + for (i = 0; i < num_init_events; i++) + { + int init_event = GetGlobalAnimEventValue(c->init_event, i); - for (i = 0; i < num_anim_events; i++) + if (checkGlobalAnimEvent(init_event, mask)) + return TRUE; + } + } + else if (part->anim_event_state) { - int anim_event = GetGlobalAnimEventValue(c->anim_event, i); + int num_anim_events = GetGlobalAnimEventValueCount(c->anim_event); + + for (i = 0; i < num_anim_events; i++) + { + int anim_event = GetGlobalAnimEventValue(c->anim_event, i); - if (checkGlobalAnimEvent(anim_event, mask)) - return TRUE; + if (checkGlobalAnimEvent(anim_event, mask)) + return TRUE; + } } return FALSE; } -static boolean isClickedPart(struct GlobalAnimPartControlInfo *part, - int mx, int my, boolean clicked) +static boolean isInsidePartStacked(struct GlobalAnimPartControlInfo *part, + int mx, int my) { struct GraphicInfo *g = &part->graphic_info; + struct GraphicInfo *c = &part->control_info; int part_x = part->viewport_x + part->x; int part_y = part->viewport_y + part->y; int part_width = g->width; int part_height = g->height; + int x, y; + + for (y = 0; y < c->stacked_yfactor; y++) + { + for (x = 0; x < c->stacked_xfactor; x++) + { + int part_stacked_x = part_x + x * (part_width + c->stacked_xoffset); + int part_stacked_y = part_y + y * (part_height + c->stacked_yoffset); + + if (mx >= part_stacked_x && + mx < part_stacked_x + part_width && + my >= part_stacked_y && + my < part_stacked_y + part_height) + return TRUE; + } + } + return FALSE; +} + +static boolean isClickedPart(struct GlobalAnimPartControlInfo *part, + int mx, int my, boolean clicked) +{ // check if mouse click was detected at all if (!clicked) return FALSE; - // check if mouse click is inside the animation part's viewport + // check if mouse click is outside the animation part's viewport if (mx < part->viewport_x || mx >= part->viewport_x + part->viewport_width || my < part->viewport_y || my >= part->viewport_y + part->viewport_height) return FALSE; - // check if mouse click is inside the animation part's graphic - if (mx < part_x || - mx >= part_x + part_width || - my < part_y || - my >= part_y + part_height) - return FALSE; + // check if mouse click is inside the animation part's (stacked) graphic + if (isInsidePartStacked(part, mx, my)) + return TRUE; - return TRUE; + return FALSE; } static boolean clickBlocked(struct GlobalAnimPartControlInfo *part) { - return (part->control_info.style & STYLE_BLOCK ? TRUE : FALSE); + return ((part->control_info.style & STYLE_BLOCK) ? TRUE : FALSE); } static boolean clickConsumed(struct GlobalAnimPartControlInfo *part) { - return (part->control_info.style & STYLE_PASSTHROUGH ? FALSE : TRUE); + return ((part->control_info.style & STYLE_PASSTHROUGH) ? FALSE : TRUE); +} + +static void SetGlobalAnimPartTileXY(struct GlobalAnimPartControlInfo *part) +{ + // calculate playfield position (with scrolling) for related CE tile + // (do not use FX/FY, which are incorrect during envelope requests) + int FX0 = 2 * TILEX_VAR; // same as FX during DRAW_TO_FIELDBUFFER + int FY0 = 2 * TILEY_VAR; // same as FY during DRAW_TO_FIELDBUFFER + int fx = getFieldbufferOffsetX_RND(ScreenMovDir, ScreenGfxPos); + int fy = getFieldbufferOffsetY_RND(ScreenMovDir, ScreenGfxPos); + int sx = FX0 + SCREENX(part->tile_x) * TILEX_VAR; + int sy = FY0 + SCREENY(part->tile_y) * TILEY_VAR; + int cx = SX - REAL_SX; + int cy = SY - REAL_SY; + int x = sx - fx + cx; + int y = sy - fy + cy; + + part->tile_xoffset += part->step_xoffset; + part->tile_yoffset += part->step_yoffset; + + part->x = x + part->tile_xoffset; + part->y = y + part->tile_yoffset; } static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part, @@ -1190,7 +1360,7 @@ static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part, { struct GlobalAnimPartControlInfo *part2 = &anim2->part[part2_nr]; - if (!(part2->state & ANIM_STATE_RUNNING)) + if (!(part2->state & (ANIM_STATE_RUNNING | ANIM_STATE_WAITING))) continue; if (isClickablePart(part2, mask)) @@ -1232,6 +1402,67 @@ static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part, } } +static void InitGlobalAnim_Triggered_ByCustomElement(int nr, int page, + int x, int y, + int trigger_x, + int trigger_y) +{ + struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[GAME_MODE_PLAYING]; + + int event_value = ANIM_EVENT_CE_CHANGE; + int event_bits = (nr << ANIM_EVENT_CE_BIT) | (page << ANIM_EVENT_PAGE_BIT); + int mask = event_value | event_bits; + int anim2_nr; + + for (anim2_nr = 0; anim2_nr < ctrl->num_anims; anim2_nr++) + { + struct GlobalAnimMainControlInfo *anim2 = &ctrl->anim[anim2_nr]; + int part2_nr; + + for (part2_nr = 0; part2_nr < anim2->num_parts_all; part2_nr++) + { + struct GlobalAnimPartControlInfo *part2 = &anim2->part[part2_nr]; + + if (!(part2->state & (ANIM_STATE_RUNNING | ANIM_STATE_WAITING))) + continue; + + if (isClickablePart(part2, mask) && !part2->triggered) + { + struct GraphicInfo *c = &part2->control_info; + + if (c->position == POS_CE || + c->position == POS_CE_TRIGGER) + { + // store CE tile and offset position to handle scrolling + part2->tile_x = (c->position == POS_CE_TRIGGER ? trigger_x : x); + part2->tile_y = (c->position == POS_CE_TRIGGER ? trigger_y : y); + part2->tile_xoffset = c->x; + part2->tile_yoffset = c->y; + + // set resulting animation position relative to CE tile position + // (but only for ".init_event", not ".anim_event" type events) + if (part2->init_event_state) + SetGlobalAnimPartTileXY(part2); + + // restart animation (by using current sync frame) + part2->initial_anim_sync_frame = anim_sync_frame; + } + + part2->triggered = TRUE; + + // do not trigger any other animation if CE change event was consumed + if (c->style == STYLE_CONSUME_CE_EVENT) + return; + +#if 0 + Debug("anim:InitGlobalAnim_Triggered_ByCustomElement", + "%d.%d TRIGGERED BY CE %d", anim2_nr, part2_nr, nr + 1); +#endif + } + } + } +} + static void HandleGlobalAnimDelay(struct GlobalAnimPartControlInfo *part, int delay_type, char *info_text) { @@ -1270,6 +1501,11 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part, struct GraphicInfo *g = &part->graphic_info; struct GraphicInfo *c = &part->control_info; boolean viewport_changed = SetGlobalAnimPart_Viewport(part); + int alpha = (g->alpha != -1 ? g->alpha : SDL_ALPHA_OPAQUE); + + // if game is paused, also pause playfield and door animations + if (isPausedOnPlayfieldOrDoor(part)) + return state; if (viewport_changed) state |= ANIM_STATE_RESTART; @@ -1295,12 +1531,25 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part, part->anim_event_state = (c->anim_event != ANIM_EVENT_UNDEFINED); part->initial_anim_sync_frame = - (g->anim_global_sync ? 0 : anim_sync_frame + part->init_delay_counter); + (g->anim_global_sync || g->anim_global_anim_sync ? 0 : + anim_sync_frame + part->init_delay_counter); // do not re-initialize random animation frame after fade-in if (part->anim_random_frame == -1) part->anim_random_frame = GetSimpleRandom(g->anim_frames); + if (c->fade_mode & FADE_MODE_FADE) + { + // when fading in screen, first frame is 100 % transparent or opaque + part->fade_delay_counter = c->fade_delay + 1; + part->fade_alpha = (c->fade_mode == FADE_MODE_FADE_IN ? 0 : alpha); + } + else + { + part->fade_delay_counter = 0; + part->fade_alpha = -1; + } + if (c->direction & MV_HORIZONTAL) { int pos_bottom = part->viewport_height - g->height; @@ -1403,7 +1652,7 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part, part->init_event_state) { if (part->initial_anim_sync_frame > 0) - part->initial_anim_sync_frame -= part->init_delay_counter - 1; + part->initial_anim_sync_frame = anim_sync_frame; part->init_delay_counter = 1; part->init_event_state = FALSE; @@ -1432,9 +1681,13 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part, HandleGlobalAnimDelay(part, ANIM_DELAY_INIT, "START [INIT_DELAY]"); HandleGlobalAnimEvent(part, ANIM_EVENT_START, "START [ANIM]"); - } - return ANIM_STATE_WAITING; + // continue with state ANIM_STATE_RUNNING (set below) + } + else + { + return ANIM_STATE_WAITING; + } } if (part->init_event_state) @@ -1510,11 +1763,18 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part, return ANIM_STATE_WAITING; } + if (part->fade_delay_counter > 0) + { + part->fade_delay_counter--; + part->fade_alpha = alpha * (c->fade_mode == FADE_MODE_FADE_IN ? + c->fade_delay - part->fade_delay_counter : + part->fade_delay_counter) / c->fade_delay; + } + // special case to prevent expiring loop sounds when playing PlayGlobalAnimSoundIfLoop(part); - if (!DelayReachedExt(&part->step_delay, part->step_delay_value, - anim_sync_frame)) + if (!DelayReachedExt(&part->step_delay, anim_sync_frame)) return ANIM_STATE_RUNNING; #if 0 @@ -1529,8 +1789,16 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part, } #endif - part->x += part->step_xoffset; - part->y += part->step_yoffset; + if (c->position == POS_CE || + c->position == POS_CE_TRIGGER) + { + SetGlobalAnimPartTileXY(part); + } + else + { + part->x += part->step_xoffset; + part->y += part->step_yoffset; + } anim->last_x = part->x; anim->last_y = part->y; @@ -1653,9 +1921,13 @@ static void HandleGlobalAnim_Main(struct GlobalAnimMainControlInfo *anim, for (i = 0; i < num_parts; i++) anim->part[i].state = ANIM_STATE_INACTIVE; - // ... then set current animation parts to "running" + // ... then set current animation part to "running" ... part->state = ANIM_STATE_RUNNING; + // ... unless it is waiting for an initial event + if (part->init_event_state) + part->state = ANIM_STATE_WAITING; + anim->state = HandleGlobalAnim_Part(part, anim->state); if (anim->state & ANIM_STATE_RESTART) @@ -1757,7 +2029,10 @@ static boolean DoGlobalAnim_EventAction(struct GlobalAnimPartControlInfo *part) if (event_action == ANIM_EVENT_ACTION_NONE) return FALSE; - PushUserEvent(USEREVENT_ANIM_EVENT_ACTION, event_action, 0); + if (event_action < MAX_IMAGE_FILES) + PushUserEvent(USEREVENT_ANIM_EVENT_ACTION, event_action, 0); + else + OpenURLFromHash(anim_url_hash, event_action); // check if further actions are allowed to be executed if (part->control_info.style & STYLE_MULTIPLE_ACTIONS) @@ -1768,29 +2043,17 @@ static boolean DoGlobalAnim_EventAction(struct GlobalAnimPartControlInfo *part) static void InitGlobalAnim_Clickable(void) { - int mode_nr; + int i; - for (mode_nr = 0; mode_nr < NUM_GAME_MODES; mode_nr++) + for (i = 0; i < num_global_anim_list; i++) { - struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[mode_nr]; - int anim_nr; - - for (anim_nr = 0; anim_nr < ctrl->num_anims; anim_nr++) - { - struct GlobalAnimMainControlInfo *anim = &ctrl->anim[anim_nr]; - int part_nr; - - for (part_nr = 0; part_nr < anim->num_parts_all; part_nr++) - { - struct GlobalAnimPartControlInfo *part = &anim->part[part_nr]; + struct GlobalAnimPartControlInfo *part = global_anim_list[i]; - if (part->triggered) - part->clicked = TRUE; + if (part->triggered) + part->clicked = TRUE; - part->triggered = FALSE; - part->clickable = FALSE; - } - } + part->triggered = FALSE; + part->clickable = FALSE; } } @@ -1804,103 +2067,93 @@ static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event) boolean anything_clicked = FALSE; boolean any_part_clicked = FALSE; boolean any_event_action = FALSE; - int mode_nr; int i; - // check game modes in reverse draw order (to stop when clicked) - for (mode_nr = NUM_GAME_MODES - 1; mode_nr >= 0; mode_nr--) + // check animation parts in reverse draw order (to stop when clicked) + for (i = num_global_anim_list - 1; i >= 0; i--) { - struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[mode_nr]; - int anim_nr; + struct GlobalAnimPartControlInfo *part = global_anim_list[i]; - // check animations in reverse draw order (to stop when clicked) - for (anim_nr = ctrl->num_anims - 1; anim_nr >= 0; anim_nr--) - { - struct GlobalAnimMainControlInfo *anim = &ctrl->anim[anim_nr]; - int part_nr; - - // check animation parts in reverse draw order (to stop when clicked) - for (part_nr = anim->num_parts_all - 1; part_nr >= 0; part_nr--) - { - struct GlobalAnimPartControlInfo *part = &anim->part[part_nr]; + // if request dialog is active, only handle pointer-style animations + if (game.request_active && + part->control_info.class != get_hash_from_key("pointer")) + continue; - if (clicked_event == ANIM_CLICKED_RESET) - { - part->clicked = FALSE; + if (clicked_event == ANIM_CLICKED_RESET) + { + part->clicked = FALSE; - continue; - } + continue; + } - if (!part->clickable) - continue; + if (!part->clickable) + continue; - if (!(part->state & ANIM_STATE_RUNNING)) - continue; + if (!(part->state & ANIM_STATE_RUNNING)) + continue; - // always handle "any" click events (clicking anywhere on screen) ... - if (clicked_event == ANIM_CLICKED_PRESSED && - isClickablePart(part, ANIM_EVENT_ANY)) - { + // always handle "any" click events (clicking anywhere on screen) ... + if (clicked_event == ANIM_CLICKED_PRESSED && + isClickablePart(part, ANIM_EVENT_ANY)) + { #if DEBUG_ANIM_EVENTS - Debug("anim:InitGlobalAnim_Clicked", "%d.%d TRIGGERED BY ANY", - part->old_anim_nr + 1, part->old_nr + 1); + Debug("anim:InitGlobalAnim_Clicked", "%d.%d TRIGGERED BY ANY", + part->old_anim_nr + 1, part->old_nr + 1); #endif - anything_clicked = part->clicked = TRUE; - click_consumed |= clickConsumed(part); - } + anything_clicked = part->clicked = TRUE; + click_consumed |= clickConsumed(part); + } - // always handle "unclick:any" events (releasing anywhere on screen) ... - if (clicked_event == ANIM_CLICKED_RELEASED && - isClickablePart(part, ANIM_EVENT_UNCLICK_ANY)) - { + // always handle "unclick:any" events (releasing anywhere on screen) ... + if (clicked_event == ANIM_CLICKED_RELEASED && + isClickablePart(part, ANIM_EVENT_UNCLICK_ANY)) + { #if DEBUG_ANIM_EVENTS - Debug("anim:InitGlobalAnim_Clicked", "%d.%d TRIGGERED BY UNCLICK:ANY", - part->old_anim_nr + 1, part->old_nr + 1); + Debug("anim:InitGlobalAnim_Clicked", "%d.%d TRIGGERED BY UNCLICK:ANY", + part->old_anim_nr + 1, part->old_nr + 1); #endif - anything_clicked = part->clicked = TRUE; - click_consumed |= clickConsumed(part); - } + anything_clicked = part->clicked = TRUE; + click_consumed |= clickConsumed(part); + } - // ... but only handle the first (topmost) clickable animation - if (any_part_clicked) - continue; + // ... but only handle the first (topmost) clickable animation + if (any_part_clicked) + continue; - if (clicked_event == ANIM_CLICKED_PRESSED && - isClickedPart(part, mx, my, TRUE)) - { + if (clicked_event == ANIM_CLICKED_PRESSED && + isClickedPart(part, mx, my, TRUE)) + { #if 0 - Debug("anim:InitGlobalAnim_Clicked", "%d.%d CLICKED [%d]", - anim_nr, part_nr, part->control_info.anim_event_action); + Debug("anim:InitGlobalAnim_Clicked", "%d.%d CLICKED [%d]", + anim_nr, part_nr, part->control_info.anim_event_action); #endif - // after executing event action, ignore any further actions - if (!any_event_action && DoGlobalAnim_EventAction(part)) - any_event_action = TRUE; + // after executing event action, ignore any further actions + if (!any_event_action && DoGlobalAnim_EventAction(part)) + any_event_action = TRUE; - // determine if mouse clicks should be blocked from other animations - any_part_clicked |= clickConsumed(part); + // determine if mouse clicks should be blocked from other animations + any_part_clicked |= clickConsumed(part); - if (isClickablePart(part, ANIM_EVENT_SELF)) - { + if (isClickablePart(part, ANIM_EVENT_SELF)) + { #if DEBUG_ANIM_EVENTS - Debug("anim:InitGlobalAnim_Clicked", "%d.%d TRIGGERED BY SELF", - part->old_anim_nr + 1, part->old_nr + 1); + Debug("anim:InitGlobalAnim_Clicked", "%d.%d TRIGGERED BY SELF", + part->old_anim_nr + 1, part->old_nr + 1); #endif - anything_clicked = part->clicked = TRUE; - click_consumed |= clickConsumed(part); - } + anything_clicked = part->clicked = TRUE; + click_consumed |= clickConsumed(part); + } - // determine if mouse clicks should be blocked by this animation - click_consumed |= clickBlocked(part); + // determine if mouse clicks should be blocked by this animation + click_consumed |= clickBlocked(part); - // check if this click is defined to trigger other animations - InitGlobalAnim_Triggered(part, &click_consumed, &any_event_action, - ANIM_EVENT_CLICK, "CLICK"); - } - } + // check if this click is defined to trigger other animations + InitGlobalAnim_Triggered(part, &click_consumed, &any_event_action, + ANIM_EVENT_CLICK, "CLICK"); } } @@ -1930,6 +2183,23 @@ static void ResetGlobalAnim_Clicked(void) InitGlobalAnim_Clicked(-1, -1, ANIM_CLICKED_RESET); } +void RestartGlobalAnimsByStatus(int status) +{ + int anim_status_last = global.anim_status; + + global.anim_status = status; + + // force restarting global animations by changed global animation status + DrawGlobalAnimationsExt(DRAW_TO_SCREEN, DRAW_GLOBAL_ANIM_STAGE_RESTART); + + global.anim_status = anim_status_last; +} + +void SetAnimStatusBeforeFading(int status) +{ + anim_status_last_before_fading = status; +} + boolean HandleGlobalAnimClicks(int mx, int my, int button, boolean force_click) { static boolean click_consumed = FALSE; @@ -1960,3 +2230,19 @@ boolean HandleGlobalAnimClicks(int mx, int my, int button, boolean force_click) return click_consumed_current; } + +int getGlobalAnimSyncFrame(void) +{ + return anim_sync_frame; +} + +void HandleGlobalAnimEventByElementChange(int element, int page, int x, int y, + int trigger_x, int trigger_y) +{ + if (!IS_CUSTOM_ELEMENT(element)) + return; + + // custom element stored as 0 to 255, change page stored as 1 to 32 + InitGlobalAnim_Triggered_ByCustomElement(element - EL_CUSTOM_START, page + 1, + x, y, trigger_x, trigger_y); +} diff --git a/src/anim.h b/src/anim.h index 5c5b7895..274cade9 100644 --- a/src/anim.h +++ b/src/anim.h @@ -15,9 +15,16 @@ int getAnimationFrame(int, int, int, int, int); +void InitGlobalAnimEventsForCustomElements(void); void InitGlobalAnimations(void); void DrawGlobalAnimations(int, int); +void RestartGlobalAnimsByStatus(int); +void SetAnimStatusBeforeFading(int); + boolean HandleGlobalAnimClicks(int, int, int, boolean); +void HandleGlobalAnimEventByElementChange(int, int, int, int, int, int); + +int getGlobalAnimSyncFrame(void); #endif diff --git a/src/api.c b/src/api.c new file mode 100644 index 00000000..3e2b387e --- /dev/null +++ b/src/api.c @@ -0,0 +1,1269 @@ +// ============================================================================ +// Rocks'n'Diamonds - McDuffin Strikes Back! +// ---------------------------------------------------------------------------- +// (c) 1995-2022 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// https://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// api.c +// ============================================================================ + +#include "libgame/libgame.h" + +#include "api.h" +#include "main.h" +#include "files.h" +#include "config.h" + + +// ============================================================================ +// generic helper functions +// ============================================================================ + +static void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data, + char *error) +{ +#if defined(PLATFORM_EMSCRIPTEN) + // threads currently not fully supported by Emscripten/SDL and some browsers + function(data); +#else + SDL_Thread *thread = SDL_CreateThread(function, name, data); + + if (thread != NULL) + SDL_DetachThread(thread); + else + Error("Cannot create thread to %s!", error); + + // nasty kludge to lower probability of intermingled thread error messages + Delay(1); +#endif +} + +static char *getPasswordJSON(char *password) +{ + static char password_json[MAX_FILENAME_LEN] = ""; + static boolean initialized = FALSE; + + if (!initialized) + { + if (password != NULL && + !strEqual(password, "") && + !strEqual(password, UNDEFINED_PASSWORD)) + snprintf(password_json, MAX_FILENAME_LEN, + " \"password\": \"%s\",\n", + setup.api_server_password); + + initialized = TRUE; + } + + return password_json; +} + +static char *getFileBase64(char *filename) +{ + struct stat file_status; + + if (stat(filename, &file_status) != 0) + { + Error("cannot stat file '%s'", filename); + + return NULL; + } + + int buffer_size = file_status.st_size; + byte *buffer = checked_malloc(buffer_size); + FILE *file; + int i; + + if (!(file = fopen(filename, MODE_READ))) + { + Error("cannot open file '%s'", filename); + + checked_free(buffer); + + return NULL; + } + + for (i = 0; i < buffer_size; i++) + { + int c = fgetc(file); + + if (c == EOF) + { + Error("cannot read from input file '%s'", filename); + + fclose(file); + checked_free(buffer); + + return NULL; + } + + buffer[i] = (byte)c; + } + + fclose(file); + + int buffer_encoded_size = base64_encoded_size(buffer_size); + char *buffer_encoded = checked_malloc(buffer_encoded_size); + + base64_encode(buffer_encoded, buffer, buffer_size); + + checked_free(buffer); + + return buffer_encoded; +} + + +// ============================================================================ +// add score API functions +// ============================================================================ + +struct ApiAddScoreThreadData +{ + int level_nr; + boolean tape_saved; + char *leveldir_subdir; + char *score_tape_filename; + struct ScoreEntry score_entry; +}; + +static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved, + char *score_tape_filename) +{ + struct ApiAddScoreThreadData *data = + checked_malloc(sizeof(struct ApiAddScoreThreadData)); + struct ScoreEntry *score_entry = &scores.entry[scores.last_added]; + + if (score_tape_filename == NULL) + score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr); + + data->level_nr = nr; + data->tape_saved = tape_saved; + data->leveldir_subdir = getStringCopy(leveldir_current->subdir); + data->score_tape_filename = getStringCopy(score_tape_filename); + data->score_entry = *score_entry; + + return data; +} + +static void FreeThreadData_ApiAddScore(void *data_raw) +{ + struct ApiAddScoreThreadData *data = data_raw; + + checked_free(data->leveldir_subdir); + checked_free(data->score_tape_filename); + checked_free(data); +} + +static boolean SetRequest_ApiAddScore(struct HttpRequest *request, + void *data_raw) +{ + struct ApiAddScoreThreadData *data = data_raw; + struct ScoreEntry *score_entry = &data->score_entry; + char *score_tape_filename = data->score_tape_filename; + boolean tape_saved = data->tape_saved; + int level_nr = data->level_nr; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_ADD; + + char *tape_base64 = getFileBase64(score_tape_filename); + + if (tape_base64 == NULL) + { + Error("loading and base64 encoding score tape file failed"); + + return FALSE; + } + + char *player_name_raw = score_entry->name; + char *player_uuid_raw = setup.player_uuid; + + if (options.player_name != NULL && global.autoplay_leveldir != NULL) + { + player_name_raw = options.player_name; + player_uuid_raw = ""; + } + + char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); + char *levelset_name = getEscapedJSON(leveldir_current->name); + char *levelset_author = getEscapedJSON(leveldir_current->author); + char *level_name = getEscapedJSON(level.name); + char *level_author = getEscapedJSON(level.author); + char *player_name = getEscapedJSON(player_name_raw); + char *player_uuid = getEscapedJSON(player_uuid_raw); + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"batch_time\": \"%d\",\n" + " \"levelset_identifier\": \"%s\",\n" + " \"levelset_name\": \"%s\",\n" + " \"levelset_author\": \"%s\",\n" + " \"levelset_num_levels\": \"%d\",\n" + " \"levelset_first_level\": \"%d\",\n" + " \"level_nr\": \"%d\",\n" + " \"level_name\": \"%s\",\n" + " \"level_author\": \"%s\",\n" + " \"use_step_counter\": \"%d\",\n" + " \"rate_time_over_score\": \"%d\",\n" + " \"player_name\": \"%s\",\n" + " \"player_uuid\": \"%s\",\n" + " \"score\": \"%d\",\n" + " \"time\": \"%d\",\n" + " \"tape_basename\": \"%s\",\n" + " \"tape_saved\": \"%d\",\n" + " \"tape\": \"%s\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + (int)global.autoplay_time, + levelset_identifier, + levelset_name, + levelset_author, + leveldir_current->levels, + leveldir_current->first_level, + level_nr, + level_name, + level_author, + level.use_step_counter, + level.rate_time_over_score, + player_name, + player_uuid, + score_entry->score, + score_entry->time, + score_entry->tape_basename, + tape_saved, + tape_base64); + + checked_free(tape_base64); + + checked_free(levelset_identifier); + checked_free(levelset_name); + checked_free(levelset_author); + checked_free(level_name); + checked_free(level_author); + checked_free(player_name); + checked_free(player_uuid); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiAddScore(struct HttpResponse *response, + void *data_raw) +{ + server_scores.uploaded = TRUE; +} + +static void HandleFailure_ApiAddScore(void *data_raw) +{ + struct ApiAddScoreThreadData *data = data_raw; + + PrepareScoreTapesForUpload(data->leveldir_subdir); +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiAddScore(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + + HandleFailure_ApiAddScore(data_raw); + } + + FreeThreadData_ApiAddScore(data_raw); +} + +static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + HandleFailure_ApiAddScore(data_raw); + + FreeThreadData_ApiAddScore(data_raw); +} + +static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiAddScore(request, data_raw)) + { + FreeThreadData_ApiAddScore(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiAddScore_Loaded, + Emscripten_ApiAddScore_Failed, + Emscripten_ApiAddScore_Progress); +} + +#else + +static void ApiAddScore_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiAddScore(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + HandleFailure_ApiAddScore(data_raw); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + HandleFailure_ApiAddScore(data_raw); + + return; + } + + HandleResponse_ApiAddScore(response, data_raw); +} + +static void ApiAddScore_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiAddScore_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiAddScore(data_raw); +} +#endif + +static int ApiAddScoreThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiAddScore_HttpRequest(request, data_raw); +#else + ApiAddScore_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +void ApiAddScoreAsThread(int nr, boolean tape_saved, char *score_tape_filename) +{ + struct ApiAddScoreThreadData *data = + CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename); + + ExecuteAsThread(ApiAddScoreThread, + "ApiAddScore", data, + "upload score to server"); +} + + +// ============================================================================ +// get score API functions +// ============================================================================ + +struct ApiGetScoreThreadData +{ + int level_nr; + char *score_cache_filename; +}; + +static void *CreateThreadData_ApiGetScore(int nr) +{ + struct ApiGetScoreThreadData *data = + checked_malloc(sizeof(struct ApiGetScoreThreadData)); + char *score_cache_filename = getScoreCacheFilename(nr); + + data->level_nr = nr; + data->score_cache_filename = getStringCopy(score_cache_filename); + + return data; +} + +static void FreeThreadData_ApiGetScore(void *data_raw) +{ + struct ApiGetScoreThreadData *data = data_raw; + + checked_free(data->score_cache_filename); + checked_free(data); +} + +static boolean SetRequest_ApiGetScore(struct HttpRequest *request, + void *data_raw) +{ + struct ApiGetScoreThreadData *data = data_raw; + int level_nr = data->level_nr; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_GET; + + char *levelset_identifier = getEscapedJSON(leveldir_current->identifier); + char *levelset_name = getEscapedJSON(leveldir_current->name); + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"levelset_identifier\": \"%s\",\n" + " \"levelset_name\": \"%s\",\n" + " \"level_nr\": \"%d\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + levelset_identifier, + levelset_name, + level_nr); + + checked_free(levelset_identifier); + checked_free(levelset_name); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiGetScore(struct HttpResponse *response, + void *data_raw) +{ + struct ApiGetScoreThreadData *data = data_raw; + + if (response->body_size == 0) + { + // no scores available for this level + + return; + } + + ConvertHttpResponseBodyToClientEncoding(response); + + char *filename = data->score_cache_filename; + FILE *file; + int i; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreCacheDirectory(levelset.identifier); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot save score cache file '%s'", filename); + + return; + } + + for (i = 0; i < response->body_size; i++) + fputc(response->body[i], file); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); + + server_scores.updated = TRUE; +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiGetScore(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + } + + FreeThreadData_ApiGetScore(data_raw); +} + +static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + FreeThreadData_ApiGetScore(data_raw); +} + +static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiGetScore(request, data_raw)) + { + FreeThreadData_ApiGetScore(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiGetScore_Loaded, + Emscripten_ApiGetScore_Failed, + Emscripten_ApiGetScore_Progress); +} + +#else + +static void ApiGetScore_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiGetScore(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + // do not show error message if no scores found for this level set + if (response->status_code == 404) + return; + + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + return; + } + + HandleResponse_ApiGetScore(response, data_raw); +} + +static void ApiGetScore_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiGetScore_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiGetScore(data_raw); +} +#endif + +static int ApiGetScoreThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiGetScore_HttpRequest(request, data_raw); +#else + ApiGetScore_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +void ApiGetScoreAsThread(int nr) +{ + struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr); + + ExecuteAsThread(ApiGetScoreThread, + "ApiGetScore", data, + "download scores from server"); +} + + +// ============================================================================ +// get score tape API functions +// ============================================================================ + +struct ApiGetScoreTapeThreadData +{ + int level_nr; + int score_id; + char *score_tape_filename; +}; + +static void *CreateThreadData_ApiGetScoreTape(int nr, int id, + char *score_tape_basename) +{ + struct ApiGetScoreTapeThreadData *data = + checked_malloc(sizeof(struct ApiGetScoreTapeThreadData)); + char *score_tape_filename = getScoreCacheTapeFilename(score_tape_basename, nr); + + data->level_nr = nr; + data->score_id = id; + data->score_tape_filename = getStringCopy(score_tape_filename); + + return data; +} + +static void FreeThreadData_ApiGetScoreTape(void *data_raw) +{ + struct ApiGetScoreTapeThreadData *data = data_raw; + + checked_free(data->score_tape_filename); + checked_free(data); +} + +static boolean SetRequest_ApiGetScoreTape(struct HttpRequest *request, + void *data_raw) +{ + struct ApiGetScoreTapeThreadData *data = data_raw; + int score_id = data->score_id; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_GETTAPE; + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"id\": \"%d\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + score_id); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiGetScoreTape(struct HttpResponse *response, + void *data_raw) +{ + struct ApiGetScoreTapeThreadData *data = data_raw; + + if (response->body_size == 0) + { + // no score tape available for this level + + return; + } + + // (do not convert HTTP response body, as it contains binary data here) + + int level_nr = data->level_nr; + char *filename = data->score_tape_filename; + FILE *file; + int i; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreCacheTapeDirectory(levelset.identifier, level_nr); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot save score tape file '%s'", filename); + + return; + } + + for (i = 0; i < response->body_size; i++) + fputc(response->body[i], file); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); + + server_scores.tape_downloaded = TRUE; +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiGetScoreTape_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiGetScoreTape(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + } + + FreeThreadData_ApiGetScoreTape(data_raw); +} + +static void Emscripten_ApiGetScoreTape_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + FreeThreadData_ApiGetScoreTape(data_raw); +} + +static void Emscripten_ApiGetScoreTape_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiGetScoreTape_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiGetScoreTape(request, data_raw)) + { + FreeThreadData_ApiGetScoreTape(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiGetScoreTape_Loaded, + Emscripten_ApiGetScoreTape_Failed, + Emscripten_ApiGetScoreTape_Progress); +} + +#else + +static void ApiGetScoreTape_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiGetScoreTape(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + // do not show error message if no scores found for this level set + if (response->status_code == 404) + return; + + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + return; + } + + HandleResponse_ApiGetScoreTape(response, data_raw); +} + +static void ApiGetScoreTape_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiGetScoreTape_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiGetScoreTape(data_raw); +} +#endif + +static int ApiGetScoreTapeThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiGetScoreTape_HttpRequest(request, data_raw); +#else + ApiGetScoreTape_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +void ApiGetScoreTapeAsThread(int nr, int id, char *score_tape_basename) +{ + struct ApiGetScoreTapeThreadData *data = + CreateThreadData_ApiGetScoreTape(nr, id, score_tape_basename); + + ExecuteAsThread(ApiGetScoreTapeThread, + "ApiGetScoreTape", data, + "download score tape from server"); +} + + +// ============================================================================ +// rename player API functions +// ============================================================================ + +struct ApiRenamePlayerThreadData +{ + char *player_name; + char *player_uuid; +}; + +static void *CreateThreadData_ApiRenamePlayer(void) +{ + struct ApiRenamePlayerThreadData *data = + checked_malloc(sizeof(struct ApiRenamePlayerThreadData)); + + data->player_name = getStringCopy(setup.player_name); + data->player_uuid = getStringCopy(setup.player_uuid); + + return data; +} + +static void FreeThreadData_ApiRenamePlayer(void *data_raw) +{ + struct ApiRenamePlayerThreadData *data = data_raw; + + checked_free(data->player_name); + checked_free(data->player_uuid); + checked_free(data); +} + +static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request, + void *data_raw) +{ + struct ApiRenamePlayerThreadData *data = data_raw; + char *player_name_raw = data->player_name; + char *player_uuid_raw = data->player_uuid; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_RENAME; + + char *player_name = getEscapedJSON(player_name_raw); + char *player_uuid = getEscapedJSON(player_uuid_raw); + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"name\": \"%s\",\n" + " \"uuid\": \"%s\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + player_name, + player_uuid); + + checked_free(player_name); + checked_free(player_uuid); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response, + void *data_raw) +{ + // nothing to do here +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiRenamePlayer_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiRenamePlayer(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + } + + FreeThreadData_ApiRenamePlayer(data_raw); +} + +static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + FreeThreadData_ApiRenamePlayer(data_raw); +} + +static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiRenamePlayer(request, data_raw)) + { + FreeThreadData_ApiRenamePlayer(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiRenamePlayer_Loaded, + Emscripten_ApiRenamePlayer_Failed, + Emscripten_ApiRenamePlayer_Progress); +} + +#else + +static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiRenamePlayer(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + return; + } + + HandleResponse_ApiRenamePlayer(response, data_raw); +} + +static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiRenamePlayer_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiRenamePlayer(data_raw); +} +#endif + +static int ApiRenamePlayerThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw); +#else + ApiRenamePlayer_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +void ApiRenamePlayerAsThread(void) +{ + struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer(); + + ExecuteAsThread(ApiRenamePlayerThread, + "ApiRenamePlayer", data, + "rename player on server"); +} + + +// ============================================================================ +// reset player UUID API functions +// ============================================================================ + +struct ApiResetUUIDThreadData +{ + char *player_name; + char *player_uuid_old; + char *player_uuid_new; +}; + +static void *CreateThreadData_ApiResetUUID(char *uuid_new) +{ + struct ApiResetUUIDThreadData *data = + checked_malloc(sizeof(struct ApiResetUUIDThreadData)); + + data->player_name = getStringCopy(setup.player_name); + data->player_uuid_old = getStringCopy(setup.player_uuid); + data->player_uuid_new = getStringCopy(uuid_new); + + return data; +} + +static void FreeThreadData_ApiResetUUID(void *data_raw) +{ + struct ApiResetUUIDThreadData *data = data_raw; + + checked_free(data->player_name); + checked_free(data->player_uuid_old); + checked_free(data->player_uuid_new); + checked_free(data); +} + +static boolean SetRequest_ApiResetUUID(struct HttpRequest *request, + void *data_raw) +{ + struct ApiResetUUIDThreadData *data = data_raw; + char *player_name_raw = data->player_name; + char *player_uuid_old_raw = data->player_uuid_old; + char *player_uuid_new_raw = data->player_uuid_new; + + request->hostname = setup.api_server_hostname; + request->port = API_SERVER_PORT; + request->method = API_SERVER_METHOD; + request->uri = API_SERVER_URI_RESETUUID; + + char *player_name = getEscapedJSON(player_name_raw); + char *player_uuid_old = getEscapedJSON(player_uuid_old_raw); + char *player_uuid_new = getEscapedJSON(player_uuid_new_raw); + + snprintf(request->body, MAX_HTTP_BODY_SIZE, + "{\n" + "%s" + " \"game_version\": \"%s\",\n" + " \"game_platform\": \"%s\",\n" + " \"name\": \"%s\",\n" + " \"uuid_old\": \"%s\",\n" + " \"uuid_new\": \"%s\"\n" + "}\n", + getPasswordJSON(setup.api_server_password), + getProgramRealVersionString(), + getProgramPlatformString(), + player_name, + player_uuid_old, + player_uuid_new); + + checked_free(player_name); + checked_free(player_uuid_old); + checked_free(player_uuid_new); + + ConvertHttpRequestBodyToServerEncoding(request); + + return TRUE; +} + +static void HandleResponse_ApiResetUUID(struct HttpResponse *response, + void *data_raw) +{ + struct ApiResetUUIDThreadData *data = data_raw; + + // upgrade player UUID in server setup file + setup.player_uuid = getStringCopy(data->player_uuid_new); + setup.player_version = 2; + + SaveSetup_ServerSetup(); +} + +#if defined(PLATFORM_EMSCRIPTEN) +static void Emscripten_ApiResetUUID_Loaded(unsigned handle, void *data_raw, + void *buffer, unsigned int size) +{ + struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size); + + if (response != NULL) + { + HandleResponse_ApiResetUUID(response, data_raw); + + checked_free(response); + } + else + { + Error("server response too large to handle (%d bytes)", size); + } + + FreeThreadData_ApiResetUUID(data_raw); +} + +static void Emscripten_ApiResetUUID_Failed(unsigned handle, void *data_raw, + int code, const char *status) +{ + Error("server failed to handle request: %d %s", code, status); + + FreeThreadData_ApiResetUUID(data_raw); +} + +static void Emscripten_ApiResetUUID_Progress(unsigned handle, void *data_raw, + int bytes, int size) +{ + // nothing to do here +} + +static void Emscripten_ApiResetUUID_HttpRequest(struct HttpRequest *request, + void *data_raw) +{ + if (!SetRequest_ApiResetUUID(request, data_raw)) + { + FreeThreadData_ApiResetUUID(data_raw); + + return; + } + + emscripten_async_wget2_data(request->uri, + request->method, + request->body, + data_raw, + TRUE, + Emscripten_ApiResetUUID_Loaded, + Emscripten_ApiResetUUID_Failed, + Emscripten_ApiResetUUID_Progress); +} + +#else + +static void ApiResetUUID_HttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + if (!SetRequest_ApiResetUUID(request, data_raw)) + return; + + if (!DoHttpRequest(request, response)) + { + Error("HTTP request failed: %s", GetHttpError()); + + return; + } + + if (!HTTP_SUCCESS(response->status_code)) + { + Error("server failed to handle request: %d %s", + response->status_code, + response->status_text); + + return; + } + + HandleResponse_ApiResetUUID(response, data_raw); +} + +static void ApiResetUUID_HttpRequest(struct HttpRequest *request, + struct HttpResponse *response, + void *data_raw) +{ + ApiResetUUID_HttpRequestExt(request, response, data_raw); + + FreeThreadData_ApiResetUUID(data_raw); +} +#endif + +static int ApiResetUUIDThread(void *data_raw) +{ + struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest)); + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + program.api_thread_count++; + +#if defined(PLATFORM_EMSCRIPTEN) + Emscripten_ApiResetUUID_HttpRequest(request, data_raw); +#else + ApiResetUUID_HttpRequest(request, response, data_raw); +#endif + + program.api_thread_count--; + + checked_free(request); + checked_free(response); + + return 0; +} + +void ApiResetUUIDAsThread(char *uuid_new) +{ + struct ApiResetUUIDThreadData *data = CreateThreadData_ApiResetUUID(uuid_new); + + ExecuteAsThread(ApiResetUUIDThread, + "ApiResetUUID", data, + "reset UUID on server"); +} diff --git a/src/api.h b/src/api.h new file mode 100644 index 00000000..4f897421 --- /dev/null +++ b/src/api.h @@ -0,0 +1,21 @@ +// ============================================================================ +// Rocks'n'Diamonds - McDuffin Strikes Back! +// ---------------------------------------------------------------------------- +// (c) 1995-2022 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// https://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// api.h +// ============================================================================ + +#ifndef API_H +#define API_H + +void ApiAddScoreAsThread(int, boolean, char *); +void ApiGetScoreAsThread(int); +void ApiGetScoreTapeAsThread(int, int, char *); +void ApiRenamePlayerAsThread(void); +void ApiResetUUIDAsThread(char *); + +#endif diff --git a/src/conf_gfx.c b/src/conf_gfx.c index 343c2724..62bdab0e 100644 --- a/src/conf_gfx.c +++ b/src/conf_gfx.c @@ -41,6 +41,7 @@ struct ConfigTypeInfo image_config_suffix[] = { ".delay", "1", TYPE_INTEGER }, { ".anim_mode", ARG_UNDEFINED, TYPE_STRING }, { ".global_sync", "false", TYPE_BOOLEAN }, + { ".global_anim_sync", "false", TYPE_BOOLEAN }, { ".crumbled_like", ARG_UNDEFINED, TYPE_ELEMENT }, { ".diggable_like", ARG_UNDEFINED, TYPE_ELEMENT }, { ".border_size", ARG_UNDEFINED, TYPE_INTEGER }, @@ -81,10 +82,15 @@ struct ConfigTypeInfo image_config_suffix[] = { ".sort_priority", ARG_UNDEFINED, TYPE_INTEGER }, { ".class", ARG_UNDEFINED, TYPE_STRING }, { ".style", ARG_UNDEFINED, TYPE_STRING }, + { ".alpha", ARG_UNDEFINED, TYPE_INTEGER }, { ".active_xoffset", "0", TYPE_INTEGER }, { ".active_yoffset", "0", TYPE_INTEGER }, { ".pressed_xoffset", "0", TYPE_INTEGER }, { ".pressed_yoffset", "0", TYPE_INTEGER }, + { ".stacked_xfactor", "1", TYPE_INTEGER }, + { ".stacked_yfactor", "1", TYPE_INTEGER }, + { ".stacked_xoffset", "0", TYPE_INTEGER }, + { ".stacked_yoffset", "0", TYPE_INTEGER }, { NULL, NULL, 0 } }; @@ -153,6 +159,11 @@ struct ConfigInfo image_config[] = { "bd_diamond.falling.ypos", "10" }, { "bd_diamond.falling.frames", "2" }, { "bd_diamond.falling.delay", "4" }, + { "bd_diamond.collecting", "RocksCollect.png" }, + { "bd_diamond.collecting.xpos", "0" }, + { "bd_diamond.collecting.ypos", "8" }, + { "bd_diamond.collecting.frames", "7" }, + { "bd_diamond.collecting.anim_mode", "linear" }, { "bd_magic_wall", "RocksElements.png" }, { "bd_magic_wall.xpos", "12" }, @@ -195,8 +206,7 @@ struct ConfigInfo image_config[] = { "bd_amoeba.xpos", "8" }, { "bd_amoeba.ypos", "6" }, { "bd_amoeba.frames", "4" }, - { "bd_amoeba.delay", "1000000" }, - { "bd_amoeba.anim_mode", "random" }, + { "bd_amoeba.anim_mode", "random_static" }, { "bd_amoeba.EDITOR", "RocksElements.png" }, { "bd_amoeba.EDITOR.xpos", "8" }, { "bd_amoeba.EDITOR.ypos", "7" }, @@ -979,11 +989,10 @@ struct ConfigInfo image_config[] = { "emerald.falling.ypos", "0" }, { "emerald.falling.frames", "2" }, { "emerald.falling.delay", "4" }, - { "emerald.collecting", "RocksMore.png" }, - { "emerald.collecting.xpos", "3" }, - { "emerald.collecting.ypos", "2" }, - { "emerald.collecting.frames", "3" }, - { "emerald.collecting.delay", "2" }, + { "emerald.collecting", "RocksCollect.png" }, + { "emerald.collecting.xpos", "0" }, + { "emerald.collecting.ypos", "0" }, + { "emerald.collecting.frames", "7" }, { "emerald.collecting.anim_mode", "linear" }, { "diamond", "RocksElements.png" }, @@ -1000,11 +1009,10 @@ struct ConfigInfo image_config[] = { "diamond.falling.ypos", "0" }, { "diamond.falling.frames", "2" }, { "diamond.falling.delay", "4" }, - { "diamond.collecting", "RocksMore.png" }, - { "diamond.collecting.xpos", "7" }, - { "diamond.collecting.ypos", "2" }, - { "diamond.collecting.frames", "3" }, - { "diamond.collecting.delay", "2" }, + { "diamond.collecting", "RocksCollect.png" }, + { "diamond.collecting.xpos", "0" }, + { "diamond.collecting.ypos", "1" }, + { "diamond.collecting.frames", "7" }, { "diamond.collecting.anim_mode", "linear" }, { "bomb", "RocksElements.png" }, @@ -1033,6 +1041,11 @@ struct ConfigInfo image_config[] = { "dynamite.active.frames", "7" }, { "dynamite.active.delay", "12" }, { "dynamite.active.anim_mode", "linear" }, + { "dynamite.collecting", "RocksCollect.png" }, + { "dynamite.collecting.xpos", "0" }, + { "dynamite.collecting.ypos", "7" }, + { "dynamite.collecting.frames", "7" }, + { "dynamite.collecting.anim_mode", "linear" }, { "em_dynamite", "RocksEMC.png" }, { "em_dynamite.xpos", "0" }, @@ -1047,6 +1060,11 @@ struct ConfigInfo image_config[] = { "em_dynamite.active.EDITOR", "RocksEMC.png" }, { "em_dynamite.active.EDITOR.xpos", "2" }, { "em_dynamite.active.EDITOR.ypos", "15" }, + { "em_dynamite.collecting", "RocksCollect.png" }, + { "em_dynamite.collecting.xpos", "0" }, + { "em_dynamite.collecting.ypos", "15" }, + { "em_dynamite.collecting.frames", "7" }, + { "em_dynamite.collecting.anim_mode", "linear" }, { "wall_emerald", "RocksElements.png" }, { "wall_emerald.xpos", "4" }, @@ -1482,8 +1500,7 @@ struct ConfigInfo image_config[] = { "amoeba_wet.xpos", "8" }, { "amoeba_wet.ypos", "6" }, { "amoeba_wet.frames", "4" }, - { "amoeba_wet.delay", "1000000" }, - { "amoeba_wet.anim_mode", "random" }, + { "amoeba_wet.anim_mode", "random_static" }, { "amoeba_wet.EDITOR", "RocksElements.png" }, { "amoeba_wet.EDITOR.xpos", "4" }, { "amoeba_wet.EDITOR.ypos", "6" }, @@ -1491,20 +1508,17 @@ struct ConfigInfo image_config[] = { "amoeba.dropping.xpos", "8" }, { "amoeba.dropping.ypos", "6" }, { "amoeba.dropping.frames", "4" }, - { "amoeba.dropping.delay", "1000000" }, - { "amoeba.dropping.anim_mode", "random" }, + { "amoeba.dropping.anim_mode", "random_static" }, { "amoeba_dry", "RocksElements.png" }, { "amoeba_dry.xpos", "8" }, { "amoeba_dry.ypos", "6" }, { "amoeba_dry.frames", "4" }, - { "amoeba_dry.delay", "1000000" }, - { "amoeba_dry.anim_mode", "random" }, + { "amoeba_dry.anim_mode", "random_static" }, { "amoeba_full", "RocksElements.png" }, { "amoeba_full.xpos", "8" }, { "amoeba_full.ypos", "6" }, { "amoeba_full.frames", "4" }, - { "amoeba_full.delay", "1000000" }, - { "amoeba_full.anim_mode", "random" }, + { "amoeba_full.anim_mode", "random_static" }, { "amoeba_full.EDITOR", "RocksElements.png" }, { "amoeba_full.EDITOR.xpos", "8" }, { "amoeba_full.EDITOR.ypos", "7" }, @@ -1512,8 +1526,7 @@ struct ConfigInfo image_config[] = { "amoeba_dead.xpos", "12" }, { "amoeba_dead.ypos", "6" }, { "amoeba_dead.frames", "4" }, - { "amoeba_dead.delay", "1000000" }, - { "amoeba_dead.anim_mode", "random" }, + { "amoeba_dead.anim_mode", "random_static" }, { "amoeba_dead.EDITOR", "RocksElements.png" }, { "amoeba_dead.EDITOR.xpos", "12" }, { "amoeba_dead.EDITOR.ypos", "6" }, @@ -1522,23 +1535,48 @@ struct ConfigInfo image_config[] = { "em_key_1.xpos", "4" }, { "em_key_1.ypos", "6" }, { "em_key_1.frames", "1" }, + { "em_key_1.collecting", "RocksCollect.png" }, + { "em_key_1.collecting.xpos", "7" }, + { "em_key_1.collecting.ypos", "4" }, + { "em_key_1.collecting.frames", "7" }, + { "em_key_1.collecting.anim_mode", "linear" }, { "em_key_2", "RocksSP.png" }, { "em_key_2.xpos", "5" }, { "em_key_2.ypos", "6" }, { "em_key_2.frames", "1" }, + { "em_key_2.collecting", "RocksCollect.png" }, + { "em_key_2.collecting.xpos", "7" }, + { "em_key_2.collecting.ypos", "5" }, + { "em_key_2.collecting.frames", "7" }, + { "em_key_2.collecting.anim_mode", "linear" }, { "em_key_3", "RocksSP.png" }, { "em_key_3.xpos", "6" }, { "em_key_3.ypos", "6" }, { "em_key_3.frames", "1" }, + { "em_key_3.collecting", "RocksCollect.png" }, + { "em_key_3.collecting.xpos", "7" }, + { "em_key_3.collecting.ypos", "6" }, + { "em_key_3.collecting.frames", "7" }, + { "em_key_3.collecting.anim_mode", "linear" }, { "em_key_4", "RocksSP.png" }, { "em_key_4.xpos", "7" }, { "em_key_4.ypos", "6" }, { "em_key_4.frames", "1" }, + { "em_key_4.collecting", "RocksCollect.png" }, + { "em_key_4.collecting.xpos", "7" }, + { "em_key_4.collecting.ypos", "7" }, + { "em_key_4.collecting.frames", "7" }, + { "em_key_4.collecting.anim_mode", "linear" }, { "dc_key_white", "RocksSP.png" }, { "dc_key_white.xpos", "13" }, { "dc_key_white.ypos", "1" }, { "dc_key_white.frames", "1" }, + { "dc_key_white.collecting", "RocksCollect.png" }, + { "dc_key_white.collecting.xpos", "7" }, + { "dc_key_white.collecting.ypos", "0" }, + { "dc_key_white.collecting.frames", "7" }, + { "dc_key_white.collecting.anim_mode", "linear" }, { "em_gate_1", "RocksSP.png" }, { "em_gate_1.xpos", "0" }, @@ -2118,41 +2156,37 @@ struct ConfigInfo image_config[] = { "envelope_1.xpos", "0" }, { "envelope_1.ypos", "4" }, { "envelope_1.frames", "1" }, - { "envelope_1.collecting", "RocksMore.png" }, - { "envelope_1.collecting.xpos", "5" }, - { "envelope_1.collecting.ypos", "4" }, - { "envelope_1.collecting.frames", "3" }, - { "envelope_1.collecting.delay", "2" }, + { "envelope_1.collecting", "RocksCollect.png" }, + { "envelope_1.collecting.xpos", "7" }, + { "envelope_1.collecting.ypos", "8" }, + { "envelope_1.collecting.frames", "7" }, { "envelope_1.collecting.anim_mode", "linear" }, { "envelope_2", "RocksMore.png" }, { "envelope_2.xpos", "1" }, { "envelope_2.ypos", "4" }, { "envelope_2.frames", "1" }, - { "envelope_2.collecting", "RocksMore.png" }, - { "envelope_2.collecting.xpos", "5" }, - { "envelope_2.collecting.ypos", "4" }, - { "envelope_2.collecting.frames", "3" }, - { "envelope_2.collecting.delay", "2" }, + { "envelope_2.collecting", "RocksCollect.png" }, + { "envelope_2.collecting.xpos", "7" }, + { "envelope_2.collecting.ypos", "9" }, + { "envelope_2.collecting.frames", "7" }, { "envelope_2.collecting.anim_mode", "linear" }, { "envelope_3", "RocksMore.png" }, { "envelope_3.xpos", "2" }, { "envelope_3.ypos", "4" }, { "envelope_3.frames", "1" }, - { "envelope_3.collecting", "RocksMore.png" }, - { "envelope_3.collecting.xpos", "5" }, - { "envelope_3.collecting.ypos", "4" }, - { "envelope_3.collecting.frames", "3" }, - { "envelope_3.collecting.delay", "2" }, + { "envelope_3.collecting", "RocksCollect.png" }, + { "envelope_3.collecting.xpos", "7" }, + { "envelope_3.collecting.ypos", "10" }, + { "envelope_3.collecting.frames", "7" }, { "envelope_3.collecting.anim_mode", "linear" }, { "envelope_4", "RocksMore.png" }, { "envelope_4.xpos", "3" }, { "envelope_4.ypos", "4" }, { "envelope_4.frames", "1" }, - { "envelope_4.collecting", "RocksMore.png" }, - { "envelope_4.collecting.xpos", "5" }, - { "envelope_4.collecting.ypos", "4" }, - { "envelope_4.collecting.frames", "3" }, - { "envelope_4.collecting.delay", "2" }, + { "envelope_4.collecting", "RocksCollect.png" }, + { "envelope_4.collecting.xpos", "7" }, + { "envelope_4.collecting.ypos", "11" }, + { "envelope_4.collecting.frames", "7" }, { "envelope_4.collecting.anim_mode", "linear" }, { "sign_radioactivity", "RocksDC.png" }, @@ -2277,6 +2311,11 @@ struct ConfigInfo image_config[] = { "extra_time.ypos", "0" }, { "extra_time.frames", "6" }, { "extra_time.delay", "4" }, + { "extra_time.collecting", "RocksCollect.png" }, + { "extra_time.collecting.xpos", "7" }, + { "extra_time.collecting.ypos", "2" }, + { "extra_time.collecting.frames", "7" }, + { "extra_time.collecting.anim_mode", "linear" }, { "shield_normal", "RocksDC.png" }, { "shield_normal.xpos", "8" }, @@ -2289,6 +2328,11 @@ struct ConfigInfo image_config[] = { "shield_normal.active.frames", "3" }, { "shield_normal.active.delay", "8" }, { "shield_normal.active.anim_mode", "pingpong" }, + { "shield_normal.collecting", "RocksCollect.png" }, + { "shield_normal.collecting.xpos", "7" }, + { "shield_normal.collecting.ypos", "1" }, + { "shield_normal.collecting.frames", "7" }, + { "shield_normal.collecting.anim_mode", "linear" }, { "shield_deadly", "RocksDC.png" }, { "shield_deadly.xpos", "8" }, @@ -2301,6 +2345,11 @@ struct ConfigInfo image_config[] = { "shield_deadly.active.frames", "3" }, { "shield_deadly.active.delay", "8" }, { "shield_deadly.active.anim_mode", "pingpong" }, + { "shield_deadly.collecting", "RocksCollect.png" }, + { "shield_deadly.collecting.xpos", "7" }, + { "shield_deadly.collecting.ypos", "3" }, + { "shield_deadly.collecting.frames", "7" }, + { "shield_deadly.collecting.anim_mode", "linear" }, { "switchgate_closed", "RocksDC.png" }, { "switchgate_closed.xpos", "8" }, @@ -2352,11 +2401,21 @@ struct ConfigInfo image_config[] = { "pearl.breaking.frames", "4" }, { "pearl.breaking.delay", "2" }, { "pearl.breaking.anim_mode", "linear" }, + { "pearl.collecting", "RocksCollect.png" }, + { "pearl.collecting.xpos", "0" }, + { "pearl.collecting.ypos", "16" }, + { "pearl.collecting.frames", "7" }, + { "pearl.collecting.anim_mode", "linear" }, { "crystal", "RocksDC.png" }, { "crystal.xpos", "9" }, { "crystal.ypos", "11" }, { "crystal.frames", "1" }, + { "crystal.collecting", "RocksCollect.png" }, + { "crystal.collecting.xpos", "0" }, + { "crystal.collecting.ypos", "17" }, + { "crystal.collecting.frames", "7" }, + { "crystal.collecting.anim_mode", "linear" }, { "wall_pearl", "RocksDC.png" }, { "wall_pearl.xpos", "10" }, @@ -2540,18 +2599,38 @@ struct ConfigInfo image_config[] = { "key_1.xpos", "4" }, { "key_1.ypos", "1" }, { "key_1.frames", "1" }, + { "key_1.collecting", "RocksCollect.png" }, + { "key_1.collecting.xpos", "0" }, + { "key_1.collecting.ypos", "3" }, + { "key_1.collecting.frames", "7" }, + { "key_1.collecting.anim_mode", "linear" }, { "key_2", "RocksElements.png" }, { "key_2.xpos", "5" }, { "key_2.ypos", "1" }, { "key_2.frames", "1" }, + { "key_2.collecting", "RocksCollect.png" }, + { "key_2.collecting.xpos", "0" }, + { "key_2.collecting.ypos", "4" }, + { "key_2.collecting.frames", "7" }, + { "key_2.collecting.anim_mode", "linear" }, { "key_3", "RocksElements.png" }, { "key_3.xpos", "6" }, { "key_3.ypos", "1" }, { "key_3.frames", "1" }, + { "key_3.collecting", "RocksCollect.png" }, + { "key_3.collecting.xpos", "0" }, + { "key_3.collecting.ypos", "5" }, + { "key_3.collecting.frames", "7" }, + { "key_3.collecting.anim_mode", "linear" }, { "key_4", "RocksElements.png" }, { "key_4.xpos", "7" }, { "key_4.ypos", "1" }, { "key_4.frames", "1" }, + { "key_4.collecting", "RocksCollect.png" }, + { "key_4.collecting.xpos", "0" }, + { "key_4.collecting.ypos", "6" }, + { "key_4.collecting.frames", "7" }, + { "key_4.collecting.anim_mode", "linear" }, { "gate_1", "RocksElements.png" }, { "gate_1.xpos", "4" }, @@ -2701,6 +2780,11 @@ struct ConfigInfo image_config[] = { "emerald_yellow.falling.ypos", "8" }, { "emerald_yellow.falling.frames", "2" }, { "emerald_yellow.falling.delay", "4" }, + { "emerald_yellow.collecting", "RocksCollect.png" }, + { "emerald_yellow.collecting.xpos", "0" }, + { "emerald_yellow.collecting.ypos", "9" }, + { "emerald_yellow.collecting.frames", "7" }, + { "emerald_yellow.collecting.anim_mode", "linear" }, { "emerald_red", "RocksElements.png" }, { "emerald_red.xpos", "8" }, { "emerald_red.ypos", "9" }, @@ -2715,6 +2799,11 @@ struct ConfigInfo image_config[] = { "emerald_red.falling.ypos", "9" }, { "emerald_red.falling.frames", "2" }, { "emerald_red.falling.delay", "4" }, + { "emerald_red.collecting", "RocksCollect.png" }, + { "emerald_red.collecting.xpos", "0" }, + { "emerald_red.collecting.ypos", "13" }, + { "emerald_red.collecting.frames", "7" }, + { "emerald_red.collecting.anim_mode", "linear" }, { "emerald_purple", "RocksElements.png" }, { "emerald_purple.xpos", "10" }, { "emerald_purple.ypos", "9" }, @@ -2729,6 +2818,11 @@ struct ConfigInfo image_config[] = { "emerald_purple.falling.ypos", "9" }, { "emerald_purple.falling.frames", "2" }, { "emerald_purple.falling.delay", "4" }, + { "emerald_purple.collecting", "RocksCollect.png" }, + { "emerald_purple.collecting.xpos", "0" }, + { "emerald_purple.collecting.ypos", "14" }, + { "emerald_purple.collecting.frames", "7" }, + { "emerald_purple.collecting.anim_mode", "linear" }, { "wall_emerald_yellow", "RocksElements.png" }, { "wall_emerald_yellow.xpos", "8" }, @@ -2862,6 +2956,11 @@ struct ConfigInfo image_config[] = { "speed_pill.xpos", "14" }, { "speed_pill.ypos", "9" }, { "speed_pill.frames", "1" }, + { "speed_pill.collecting", "RocksCollect.png" }, + { "speed_pill.collecting.xpos", "0" }, + { "speed_pill.collecting.ypos", "2" }, + { "speed_pill.collecting.frames", "7" }, + { "speed_pill.collecting.anim_mode", "linear" }, { "dark_yamyam", "RocksElements.png" }, { "dark_yamyam.xpos", "8" }, @@ -2923,14 +3022,29 @@ struct ConfigInfo image_config[] = { "dynabomb_increase_number.xpos", "12" }, { "dynabomb_increase_number.ypos", "11" }, { "dynabomb_increase_number.frames", "1" }, + { "dynabomb_increase_number.collecting", "RocksCollect.png" }, + { "dynabomb_increase_number.collecting.xpos", "0" }, + { "dynabomb_increase_number.collecting.ypos", "10" }, + { "dynabomb_increase_number.collecting.frames", "7" }, + { "dynabomb_increase_number.collecting.anim_mode", "linear" }, { "dynabomb_increase_size", "RocksElements.png" }, { "dynabomb_increase_size.xpos", "15" }, { "dynabomb_increase_size.ypos", "11" }, { "dynabomb_increase_size.frames", "1" }, + { "dynabomb_increase_size.collecting", "RocksCollect.png" }, + { "dynabomb_increase_size.collecting.xpos", "0" }, + { "dynabomb_increase_size.collecting.ypos", "11" }, + { "dynabomb_increase_size.collecting.frames", "7" }, + { "dynabomb_increase_size.collecting.anim_mode", "linear" }, { "dynabomb_increase_power", "RocksElements.png" }, { "dynabomb_increase_power.xpos", "12" }, { "dynabomb_increase_power.ypos", "9" }, { "dynabomb_increase_power.frames", "1" }, + { "dynabomb_increase_power.collecting", "RocksCollect.png" }, + { "dynabomb_increase_power.collecting.xpos", "0" }, + { "dynabomb_increase_power.collecting.ypos", "12" }, + { "dynabomb_increase_power.collecting.frames", "7" }, + { "dynabomb_increase_power.collecting.anim_mode", "linear" }, { "pig", "RocksHeroes.png" }, { "pig.xpos", "8" }, @@ -4002,18 +4116,38 @@ struct ConfigInfo image_config[] = { "emc_key_5.xpos", "0" }, { "emc_key_5.ypos", "5" }, { "emc_key_5.frames", "1" }, + { "emc_key_5.collecting", "RocksCollect.png" }, + { "emc_key_5.collecting.xpos", "7" }, + { "emc_key_5.collecting.ypos", "12" }, + { "emc_key_5.collecting.frames", "7" }, + { "emc_key_5.collecting.anim_mode", "linear" }, { "emc_key_6", "RocksEMC.png" }, { "emc_key_6.xpos", "1" }, { "emc_key_6.ypos", "5" }, { "emc_key_6.frames", "1" }, + { "emc_key_6.collecting", "RocksCollect.png" }, + { "emc_key_6.collecting.xpos", "7" }, + { "emc_key_6.collecting.ypos", "13" }, + { "emc_key_6.collecting.frames", "7" }, + { "emc_key_6.collecting.anim_mode", "linear" }, { "emc_key_7", "RocksEMC.png" }, { "emc_key_7.xpos", "2" }, { "emc_key_7.ypos", "5" }, { "emc_key_7.frames", "1" }, + { "emc_key_7.collecting", "RocksCollect.png" }, + { "emc_key_7.collecting.xpos", "7" }, + { "emc_key_7.collecting.ypos", "14" }, + { "emc_key_7.collecting.frames", "7" }, + { "emc_key_7.collecting.anim_mode", "linear" }, { "emc_key_8", "RocksEMC.png" }, { "emc_key_8.xpos", "3" }, { "emc_key_8.ypos", "5" }, { "emc_key_8.frames", "1" }, + { "emc_key_8.collecting", "RocksCollect.png" }, + { "emc_key_8.collecting.xpos", "7" }, + { "emc_key_8.collecting.ypos", "15" }, + { "emc_key_8.collecting.frames", "7" }, + { "emc_key_8.collecting.anim_mode", "linear" }, { "emc_gate_5", "RocksEMC.png" }, { "emc_gate_5.xpos", "0" }, @@ -4235,11 +4369,21 @@ struct ConfigInfo image_config[] = { "emc_lenses.xpos", "6" }, { "emc_lenses.ypos", "4" }, { "emc_lenses.frames", "1" }, + { "emc_lenses.collecting", "RocksCollect.png" }, + { "emc_lenses.collecting.xpos", "7" }, + { "emc_lenses.collecting.ypos", "16" }, + { "emc_lenses.collecting.frames", "7" }, + { "emc_lenses.collecting.anim_mode", "linear" }, { "emc_magnifier", "RocksEMC.png" }, { "emc_magnifier.xpos", "7" }, { "emc_magnifier.ypos", "4" }, { "emc_magnifier.frames", "1" }, + { "emc_magnifier.collecting", "RocksCollect.png" }, + { "emc_magnifier.collecting.xpos", "7" }, + { "emc_magnifier.collecting.ypos", "17" }, + { "emc_magnifier.collecting.frames", "7" }, + { "emc_magnifier.collecting.anim_mode", "linear" }, { "emc_wall_9", "RocksEMC.png" }, { "emc_wall_9.xpos", "10" }, @@ -4898,20 +5042,26 @@ struct ConfigInfo image_config[] = { "mm_teleporter_blue_16.frames", "1" }, { "mm_kettle", "RocksMM.png" }, - { "mm_kettle.xpos", "12" }, - { "mm_kettle.ypos", "1" }, + { "mm_kettle.xpos", "9" }, + { "mm_kettle.ypos", "8" }, { "mm_kettle.frames", "1" }, { "mm_kettle.exploding", "RocksMM.png" }, - { "mm_kettle.exploding.xpos", "13" }, - { "mm_kettle.exploding.ypos", "1" }, - { "mm_kettle.exploding.frames", "3" }, - { "mm_kettle.exploding.delay", "4" }, + { "mm_kettle.exploding.xpos", "10" }, + { "mm_kettle.exploding.ypos", "8" }, + { "mm_kettle.exploding.frames", "6" }, + { "mm_kettle.exploding.delay", "2" }, { "mm_kettle.exploding.anim_mode", "linear" }, { "mm_bomb", "RocksMM.png" }, { "mm_bomb.xpos", "5" }, { "mm_bomb.ypos", "2" }, { "mm_bomb.frames", "1" }, + { "mm_bomb.active", "RocksMM.png" }, + { "mm_bomb.active.xpos", "12" }, + { "mm_bomb.active.ypos", "1" }, + { "mm_bomb.active.frames", "3" }, + { "mm_bomb.active.delay", "6" }, + { "mm_bomb.active.anim_mode", "pingpong" }, { "mm_prism", "RocksMM.png" }, { "mm_prism.xpos", "0" }, @@ -4931,11 +5081,23 @@ struct ConfigInfo image_config[] = { "mm_steel_lock.xpos", "8" }, { "mm_steel_lock.ypos", "2" }, { "mm_steel_lock.frames", "1" }, + { "mm_steel_lock.exploding", "RocksMM.png" }, + { "mm_steel_lock.exploding.xpos", "4" }, + { "mm_steel_lock.exploding.ypos", "8" }, + { "mm_steel_lock.exploding.frames", "5" }, + { "mm_steel_lock.exploding.delay", "2" }, + { "mm_steel_lock.exploding.anim_mode", "linear" }, { "mm_wooden_lock", "RocksMM.png" }, { "mm_wooden_lock.xpos", "9" }, { "mm_wooden_lock.ypos", "6" }, { "mm_wooden_lock.frames", "1" }, + { "mm_wooden_lock.exploding", "RocksMM.png" }, + { "mm_wooden_lock.exploding.xpos", "4" }, + { "mm_wooden_lock.exploding.ypos", "8" }, + { "mm_wooden_lock.exploding.frames", "5" }, + { "mm_wooden_lock.exploding.delay", "2" }, + { "mm_wooden_lock.exploding.anim_mode", "linear" }, { "mm_steel_block", "RocksMM.png" }, { "mm_steel_block.xpos", "8" }, @@ -4965,8 +5127,7 @@ struct ConfigInfo image_config[] = { "mm_lightball.xpos", "12" }, { "mm_lightball.ypos", "2" }, { "mm_lightball.frames", "3" }, - { "mm_lightball.delay", "1000000" }, - { "mm_lightball.anim_mode", "random" }, + { "mm_lightball.anim_mode", "random_static" }, { "mm_lightball_red", "RocksMM.png" }, { "mm_lightball_red.xpos", "12" }, { "mm_lightball_red.ypos", "2" }, @@ -4984,6 +5145,16 @@ struct ConfigInfo image_config[] = { "mm_gray_ball.xpos", "15" }, { "mm_gray_ball.ypos", "2" }, { "mm_gray_ball.frames", "1" }, + { "mm_gray_ball.active", "RocksMM.png" }, + { "mm_gray_ball.active.xpos", "15" }, + { "mm_gray_ball.active.ypos", "1" }, + { "mm_gray_ball.active.frames", "2" }, + { "mm_gray_ball.active.delay", "20" }, + { "mm_gray_ball.active.vertical", "true" }, + { "mm_gray_ball.EDITOR", "RocksMM.png" }, + { "mm_gray_ball.EDITOR.xpos", "15" }, + { "mm_gray_ball.EDITOR.ypos", "1" }, + { "mm_gray_ball.EDITOR.frames", "1" }, { "mm_fuel_full", "RocksMM.png" }, { "mm_fuel_full.xpos", "10" }, @@ -5063,53 +5234,26 @@ struct ConfigInfo image_config[] = { "mm_pacman.eating.down.ypos", "4" }, { "mm_pacman.eating.down.frames", "1" }, - { "mm_mask_mcduffin.right", "RocksMM.png" }, - { "mm_mask_mcduffin.right.xpos", "8" }, - { "mm_mask_mcduffin.right.ypos", "8" }, - { "mm_mask_mcduffin.right.frames", "1" }, - { "mm_mask_mcduffin.up", "RocksMM.png" }, - { "mm_mask_mcduffin.up.xpos", "9" }, - { "mm_mask_mcduffin.up.ypos", "8" }, - { "mm_mask_mcduffin.up.frames", "1" }, - { "mm_mask_mcduffin.left", "RocksMM.png" }, - { "mm_mask_mcduffin.left.xpos", "10" }, - { "mm_mask_mcduffin.left.ypos", "8" }, - { "mm_mask_mcduffin.left.frames", "1" }, - { "mm_mask_mcduffin.down", "RocksMM.png" }, - { "mm_mask_mcduffin.down.xpos", "11" }, - { "mm_mask_mcduffin.down.ypos", "8" }, - { "mm_mask_mcduffin.down.frames", "1" }, - - { "mm_mask_grid_1", "RocksMM.png" }, - { "mm_mask_grid_1.xpos", "4" }, - { "mm_mask_grid_1.ypos", "8" }, - { "mm_mask_grid_1.frames", "1" }, - { "mm_mask_grid_2", "RocksMM.png" }, - { "mm_mask_grid_2.xpos", "5" }, - { "mm_mask_grid_2.ypos", "8" }, - { "mm_mask_grid_2.frames", "1" }, - { "mm_mask_grid_3", "RocksMM.png" }, - { "mm_mask_grid_3.xpos", "6" }, - { "mm_mask_grid_3.ypos", "8" }, - { "mm_mask_grid_3.frames", "1" }, - { "mm_mask_grid_4", "RocksMM.png" }, - { "mm_mask_grid_4.xpos", "7" }, - { "mm_mask_grid_4.ypos", "8" }, - { "mm_mask_grid_4.frames", "1" }, - - { "mm_mask_rectangle", "RocksMM.png" }, - { "mm_mask_rectangle.xpos", "1" }, - { "mm_mask_rectangle.ypos", "8" }, - { "mm_mask_rectangle.frames", "1" }, - - { "mm_mask_circle", "RocksMM.png" }, - { "mm_mask_circle.xpos", "0" }, - { "mm_mask_circle.ypos", "8" }, - { "mm_mask_circle.frames", "1" }, + { "mm_envelope_1", UNDEFINED_FILENAME }, + { "mm_envelope_1.clone_from", "envelope_1" }, + { "mm_envelope_1.collecting", UNDEFINED_FILENAME }, + { "mm_envelope_1.collecting.clone_from", "envelope_1.collecting" }, + { "mm_envelope_2", UNDEFINED_FILENAME }, + { "mm_envelope_2.clone_from", "envelope_2" }, + { "mm_envelope_2.collecting", UNDEFINED_FILENAME }, + { "mm_envelope_2.collecting.clone_from", "envelope_2.collecting" }, + { "mm_envelope_3", UNDEFINED_FILENAME }, + { "mm_envelope_3.clone_from", "envelope_3" }, + { "mm_envelope_3.collecting", UNDEFINED_FILENAME }, + { "mm_envelope_3.collecting.clone_from", "envelope_3.collecting" }, + { "mm_envelope_4", UNDEFINED_FILENAME }, + { "mm_envelope_4.clone_from", "envelope_4" }, + { "mm_envelope_4.collecting", UNDEFINED_FILENAME }, + { "mm_envelope_4.collecting.clone_from", "envelope_4.collecting" }, { "[mm_default].exploding", "RocksMM.png" }, - { "[mm_default].exploding.xpos", "8" }, - { "[mm_default].exploding.ypos", "4" }, + { "[mm_default].exploding.xpos", "0" }, + { "[mm_default].exploding.ypos", "8" }, { "[mm_default].exploding.frames", "8" }, { "[mm_default].exploding.delay", "2" }, { "[mm_default].exploding.anim_mode", "linear" }, @@ -5637,6 +5781,94 @@ struct ConfigInfo image_config[] = { "df_mine.xpos", "4" }, { "df_mine.ypos", "8" }, { "df_mine.frames", "1" }, + { "df_mine.active", "RocksDF.png" }, + { "df_mine.active.xpos", "3" }, + { "df_mine.active.ypos", "8" }, + { "df_mine.active.frames", "3" }, + { "df_mine.active.delay", "6" }, + { "df_mine.active.anim_mode", "pingpong" }, + + { "df_mirror_fixed_1", "RocksDF.png" }, + { "df_mirror_fixed_1.xpos", "0" }, + { "df_mirror_fixed_1.ypos", "10" }, + { "df_mirror_fixed_1.frames", "1" }, + { "df_mirror_fixed_2", "RocksDF.png" }, + { "df_mirror_fixed_2.xpos", "1" }, + { "df_mirror_fixed_2.ypos", "10" }, + { "df_mirror_fixed_2.frames", "1" }, + { "df_mirror_fixed_3", "RocksDF.png" }, + { "df_mirror_fixed_3.xpos", "2" }, + { "df_mirror_fixed_3.ypos", "10" }, + { "df_mirror_fixed_3.frames", "1" }, + { "df_mirror_fixed_4", "RocksDF.png" }, + { "df_mirror_fixed_4.xpos", "3" }, + { "df_mirror_fixed_4.ypos", "10" }, + { "df_mirror_fixed_4.frames", "1" }, + { "df_mirror_fixed_5", "RocksDF.png" }, + { "df_mirror_fixed_5.xpos", "4" }, + { "df_mirror_fixed_5.ypos", "10" }, + { "df_mirror_fixed_5.frames", "1" }, + { "df_mirror_fixed_6", "RocksDF.png" }, + { "df_mirror_fixed_6.xpos", "5" }, + { "df_mirror_fixed_6.ypos", "10" }, + { "df_mirror_fixed_6.frames", "1" }, + { "df_mirror_fixed_7", "RocksDF.png" }, + { "df_mirror_fixed_7.xpos", "6" }, + { "df_mirror_fixed_7.ypos", "10" }, + { "df_mirror_fixed_7.frames", "1" }, + { "df_mirror_fixed_8", "RocksDF.png" }, + { "df_mirror_fixed_8.xpos", "7" }, + { "df_mirror_fixed_8.ypos", "10" }, + { "df_mirror_fixed_8.frames", "1" }, + { "df_mirror_fixed_9", "RocksDF.png" }, + { "df_mirror_fixed_9.xpos", "8" }, + { "df_mirror_fixed_9.ypos", "10" }, + { "df_mirror_fixed_9.frames", "1" }, + { "df_mirror_fixed_10", "RocksDF.png" }, + { "df_mirror_fixed_10.xpos", "9" }, + { "df_mirror_fixed_10.ypos", "10" }, + { "df_mirror_fixed_10.frames", "1" }, + { "df_mirror_fixed_11", "RocksDF.png" }, + { "df_mirror_fixed_11.xpos", "10" }, + { "df_mirror_fixed_11.ypos", "10" }, + { "df_mirror_fixed_11.frames", "1" }, + { "df_mirror_fixed_12", "RocksDF.png" }, + { "df_mirror_fixed_12.xpos", "11" }, + { "df_mirror_fixed_12.ypos", "10" }, + { "df_mirror_fixed_12.frames", "1" }, + { "df_mirror_fixed_13", "RocksDF.png" }, + { "df_mirror_fixed_13.xpos", "12" }, + { "df_mirror_fixed_13.ypos", "10" }, + { "df_mirror_fixed_13.frames", "1" }, + { "df_mirror_fixed_14", "RocksDF.png" }, + { "df_mirror_fixed_14.xpos", "13" }, + { "df_mirror_fixed_14.ypos", "10" }, + { "df_mirror_fixed_14.frames", "1" }, + { "df_mirror_fixed_15", "RocksDF.png" }, + { "df_mirror_fixed_15.xpos", "14" }, + { "df_mirror_fixed_15.ypos", "10" }, + { "df_mirror_fixed_15.frames", "1" }, + { "df_mirror_fixed_16", "RocksDF.png" }, + { "df_mirror_fixed_16.xpos", "15" }, + { "df_mirror_fixed_16.ypos", "10" }, + { "df_mirror_fixed_16.frames", "1" }, + + { "df_slope_1", "RocksDF.png" }, + { "df_slope_1.xpos", "0" }, + { "df_slope_1.ypos", "11" }, + { "df_slope_1.frames", "1" }, + { "df_slope_2", "RocksDF.png" }, + { "df_slope_2.xpos", "1" }, + { "df_slope_2.ypos", "11" }, + { "df_slope_2.frames", "1" }, + { "df_slope_3", "RocksDF.png" }, + { "df_slope_3.xpos", "2" }, + { "df_slope_3.ypos", "11" }, + { "df_slope_3.frames", "1" }, + { "df_slope_4", "RocksDF.png" }, + { "df_slope_4.xpos", "3" }, + { "df_slope_4.ypos", "11" }, + { "df_slope_4.frames", "1" }, // (these are only defined as elements to support ".PANEL" definitions) { "graphic_1", UNDEFINED_FILENAME }, @@ -5651,6 +5883,7 @@ struct ConfigInfo image_config[] = #include "conf_chr.c" // include auto-generated data structure definitions #include "conf_cus.c" // include auto-generated data structure definitions #include "conf_grp.c" // include auto-generated data structure definitions +#include "conf_emp.c" // include auto-generated data structure definitions // ========================================================================== @@ -5999,6 +6232,27 @@ struct ConfigInfo image_config[] = { "menu.button_prev_level.active", UNDEFINED_FILENAME }, { "menu.button_prev_level.active.clone_from", "menu.button_left.active" }, + { "menu.button_next_level2", UNDEFINED_FILENAME }, + { "menu.button_next_level2.clone_from", "menu.button_right" }, + { "menu.button_next_level2.active", UNDEFINED_FILENAME }, + { "menu.button_next_level2.active.clone_from", "menu.button_right.active" }, + { "menu.button_prev_level2", UNDEFINED_FILENAME }, + { "menu.button_prev_level2.clone_from", "menu.button_left" }, + { "menu.button_prev_level2.active", UNDEFINED_FILENAME }, + { "menu.button_prev_level2.active.clone_from", "menu.button_left.active" }, + + { "menu.button_next_score", UNDEFINED_FILENAME }, + { "menu.button_next_score.clone_from", "menu.button_down" }, + { "menu.button_next_score.active", UNDEFINED_FILENAME }, + { "menu.button_next_score.active.clone_from", "menu.button_down.active" }, + { "menu.button_prev_score", UNDEFINED_FILENAME }, + { "menu.button_prev_score.clone_from", "menu.button_up" }, + { "menu.button_prev_score.active", UNDEFINED_FILENAME }, + { "menu.button_prev_score.active.clone_from", "menu.button_up.active" }, + + { "menu.button_play_tape", UNDEFINED_FILENAME }, + { "menu.button_play_tape.clone_from", "gfx.tape.button.play" }, + { "menu.button_name", UNDEFINED_FILENAME }, { "menu.button_name.clone_from", "menu.button" }, { "menu.button_name.active", UNDEFINED_FILENAME }, @@ -6044,6 +6298,13 @@ struct ConfigInfo image_config[] = { "menu.button_play_solution", UNDEFINED_FILENAME }, { "menu.button_play_solution.active", UNDEFINED_FILENAME }, + { "menu.button_levelset_info", UNDEFINED_FILENAME }, + { "menu.button_levelset_info.clone_from", "envelope_1" }, + { "menu.button_levelset_info.pressed", UNDEFINED_FILENAME }, + { "menu.button_levelset_info.pressed.clone_from", "envelope_1.collecting" }, + { "menu.button_levelset_info.active", UNDEFINED_FILENAME }, + { "menu.button_levelset_info.active.clone_from", "envelope_1" }, + { "menu.button_switch_ecs_aga", UNDEFINED_FILENAME }, { "menu.button_switch_ecs_aga.active", UNDEFINED_FILENAME }, @@ -6159,6 +6420,13 @@ struct ConfigInfo image_config[] = { "gfx.game.button.load.height", "30" }, { "gfx.game.button.load.pressed_xoffset", "-100" }, + { "gfx.game.button.restart", "RocksDoor2.png" }, + { "gfx.game.button.restart.x", "200" }, + { "gfx.game.button.restart.y", "50" }, + { "gfx.game.button.restart.width", "30" }, + { "gfx.game.button.restart.height", "30" }, + { "gfx.game.button.restart.pressed_xoffset", "30" }, + { "gfx.game.button.sound_music", "RocksDoor.png" }, { "gfx.game.button.sound_music.x", "305" }, { "gfx.game.button.sound_music.y", "245" }, @@ -6184,6 +6452,7 @@ struct ConfigInfo image_config[] = { "gfx.game.button.panel_stop", UNDEFINED_FILENAME }, { "gfx.game.button.panel_pause", UNDEFINED_FILENAME }, { "gfx.game.button.panel_play", UNDEFINED_FILENAME }, + { "gfx.game.button.panel_restart", UNDEFINED_FILENAME }, { "gfx.game.button.panel_sound_music", UNDEFINED_FILENAME }, { "gfx.game.button.panel_sound_loops", UNDEFINED_FILENAME }, @@ -6203,6 +6472,13 @@ struct ConfigInfo image_config[] = { "gfx.game.button.touch_pause.pressed_xoffset", "-200" }, { "gfx.game.button.touch_pause.active_yoffset", "60" }, + { "gfx.game.button.touch_restart", "RocksTouch.png" }, + { "gfx.game.button.touch_restart.x", "210" }, + { "gfx.game.button.touch_restart.y", "240" }, + { "gfx.game.button.touch_restart.width", "60" }, + { "gfx.game.button.touch_restart.height", "60" }, + { "gfx.game.button.touch_restart.pressed_xoffset", "-200" }, + { "gfx.tape.button.eject", "RocksDoor.png" }, { "gfx.tape.button.eject.x", "305" }, { "gfx.tape.button.eject.y", "357" }, @@ -6597,6 +6873,8 @@ struct ConfigInfo image_config[] = { "font.request.y", "210" }, { "font.request.width", "14" }, { "font.request.height", "14" }, + { "font.request_narrow", UNDEFINED_FILENAME }, + { "font.request_narrow.clone_from", "font.text_1.DOOR" }, { "font.input_1", "RocksFontSmall.png" }, { "font.input_1.x", "0" }, @@ -7172,6 +7450,14 @@ struct ConfigInfo image_config[] = { "global.door", "RocksDoor.png" }, + { "global.busy_initial", "RocksBusy.png" }, + { "global.busy_initial.x", "0" }, + { "global.busy_initial.y", "0" }, + { "global.busy_initial.width", "32" }, + { "global.busy_initial.height", "32" }, + { "global.busy_initial.frames", "28" }, + { "global.busy_initial.frames_per_line", "7" }, + { "global.busy_initial.delay", "2" }, { "global.busy", "RocksBusy.png" }, { "global.busy.x", "0" }, { "global.busy.y", "0" }, @@ -7180,6 +7466,14 @@ struct ConfigInfo image_config[] = { "global.busy.frames", "28" }, { "global.busy.frames_per_line", "7" }, { "global.busy.delay", "2" }, + { "global.busy_playfield", "RocksBusy.png" }, + { "global.busy_playfield.x", "0" }, + { "global.busy_playfield.y", "0" }, + { "global.busy_playfield.width", "32" }, + { "global.busy_playfield.height", "32" }, + { "global.busy_playfield.frames", "28" }, + { "global.busy_playfield.frames_per_line", "7" }, + { "global.busy_playfield.delay", "2" }, { "global.tile_cursor", "RocksMore.png" }, { "global.tile_cursor.xpos", "10" }, @@ -7187,6 +7481,8 @@ struct ConfigInfo image_config[] = { "global.tile_cursor.frames", "1" }, { "background", UNDEFINED_FILENAME }, + { "background.LOADING_INITIAL", UNDEFINED_FILENAME }, + { "background.LOADING", UNDEFINED_FILENAME }, { "background.TITLE_INITIAL", UNDEFINED_FILENAME }, { "background.TITLE", UNDEFINED_FILENAME }, { "background.MAIN", UNDEFINED_FILENAME }, @@ -7194,6 +7490,7 @@ struct ConfigInfo image_config[] = { "background.LEVELS", UNDEFINED_FILENAME }, { "background.LEVELNR", UNDEFINED_FILENAME }, { "background.SCORES", UNDEFINED_FILENAME }, + { "background.SCOREINFO", UNDEFINED_FILENAME }, { "background.EDITOR", UNDEFINED_FILENAME }, { "background.INFO", UNDEFINED_FILENAME }, { "background.INFO[ELEMENTS]", UNDEFINED_FILENAME }, @@ -7718,6 +8015,7 @@ struct ConfigInfo image_config[] = { "border.draw_masked.LEVELS", "false" }, { "border.draw_masked.LEVELNR", "false" }, { "border.draw_masked.SCORES", "false" }, + { "border.draw_masked.SCOREINFO", "false" }, { "border.draw_masked.EDITOR", "false" }, { "border.draw_masked.INFO", "false" }, { "border.draw_masked.SETUP", "false" }, @@ -7726,10 +8024,18 @@ struct ConfigInfo image_config[] = { "border.draw_masked_when_fading", "true" }, + { "init.busy_initial.x", "-1" }, + { "init.busy_initial.y", "-1" }, + { "init.busy_initial.align", "center" }, + { "init.busy_initial.valign", "middle" }, { "init.busy.x", "-1" }, { "init.busy.y", "-1" }, { "init.busy.align", "center" }, { "init.busy.valign", "middle" }, + { "init.busy_playfield.x", "-1" }, + { "init.busy_playfield.y", "-1" }, + { "init.busy_playfield.align", "center" }, + { "init.busy_playfield.valign", "middle" }, { "menu.enter_menu.fade_mode", "none" }, { "menu.enter_menu.fade_delay", "250" }, @@ -7754,6 +8060,9 @@ struct ConfigInfo image_config[] = { "menu.enter_screen.SCORES.fade_mode", ARG_DEFAULT }, { "menu.enter_screen.SCORES.fade_delay", ARG_DEFAULT }, { "menu.enter_screen.SCORES.post_delay", ARG_DEFAULT }, + { "menu.enter_screen.SCOREINFO.fade_mode", ARG_DEFAULT }, + { "menu.enter_screen.SCOREINFO.fade_delay", ARG_DEFAULT }, + { "menu.enter_screen.SCOREINFO.post_delay", ARG_DEFAULT }, { "menu.enter_screen.EDITOR.fade_mode", ARG_DEFAULT }, { "menu.enter_screen.EDITOR.fade_delay", ARG_DEFAULT }, { "menu.enter_screen.EDITOR.post_delay", ARG_DEFAULT }, @@ -7771,6 +8080,9 @@ struct ConfigInfo image_config[] = { "menu.leave_screen.SCORES.fade_mode", ARG_DEFAULT }, { "menu.leave_screen.SCORES.fade_delay", ARG_DEFAULT }, { "menu.leave_screen.SCORES.post_delay", ARG_DEFAULT }, + { "menu.leave_screen.SCOREINFO.fade_mode", ARG_DEFAULT }, + { "menu.leave_screen.SCOREINFO.fade_delay", ARG_DEFAULT }, + { "menu.leave_screen.SCOREINFO.post_delay", ARG_DEFAULT }, { "menu.leave_screen.EDITOR.fade_mode", ARG_DEFAULT }, { "menu.leave_screen.EDITOR.fade_delay", ARG_DEFAULT }, { "menu.leave_screen.EDITOR.post_delay", ARG_DEFAULT }, @@ -7801,6 +8113,8 @@ struct ConfigInfo image_config[] = { "menu.draw_yoffset.LEVELNR", "0" }, { "menu.draw_xoffset.SCORES", "0" }, { "menu.draw_yoffset.SCORES", "0" }, + { "menu.draw_xoffset.SCOREINFO", "0" }, + { "menu.draw_yoffset.SCOREINFO", "0" }, { "menu.draw_xoffset.EDITOR", "0" }, { "menu.draw_yoffset.EDITOR", "0" }, { "menu.draw_xoffset.INFO", "0" }, @@ -7868,6 +8182,11 @@ struct ConfigInfo image_config[] = { "menu.list_size.INFO[ELEMENTS]", "-1" }, { "menu.list_size.SETUP", "-1" }, + { "menu.list_entry_size.INFO[ELEMENTS]", "-1" }, + + { "menu.tile_size.INFO[ELEMENTS]", "-1" }, + + { "menu.left_spacing.SCOREINFO", "16" }, { "menu.left_spacing.INFO", "16" }, { "menu.left_spacing.INFO[TITLE]", "16" }, { "menu.left_spacing.INFO[ELEMENTS]", "16" }, @@ -7878,6 +8197,9 @@ struct ConfigInfo image_config[] = { "menu.left_spacing.INFO[LEVELSET]", "16" }, { "menu.left_spacing.SETUP[INPUT]", "16" }, + { "menu.middle_spacing.INFO[ELEMENTS]", "16" }, + + { "menu.right_spacing.SCOREINFO", "16" }, { "menu.right_spacing.INFO", "16" }, { "menu.right_spacing.INFO[TITLE]", "16" }, { "menu.right_spacing.INFO[ELEMENTS]", "16" }, @@ -7888,6 +8210,7 @@ struct ConfigInfo image_config[] = { "menu.right_spacing.INFO[LEVELSET]", "16" }, { "menu.right_spacing.SETUP[INPUT]", "16" }, + { "menu.top_spacing.SCOREINFO", "100" }, { "menu.top_spacing.INFO", "100" }, { "menu.top_spacing.INFO[TITLE]", "100" }, { "menu.top_spacing.INFO[ELEMENTS]", "100" }, @@ -7898,6 +8221,7 @@ struct ConfigInfo image_config[] = { "menu.top_spacing.INFO[LEVELSET]", "100" }, { "menu.top_spacing.SETUP[INPUT]", "100" }, + { "menu.bottom_spacing.SCOREINFO", "20" }, { "menu.bottom_spacing.INFO", "20" }, { "menu.bottom_spacing.INFO[TITLE]", "20" }, { "menu.bottom_spacing.INFO[ELEMENTS]", "20" }, @@ -7908,6 +8232,7 @@ struct ConfigInfo image_config[] = { "menu.bottom_spacing.INFO[LEVELSET]", "20" }, { "menu.bottom_spacing.SETUP[INPUT]", "20" }, + { "menu.paragraph_spacing.SCOREINFO", "-2" }, { "menu.paragraph_spacing.INFO", "-3" }, { "menu.paragraph_spacing.INFO[TITLE]", "-3" }, { "menu.paragraph_spacing.INFO[ELEMENTS]", "-3" }, @@ -7918,6 +8243,7 @@ struct ConfigInfo image_config[] = { "menu.paragraph_spacing.INFO[LEVELSET]", "-3" }, { "menu.paragraph_spacing.SETUP[INPUT]", "-1" }, + { "menu.headline1_spacing.SCOREINFO", "-2" }, { "menu.headline1_spacing.INFO", "-2" }, { "menu.headline1_spacing.INFO[TITLE]", "-2" }, { "menu.headline1_spacing.INFO[ELEMENTS]", "-2" }, @@ -7928,6 +8254,7 @@ struct ConfigInfo image_config[] = { "menu.headline1_spacing.INFO[LEVELSET]", "-2" }, { "menu.headline1_spacing.SETUP[INPUT]", "-2" }, + { "menu.headline2_spacing.SCOREINFO", "-1" }, { "menu.headline2_spacing.INFO", "-1" }, { "menu.headline2_spacing.INFO[TITLE]", "-1" }, { "menu.headline2_spacing.INFO[ELEMENTS]", "-1" }, @@ -7938,6 +8265,7 @@ struct ConfigInfo image_config[] = { "menu.headline2_spacing.INFO[LEVELSET]", "-1" }, { "menu.headline2_spacing.SETUP[INPUT]", "-1" }, + { "menu.line_spacing.SCOREINFO", "0" }, { "menu.line_spacing.INFO", "0" }, { "menu.line_spacing.INFO[TITLE]", "0" }, { "menu.line_spacing.INFO[ELEMENTS]", "0" }, @@ -7948,9 +8276,10 @@ struct ConfigInfo image_config[] = { "menu.line_spacing.INFO[LEVELSET]", "0" }, { "menu.line_spacing.SETUP[INPUT]", "0" }, + { "menu.extra_spacing.SCOREINFO", "2" }, { "menu.extra_spacing.INFO", "2" }, { "menu.extra_spacing.INFO[TITLE]", "2" }, - { "menu.extra_spacing.INFO[ELEMENTS]", "2" }, + { "menu.extra_spacing.INFO[ELEMENTS]", "4" }, { "menu.extra_spacing.INFO[MUSIC]", "2" }, { "menu.extra_spacing.INFO[CREDITS]", "2" }, { "menu.extra_spacing.INFO[PROGRAM]", "2" }, @@ -7992,6 +8321,9 @@ struct ConfigInfo image_config[] = { "main.button.play_solution.x", "-1" }, { "main.button.play_solution.y", "-1" }, + { "main.button.levelset_info.x", "-1" }, + { "main.button.levelset_info.y", "-1" }, + { "main.button.switch_ecs_aga.x", "-1" }, { "main.button.switch_ecs_aga.y", "-1" }, @@ -8173,6 +8505,19 @@ struct ConfigInfo image_config[] = { "setup.button.touch_next2.x", "-60" }, { "setup.button.touch_next2.y", "-60" }, + { "scores.button.prev_level.x", "-1" }, + { "scores.button.prev_level.y", "-1" }, + { "scores.button.next_level.x", "-1" }, + { "scores.button.next_level.y", "-1" }, + + { "scores.button.prev_score.x", "-1" }, + { "scores.button.prev_score.y", "-1" }, + { "scores.button.next_score.x", "-1" }, + { "scores.button.next_score.y", "-1" }, + + { "scores.button.play_tape.x", "-1" }, + { "scores.button.play_tape.y", "-1" }, + { "preview.x", "272" }, { "preview.y", "380" }, { "preview.align", "center" }, @@ -8411,8 +8756,9 @@ struct ConfigInfo image_config[] = { "game.panel.inventory_count.y", "89" }, { "game.panel.inventory_count.align", "center" }, { "game.panel.inventory_count.valign", "top" }, - { "game.panel.inventory_count.digits", "3" }, + { "game.panel.inventory_count.digits", "-1" }, { "game.panel.inventory_count.font", "font.text_2" }, + { "game.panel.inventory_count.font_narrow", "font.text_1" }, { "game.panel.inventory_count.draw_masked", "true" }, { "game.panel.inventory_count.draw_order", "0" }, { "game.panel.inventory_count.class", "none" }, @@ -8610,8 +8956,9 @@ struct ConfigInfo image_config[] = { "game.panel.score.y", "159" }, { "game.panel.score.align", "center" }, { "game.panel.score.valign", "top" }, - { "game.panel.score.digits", "5" }, + { "game.panel.score.digits", "-1" }, { "game.panel.score.font", "font.text_2" }, + { "game.panel.score.font_narrow", "font.text_1" }, { "game.panel.score.draw_masked", "true" }, { "game.panel.score.draw_order", "0" }, { "game.panel.score.class", "none" }, @@ -8621,8 +8968,9 @@ struct ConfigInfo image_config[] = { "game.panel.highscore.y", "-1" }, { "game.panel.highscore.align", "left" }, { "game.panel.highscore.valign", "top" }, - { "game.panel.highscore.digits", "5" }, + { "game.panel.highscore.digits", "-1" }, { "game.panel.highscore.font", "font.text_2" }, + { "game.panel.highscore.font_narrow", "font.text_1" }, { "game.panel.highscore.draw_masked", "true" }, { "game.panel.highscore.draw_order", "0" }, { "game.panel.highscore.class", "none" }, @@ -9400,6 +9748,8 @@ struct ConfigInfo image_config[] = { "game.button.pause2.y", "-1" }, { "game.button.load.x", "-1" }, { "game.button.load.y", "-1" }, + { "game.button.restart.x", "-1" }, + { "game.button.restart.y", "-1" }, { "game.button.sound_music.x", "5" }, { "game.button.sound_music.y", "245" }, { "game.button.sound_loops.x", "35" }, @@ -9413,6 +9763,8 @@ struct ConfigInfo image_config[] = { "game.button.panel_pause.y", "-1" }, { "game.button.panel_play.x", "-1" }, { "game.button.panel_play.y", "-1" }, + { "game.button.panel_restart.x", "-1" }, + { "game.button.panel_restart.y", "-1" }, { "game.button.panel_sound_music.x", "-1" }, { "game.button.panel_sound_music.y", "-1" }, { "game.button.panel_sound_loops.x", "-1" }, @@ -9424,6 +9776,8 @@ struct ConfigInfo image_config[] = { "game.button.touch_stop.y", "0" }, { "game.button.touch_pause.x", "-60" }, { "game.button.touch_pause.y", "0" }, + { "game.button.touch_restart.x", "-1" }, + { "game.button.touch_restart.y", "-1" }, { "tape.button.eject.x", "5" }, { "tape.button.eject.y", "77" }, @@ -9739,15 +10093,17 @@ struct ConfigInfo image_config[] = { "request.anim_mode", "default" }, { "request.align", "center" }, { "request.valign", "middle" }, - { "request.draw_order", "0" }, { "request.autowrap", "false" }, { "request.centered", "true" }, { "request.wrap_single_words", "true" }, + { "request.draw_order", "-1" }, { "global.use_envelope_request", "false" }, { "game.graphics_engine_version", "-1" }, { "game.forced_scroll_delay_value", "-1" }, + { "game.forced_scroll_x", ARG_UNDEFINED }, + { "game.forced_scroll_y", ARG_UNDEFINED }, { "game.use_native_emc_graphics_engine", "false" }, { "game.use_native_sp_graphics_engine", "true" }, { "game.use_masked_pushing", "false" }, diff --git a/src/conf_mus.c b/src/conf_mus.c index d11a5bff..864cbc90 100644 --- a/src/conf_mus.c +++ b/src/conf_mus.c @@ -36,6 +36,11 @@ struct ConfigInfo music_config[] = { "background.SCORES", UNDEFINED_FILENAME }, { "background.EDITOR", UNDEFINED_FILENAME }, { "background.INFO", "rhythmloop.wav" }, + { "background.INFO[ELEMENTS]", UNDEFINED_FILENAME }, + { "background.INFO[CREDITS]", UNDEFINED_FILENAME }, + { "background.INFO[PROGRAM]", UNDEFINED_FILENAME }, + { "background.INFO[VERSION]", UNDEFINED_FILENAME }, + { "background.INFO[LEVELSET]", UNDEFINED_FILENAME }, { "background.SETUP", UNDEFINED_FILENAME }, { "background.titlescreen_initial_1", UNDEFINED_FILENAME }, diff --git a/src/conf_snd.c b/src/conf_snd.c index eacaf778..64fda496 100644 --- a/src/conf_snd.c +++ b/src/conf_snd.c @@ -53,7 +53,9 @@ struct ConfigInfo sound_config[] = { "bd_amoeba.waiting", UNDEFINED_FILENAME }, { "bd_amoeba.growing", "amoebe.wav" }, { "bd_amoeba.turning_to_gem", "pling.wav" }, + { "bd_amoeba.turning_to_gem.mode_loop", "false" }, { "bd_amoeba.turning_to_rock", "klopf.wav" }, + { "bd_amoeba.turning_to_rock.mode_loop", "false" }, { "bd_butterfly.moving", "klapper.wav" }, { "bd_butterfly.waiting", "klapper.wav" }, { "bd_firefly.moving", "roehr.wav" }, @@ -202,7 +204,9 @@ struct ConfigInfo sound_config[] = // sounds for Rocks'n'Diamonds style elements and actions { "amoeba.turning_to_gem", "pling.wav" }, + { "amoeba.turning_to_gem.mode_loop", "false" }, { "amoeba.turning_to_rock", "klopf.wav" }, + { "amoeba.turning_to_rock.mode_loop", "false" }, { "speed_pill.collecting", "pong.wav" }, { "dynabomb_increase_number.collecting","pong.wav" }, { "dynabomb_increase_size.collecting","pong.wav" }, @@ -290,7 +294,7 @@ struct ConfigInfo sound_config[] = { "game.running_out_of_time", "gong.wav" }, { "game.leveltime_bonus", "sirr.wav" }, { "game.health_bonus", "sirr.wav" }, - { "game.losing", "lachen.wav" }, + { "game.losing", UNDEFINED_FILENAME }, { "game.winning", UNDEFINED_FILENAME }, { "game.sokoban_solving", "buing.wav" }, @@ -322,6 +326,11 @@ struct ConfigInfo sound_config[] = { "background.SCORES.mode_loop", "false" }, { "background.EDITOR", UNDEFINED_FILENAME }, { "background.INFO", UNDEFINED_FILENAME }, + { "background.INFO[ELEMENTS]", UNDEFINED_FILENAME }, + { "background.INFO[CREDITS]", UNDEFINED_FILENAME }, + { "background.INFO[PROGRAM]", UNDEFINED_FILENAME }, + { "background.INFO[VERSION]", UNDEFINED_FILENAME }, + { "background.INFO[LEVELSET]", UNDEFINED_FILENAME }, { "background.SETUP", UNDEFINED_FILENAME }, { "background.titlescreen_initial_1", UNDEFINED_FILENAME }, diff --git a/src/config.c b/src/config.c index d50ba684..8a7a5c5b 100644 --- a/src/config.c +++ b/src/config.c @@ -47,6 +47,11 @@ char *getProgramVersionString(void) return program.version_string; } +char *getProgramPlatformString(void) +{ + return PLATFORM_STRING; +} + char *getProgramInitString(void) { static char *program_init_string = NULL; diff --git a/src/config.h b/src/config.h index 06ae39e6..31e86b87 100644 --- a/src/config.h +++ b/src/config.h @@ -19,6 +19,7 @@ char *getSourceHashString(void); char *getProgramTitleString(void); char *getProgramRealVersionString(void); char *getProgramVersionString(void); +char *getProgramPlatformString(void); char *getProgramInitString(void); char *getConfigProgramTitleString(void); char *getConfigProgramCopyrightString(void); diff --git a/src/editor.c b/src/editor.c index 24590595..671ac61d 100644 --- a/src/editor.c +++ b/src/editor.c @@ -382,7 +382,7 @@ enum GADGET_ID_PICK_ELEMENT, GADGET_ID_UNDO, - GADGET_ID_INFO, + GADGET_ID_CONF, GADGET_ID_SAVE, GADGET_ID_CLEAR, GADGET_ID_TEST, @@ -459,6 +459,9 @@ enum GADGET_ID_INVENTORY_SIZE_DOWN, GADGET_ID_INVENTORY_SIZE_TEXT, GADGET_ID_INVENTORY_SIZE_UP, + GADGET_ID_MM_BALL_CONTENT_DOWN, + GADGET_ID_MM_BALL_CONTENT_TEXT, + GADGET_ID_MM_BALL_CONTENT_UP, GADGET_ID_CUSTOM_SCORE_DOWN, GADGET_ID_CUSTOM_SCORE_TEXT, GADGET_ID_CUSTOM_SCORE_UP, @@ -539,6 +542,7 @@ enum GADGET_ID_ARTWORK_ELEMENT, GADGET_ID_EXPLOSION_ELEMENT, GADGET_ID_INVENTORY_CONTENT, + GADGET_ID_MM_BALL_CONTENT, GADGET_ID_CUSTOM_GRAPHIC, GADGET_ID_CUSTOM_CONTENT, GADGET_ID_CUSTOM_MOVE_ENTER, @@ -570,6 +574,7 @@ enum GADGET_ID_LEVELSET_SAVE_MODE, GADGET_ID_WIND_DIRECTION, GADGET_ID_PLAYER_SPEED, + GADGET_ID_MM_BALL_CHOICE_MODE, GADGET_ID_CUSTOM_WALK_TO_ACTION, GADGET_ID_CUSTOM_EXPLOSION_TYPE, GADGET_ID_CUSTOM_DEADLINESS, @@ -598,9 +603,9 @@ enum // textbutton identifiers - GADGET_ID_LEVELINFO_LEVEL, - GADGET_ID_LEVELINFO_LEVELSET, - GADGET_ID_LEVELINFO_EDITOR, + GADGET_ID_LEVELCONFIG_LEVEL, + GADGET_ID_LEVELCONFIG_LEVELSET, + GADGET_ID_LEVELCONFIG_EDITOR, GADGET_ID_PROPERTIES_INFO, GADGET_ID_PROPERTIES_CONFIG, GADGET_ID_PROPERTIES_CONFIG_1, @@ -637,6 +642,7 @@ enum // checkbuttons/radiobuttons for level/element properties GADGET_ID_AUTO_COUNT_GEMS, + GADGET_ID_RATE_TIME_OVER_SCORE, GADGET_ID_USE_LEVELSET_ARTWORK, GADGET_ID_COPY_LEVEL_TEMPLATE, GADGET_ID_RANDOM_PERCENTAGE, @@ -656,6 +662,7 @@ enum GADGET_ID_AUTO_EXIT_SOKOBAN, GADGET_ID_SOLVED_BY_ONE_PLAYER, GADGET_ID_FINISH_DIG_COLLECT, + GADGET_ID_KEEP_WALKABLE_CE, GADGET_ID_CONTINUOUS_SNAPPING, GADGET_ID_BLOCK_SNAP_FIELD, GADGET_ID_BLOCK_LAST_FIELD, @@ -680,6 +687,8 @@ enum GADGET_ID_DF_LASER_RED, GADGET_ID_DF_LASER_GREEN, GADGET_ID_DF_LASER_BLUE, + GADGET_ID_ROTATE_MM_BALL_CONTENT, + GADGET_ID_EXPLODE_MM_BALL, GADGET_ID_CUSTOM_INDESTRUCTIBLE, GADGET_ID_CUSTOM_CAN_EXPLODE, GADGET_ID_CUSTOM_EXPLODE_FIRE, @@ -748,6 +757,7 @@ enum ED_COUNTER_ID_ENVELOPE_XSIZE, ED_COUNTER_ID_ENVELOPE_YSIZE, ED_COUNTER_ID_INVENTORY_SIZE, + ED_COUNTER_ID_MM_BALL_CONTENT, ED_COUNTER_ID_CUSTOM_SCORE, ED_COUNTER_ID_CUSTOM_GEMCOUNT, ED_COUNTER_ID_CUSTOM_VALUE_FIX, @@ -854,6 +864,7 @@ enum ED_SELECTBOX_ID_LEVELSET_SAVE_MODE, ED_SELECTBOX_ID_WIND_DIRECTION, ED_SELECTBOX_ID_PLAYER_SPEED, + ED_SELECTBOX_ID_MM_BALL_CHOICE_MODE, ED_SELECTBOX_ID_CUSTOM_ACCESS_TYPE, ED_SELECTBOX_ID_CUSTOM_ACCESS_LAYER, ED_SELECTBOX_ID_CUSTOM_ACCESS_PROTECTED, @@ -902,9 +913,9 @@ enum // values for textbutton gadgets enum { - ED_TEXTBUTTON_ID_LEVELINFO_LEVEL, - ED_TEXTBUTTON_ID_LEVELINFO_LEVELSET, - ED_TEXTBUTTON_ID_LEVELINFO_EDITOR, + ED_TEXTBUTTON_ID_LEVELCONFIG_LEVEL, + ED_TEXTBUTTON_ID_LEVELCONFIG_LEVELSET, + ED_TEXTBUTTON_ID_LEVELCONFIG_EDITOR, ED_TEXTBUTTON_ID_PROPERTIES_INFO, ED_TEXTBUTTON_ID_PROPERTIES_CONFIG, ED_TEXTBUTTON_ID_PROPERTIES_CONFIG_1, @@ -919,8 +930,8 @@ enum ED_NUM_TEXTBUTTONS }; -#define ED_TAB_BUTTON_ID_LEVELINFO_FIRST ED_TEXTBUTTON_ID_LEVELINFO_LEVEL -#define ED_TAB_BUTTON_ID_LEVELINFO_LAST ED_TEXTBUTTON_ID_LEVELINFO_EDITOR +#define ED_TAB_BUTTON_ID_LEVELCONFIG_FIRST ED_TEXTBUTTON_ID_LEVELCONFIG_LEVEL +#define ED_TAB_BUTTON_ID_LEVELCONFIG_LAST ED_TEXTBUTTON_ID_LEVELCONFIG_EDITOR #define ED_TAB_BUTTON_ID_PROPERTIES_FIRST ED_TEXTBUTTON_ID_PROPERTIES_INFO #define ED_TAB_BUTTON_ID_PROPERTIES_LAST ED_TEXTBUTTON_ID_PROPERTIES_CHANGE @@ -946,6 +957,7 @@ enum enum { ED_CHECKBUTTON_ID_AUTO_COUNT_GEMS, + ED_CHECKBUTTON_ID_RATE_TIME_OVER_SCORE, ED_CHECKBUTTON_ID_USE_LEVELSET_ARTWORK, ED_CHECKBUTTON_ID_COPY_LEVEL_TEMPLATE, ED_CHECKBUTTON_ID_RANDOM_RESTRICTED, @@ -965,6 +977,7 @@ enum ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN, ED_CHECKBUTTON_ID_SOLVED_BY_ONE_PLAYER, ED_CHECKBUTTON_ID_FINISH_DIG_COLLECT, + ED_CHECKBUTTON_ID_KEEP_WALKABLE_CE, ED_CHECKBUTTON_ID_CONTINUOUS_SNAPPING, ED_CHECKBUTTON_ID_BLOCK_SNAP_FIELD, ED_CHECKBUTTON_ID_BLOCK_LAST_FIELD, @@ -989,6 +1002,8 @@ enum ED_CHECKBUTTON_ID_DF_LASER_RED, ED_CHECKBUTTON_ID_DF_LASER_GREEN, ED_CHECKBUTTON_ID_DF_LASER_BLUE, + ED_CHECKBUTTON_ID_ROTATE_MM_BALL_CONTENT, + ED_CHECKBUTTON_ID_EXPLODE_MM_BALL, ED_CHECKBUTTON_ID_CUSTOM_USE_GRAPHIC, ED_CHECKBUTTON_ID_CUSTOM_USE_TEMPLATE_1, ED_CHECKBUTTON_ID_CUSTOM_ACCESSIBLE, @@ -1019,7 +1034,7 @@ enum }; #define ED_CHECKBUTTON_ID_LEVEL_FIRST ED_CHECKBUTTON_ID_AUTO_COUNT_GEMS -#define ED_CHECKBUTTON_ID_LEVEL_LAST ED_CHECKBUTTON_ID_AUTO_COUNT_GEMS +#define ED_CHECKBUTTON_ID_LEVEL_LAST ED_CHECKBUTTON_ID_RATE_TIME_OVER_SCORE #define ED_CHECKBUTTON_ID_LEVELSET_FIRST ED_CHECKBUTTON_ID_USE_LEVELSET_ARTWORK #define ED_CHECKBUTTON_ID_LEVELSET_LAST ED_CHECKBUTTON_ID_COPY_LEVEL_TEMPLATE @@ -1075,6 +1090,7 @@ enum ED_DRAWING_ID_ARTWORK_ELEMENT, ED_DRAWING_ID_EXPLOSION_ELEMENT, ED_DRAWING_ID_INVENTORY_CONTENT, + ED_DRAWING_ID_MM_BALL_CONTENT, ED_DRAWING_ID_CUSTOM_GRAPHIC, ED_DRAWING_ID_CUSTOM_CONTENT, ED_DRAWING_ID_CUSTOM_MOVE_ENTER, @@ -1110,14 +1126,14 @@ enum // screens in the level editor #define ED_MODE_DRAWING 0 -#define ED_MODE_INFO 1 +#define ED_MODE_LEVELCONFIG 1 #define ED_MODE_PROPERTIES 2 #define ED_MODE_PALETTE 3 // sub-screens in the global settings section -#define ED_MODE_LEVELINFO_LEVEL ED_TEXTBUTTON_ID_LEVELINFO_LEVEL -#define ED_MODE_LEVELINFO_LEVELSET ED_TEXTBUTTON_ID_LEVELINFO_LEVELSET -#define ED_MODE_LEVELINFO_EDITOR ED_TEXTBUTTON_ID_LEVELINFO_EDITOR +#define ED_MODE_LEVELCONFIG_LEVEL ED_TEXTBUTTON_ID_LEVELCONFIG_LEVEL +#define ED_MODE_LEVELCONFIG_LEVELSET ED_TEXTBUTTON_ID_LEVELCONFIG_LEVELSET +#define ED_MODE_LEVELCONFIG_EDITOR ED_TEXTBUTTON_ID_LEVELCONFIG_EDITOR // sub-screens in the element properties section #define ED_MODE_PROPERTIES_INFO ED_TEXTBUTTON_ID_PROPERTIES_INFO @@ -1258,9 +1274,9 @@ static struct "undo/redo last operation", 'u' }, { - IMG_GFX_EDITOR_BUTTON_CONF, GADGET_ID_INFO, + IMG_GFX_EDITOR_BUTTON_CONF, GADGET_ID_CONF, &editor.button.conf, GD_TYPE_NORMAL_BUTTON, - "properties of level", 'I' + "level and editor settings", 'I' }, { IMG_GFX_EDITOR_BUTTON_SAVE, GADGET_ID_SAVE, @@ -1423,7 +1439,7 @@ static struct "score for time or steps left:", NULL, NULL }, { - ED_LEVEL_SETTINGS_XPOS(0), ED_LEVEL_SETTINGS_YPOS(12), + ED_LEVEL_SETTINGS_XPOS(0), ED_LEVEL_SETTINGS_YPOS(13), 0, 9999, GADGET_ID_LEVEL_RANDOM_SEED_DOWN, GADGET_ID_LEVEL_RANDOM_SEED_UP, GADGET_ID_LEVEL_RANDOM_SEED_TEXT, GADGET_ID_NONE, @@ -1529,6 +1545,14 @@ static struct &level.initial_inventory_size[0], NULL, NULL, "number of inventory elements" }, + { + ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(3), + MIN_ELEMENTS_IN_GROUP, MAX_MM_BALL_CONTENTS, + GADGET_ID_MM_BALL_CONTENT_DOWN, GADGET_ID_MM_BALL_CONTENT_UP, + GADGET_ID_MM_BALL_CONTENT_TEXT, GADGET_ID_NONE, + &level.num_mm_ball_contents, + NULL, NULL, "number of content elements" + }, // ---------- element settings: configure 1 (custom elements) --------------- @@ -1739,7 +1763,7 @@ static struct int gadget_id; int xsize, ysize; char *value; - char *text_above, *infotext; + char *text_above, *text_above_cropped, *infotext; } textarea_info[ED_NUM_TEXTAREAS] = { { @@ -1747,7 +1771,7 @@ static struct GADGET_ID_ENVELOPE_INFO, MAX_ENVELOPE_XSIZE, MAX_ENVELOPE_YSIZE, NULL, - "Envelope Content:", "Envelope Content" + "Envelope Content:", "Envelope Content (cropped):", "Envelope Content" } }; @@ -2005,6 +2029,9 @@ static struct ValueTextInfo options_change_direct_action[] = { CE_HEADLINE_SPECIAL_EVENTS, "[mouse events]" }, { CE_CLICKED_BY_MOUSE, "clicked by mouse" }, { CE_PRESSED_BY_MOUSE, "pressed by mouse" }, + { CE_UNDEFINED, " " }, + { CE_HEADLINE_SPECIAL_EVENTS, "[static states]" }, + { CE_NEXT_TO_PLAYER, "next to player" }, { -1, NULL } }; @@ -2038,6 +2065,10 @@ static struct ValueTextInfo options_change_other_action[] = { CE_HEADLINE_SPECIAL_EVENTS, "[mouse events]" }, { CE_MOUSE_CLICKED_ON_X, "mouse clicked on" }, { CE_MOUSE_PRESSED_ON_X, "mouse pressed on" }, + { CE_UNDEFINED, " " }, + { CE_HEADLINE_SPECIAL_EVENTS, "[static states]" }, + { CE_PLAYER_NEXT_TO_X, "player next to" }, + { CE_NEXT_TO_X, "next to" }, { -1, NULL } }; @@ -2068,38 +2099,38 @@ static struct ValueTextInfo options_change_trigger_player[] = static struct ValueTextInfo options_change_trigger_page[] = { - { (1 << 0), "1" }, - { (1 << 1), "2" }, - { (1 << 2), "3" }, - { (1 << 3), "4" }, - { (1 << 4), "5" }, - { (1 << 5), "6" }, - { (1 << 6), "7" }, - { (1 << 7), "8" }, - { (1 << 8), "9" }, - { (1 << 9), "10" }, - { (1 << 10), "11" }, - { (1 << 11), "12" }, - { (1 << 12), "13" }, - { (1 << 13), "14" }, - { (1 << 14), "15" }, - { (1 << 15), "16" }, - { (1 << 16), "17" }, - { (1 << 17), "18" }, - { (1 << 18), "19" }, - { (1 << 19), "20" }, - { (1 << 20), "21" }, - { (1 << 21), "22" }, - { (1 << 22), "23" }, - { (1 << 23), "24" }, - { (1 << 24), "25" }, - { (1 << 25), "26" }, - { (1 << 26), "27" }, - { (1 << 27), "28" }, - { (1 << 28), "29" }, - { (1 << 29), "30" }, - { (1 << 30), "31" }, - { (1 << 31), "32" }, + { (1u << 0), "1" }, + { (1u << 1), "2" }, + { (1u << 2), "3" }, + { (1u << 3), "4" }, + { (1u << 4), "5" }, + { (1u << 5), "6" }, + { (1u << 6), "7" }, + { (1u << 7), "8" }, + { (1u << 8), "9" }, + { (1u << 9), "10" }, + { (1u << 10), "11" }, + { (1u << 11), "12" }, + { (1u << 12), "13" }, + { (1u << 13), "14" }, + { (1u << 14), "15" }, + { (1u << 15), "16" }, + { (1u << 16), "17" }, + { (1u << 17), "18" }, + { (1u << 18), "19" }, + { (1u << 19), "20" }, + { (1u << 20), "21" }, + { (1u << 21), "22" }, + { (1u << 22), "23" }, + { (1u << 23), "24" }, + { (1u << 24), "25" }, + { (1u << 25), "26" }, + { (1u << 26), "27" }, + { (1u << 27), "28" }, + { (1u << 28), "29" }, + { (1u << 29), "30" }, + { (1u << 30), "31" }, + { (1u << 31), "32" }, { CH_PAGE_ANY, "any" }, { -1, NULL } @@ -2434,6 +2465,7 @@ static struct ValueTextInfo options_group_choice_mode[] = { ANIM_LINEAR, "linear" }, { ANIM_PINGPONG, "pingpong" }, { ANIM_PINGPONG2, "pingpong 2" }, + { ANIM_LEVEL_NR, "level number" }, { -1, NULL } }; @@ -2510,7 +2542,7 @@ static struct NULL, NULL, NULL, "time score for 1 or 10 seconds/steps" }, { - ED_LEVEL_SETTINGS_XPOS(0), ED_LEVEL_SETTINGS_YPOS(11), + ED_LEVEL_SETTINGS_XPOS(0), ED_LEVEL_SETTINGS_YPOS(12), GADGET_ID_GAME_ENGINE_TYPE, GADGET_ID_NONE, -1, options_game_engine_type, @@ -2544,6 +2576,14 @@ static struct &level.initial_player_stepsize[0], NULL, "initial player speed:", NULL, "initial player speed" }, + { + ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(4), + GADGET_ID_MM_BALL_CHOICE_MODE, GADGET_ID_NONE, + -1, + options_group_choice_mode, + &level.mm_ball_choice_mode, + NULL, "choice type:", NULL, "type of content choice" + }, // ---------- element settings: configure 1 (custom elements) --------------- @@ -2775,21 +2815,21 @@ static struct { ED_LEVEL_TABS_XPOS(0), ED_LEVEL_TABS_YPOS(0), - GADGET_ID_LEVELINFO_LEVEL, GADGET_ID_NONE, + GADGET_ID_LEVELCONFIG_LEVEL, GADGET_ID_NONE, 8, "Level", - NULL, NULL, NULL, "Configure level properties" + NULL, NULL, NULL, "Configure level settings" }, { -1, -1, - GADGET_ID_LEVELINFO_LEVELSET, GADGET_ID_LEVELINFO_LEVEL, + GADGET_ID_LEVELCONFIG_LEVELSET, GADGET_ID_LEVELCONFIG_LEVEL, 8, "Levelset", NULL, NULL, NULL, "Update this or create new level set" }, { -1, -1, - GADGET_ID_LEVELINFO_EDITOR, GADGET_ID_LEVELINFO_LEVELSET, + GADGET_ID_LEVELCONFIG_EDITOR, GADGET_ID_LEVELCONFIG_LEVELSET, 8, "Editor", - NULL, NULL, NULL, "Configure editor properties" + NULL, NULL, NULL, "Configure editor settings" }, // ---------- element settings (tabs) --------------------------------------- @@ -3025,6 +3065,13 @@ static struct NULL, NULL, "automatically count gems needed", "set counter to number of gems" }, + { + ED_LEVEL_SETTINGS_XPOS(0), ED_LEVEL_SETTINGS_YPOS(11), + GADGET_ID_RATE_TIME_OVER_SCORE, GADGET_ID_NONE, + &level.rate_time_over_score, + NULL, NULL, + "rate time/steps used over score", "sort high scores by playing time/steps" + }, { ED_LEVEL_SETTINGS_XPOS(0), ED_LEVEL_SETTINGS_YPOS(7), GADGET_ID_USE_LEVELSET_ARTWORK, GADGET_ID_NONE, @@ -3161,6 +3208,13 @@ static struct NULL, NULL, "CE action on finished dig/collect", "only finished dig/collect triggers CE" }, + { + ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(4), + GADGET_ID_KEEP_WALKABLE_CE, GADGET_ID_NONE, + &level.keep_walkable_ce, + NULL, NULL, + "keep walkable CE changed to player", "keep CE changing to player if walkable" + }, { ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(9), GADGET_ID_CONTINUOUS_SNAPPING, GADGET_ID_NONE, @@ -3329,6 +3383,20 @@ static struct NULL, NULL, "blue", "use blue color components in laser" }, + { + ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(5), + GADGET_ID_ROTATE_MM_BALL_CONTENT, GADGET_ID_NONE, + &level.rotate_mm_ball_content, + NULL, NULL, + "randomly rotate created content", "randomly rotate newly created content" + }, + { + ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(6), + GADGET_ID_EXPLODE_MM_BALL, GADGET_ID_NONE, + &level.explode_mm_ball, + NULL, NULL, + "explode ball instead of melting", "use explosion to release ball content" + }, // ---------- element settings: configure 1 (custom elements) --------------- @@ -3715,6 +3783,16 @@ static struct NULL, NULL, NULL, NULL, "content for initial inventory" }, + // ---------- gray ball content ----------------------------------------- + + { + ED_AREA_1X1_SETTINGS_XPOS(0), ED_AREA_1X1_SETTINGS_YPOS(2), + ED_AREA_1X1_SETTINGS_XOFF, ED_AREA_1X1_SETTINGS_YOFF, + GADGET_ID_MM_BALL_CONTENT, GADGET_ID_NONE, + &level.mm_ball_content[0], MAX_MM_BALL_CONTENTS, 1, + "content:", NULL, NULL, NULL, "content for gray ball" + }, + // ---------- element settings: configure 1 (custom elements) --------------- // ---------- custom graphic ------------------------------------------------ @@ -3723,7 +3801,7 @@ static struct -1, ED_AREA_1X1_SETTINGS_YPOS(1), 0, ED_AREA_1X1_SETTINGS_YOFF, GADGET_ID_CUSTOM_GRAPHIC, GADGET_ID_CUSTOM_USE_GRAPHIC, - &custom_element.gfx_element_initial,1, 1, + &custom_element.gfx_element_initial, 1, 1, NULL, NULL, NULL, NULL, "custom graphic element" }, @@ -3837,7 +3915,7 @@ static int level_xpos = -1, level_ypos = -1; static int ed_tilesize = DEFAULT_EDITOR_TILESIZE; static int ed_tilesize_default = DEFAULT_EDITOR_TILESIZE; -#define IN_ED_FIELD(x,y) IN_FIELD(x, y, ed_fieldx, ed_fieldy) +#define IN_ED_FIELD(x, y) IN_FIELD(x, y, ed_fieldx, ed_fieldy) // drawing elements on the three mouse buttons static int new_element1 = EL_WALL; @@ -3871,7 +3949,7 @@ static void AdjustElementListScrollbar(void); static void RedrawDrawingElements(void); static void DrawDrawingWindowExt(boolean); static void DrawDrawingWindow(void); -static void DrawLevelInfoWindow(void); +static void DrawLevelConfigWindow(void); static void DrawPropertiesWindow(void); static void DrawPaletteWindow(void); static void UpdateCustomElementGraphicGadgets(void); @@ -3896,6 +3974,7 @@ static boolean getDrawModeHiRes(void); static int getTabulatorBarWidth(void); static int getTabulatorBarHeight(void); static Pixel getTabulatorBarColor(void); +static int numHiresTiles(int); static int num_editor_gadgets = 0; // dynamically determined @@ -3915,7 +3994,7 @@ static int undo_buffer_steps = 0; static int redo_buffer_steps = 0; static int edit_mode; -static int edit_mode_levelinfo; +static int edit_mode_levelconfig; static int edit_mode_properties; static int element_shift = 0; @@ -4585,7 +4664,12 @@ static int editor_el_mirror_magic[] = EL_MM_WOODEN_GRID_FIXED_1, EL_MM_WOODEN_GRID_FIXED_2, EL_MM_WOODEN_GRID_FIXED_3, - EL_MM_WOODEN_GRID_FIXED_4 + EL_MM_WOODEN_GRID_FIXED_4, + + EL_MM_ENVELOPE_1, + EL_MM_ENVELOPE_2, + EL_MM_ENVELOPE_3, + EL_MM_ENVELOPE_4 }; static int *editor_hl_mirror_magic_ptr = editor_hl_mirror_magic; static int *editor_el_mirror_magic_ptr = editor_el_mirror_magic; @@ -4614,8 +4698,8 @@ static int editor_el_deflektor[] = EL_DF_MIRROR_START, EL_DF_MIRROR_ROTATING_START, + EL_DF_MIRROR_FIXED_START, EL_DF_CELL, - EL_DF_MINE, EL_DF_FIBRE_OPTIC_RED_1, EL_DF_FIBRE_OPTIC_YELLOW_1, @@ -4630,7 +4714,12 @@ static int editor_el_deflektor[] = EL_DF_STEEL_WALL, EL_DF_WOODEN_WALL, EL_DF_REFRACTOR, - EL_EMPTY + EL_DF_MINE, + + EL_DF_SLOPE_1, + EL_DF_SLOPE_2, + EL_DF_SLOPE_3, + EL_DF_SLOPE_4 }; static int *editor_hl_deflektor_ptr = editor_hl_deflektor; static int *editor_el_deflektor_ptr = editor_el_deflektor; @@ -5247,6 +5336,41 @@ static int *editor_el_group_ptr = editor_el_group; static int num_editor_hl_group = ARRAY_SIZE(editor_hl_group); static int num_editor_el_group = ARRAY_SIZE(editor_el_group); +static int editor_hl_empty_space[] = +{ + EL_INTERNAL_CASCADE_ES_ACTIVE, + EL_CHAR('E'), + EL_CHAR('S'), + EL_EMPTY, +}; + +static int editor_el_empty_space[] = +{ + EL_EMPTY_SPACE_1, + EL_EMPTY_SPACE_2, + EL_EMPTY_SPACE_3, + EL_EMPTY_SPACE_4, + + EL_EMPTY_SPACE_5, + EL_EMPTY_SPACE_6, + EL_EMPTY_SPACE_7, + EL_EMPTY_SPACE_8, + + EL_EMPTY_SPACE_9, + EL_EMPTY_SPACE_10, + EL_EMPTY_SPACE_11, + EL_EMPTY_SPACE_12, + + EL_EMPTY_SPACE_13, + EL_EMPTY_SPACE_14, + EL_EMPTY_SPACE_15, + EL_EMPTY_SPACE_16 +}; +static int *editor_hl_empty_space_ptr = editor_hl_empty_space; +static int *editor_el_empty_space_ptr = editor_el_empty_space; +static int num_editor_hl_empty_space = ARRAY_SIZE(editor_hl_empty_space); +static int num_editor_el_empty_space = ARRAY_SIZE(editor_el_empty_space); + static int editor_hl_reference[] = { EL_INTERNAL_CASCADE_REF_ACTIVE, @@ -5459,6 +5583,12 @@ editor_elements_info[] = &editor_hl_group_ptr, &num_editor_hl_group, &editor_el_group_ptr, &num_editor_el_group }, + { + &setup_editor_el_custom, + &setup.editor_cascade.el_es, + &editor_hl_empty_space_ptr, &num_editor_hl_empty_space, + &editor_el_empty_space_ptr, &num_editor_el_empty_space + }, { &setup_editor_el_custom, &setup.editor_cascade.el_ref, @@ -5491,6 +5621,14 @@ editor_elements_info[] = } }; +static struct XY xy_directions[] = +{ + { -1, 0 }, + { +1, 0 }, + { 0, -1 }, + { 0, +1 } +}; + // ---------------------------------------------------------------------------- // functions @@ -5544,7 +5682,7 @@ static char *getElementInfoText(int element) static char *getElementDescriptionFilenameExt(char *basename) { - char *elements_subdir = "elements"; + char *elements_subdir = ELEMENTS_DIRECTORY; static char *elements_subdir2 = NULL; static char *filename = NULL; @@ -5585,6 +5723,11 @@ static char *getElementDescriptionFilename(int element) if (filename != NULL) return filename; + // 3rd try: look for generic fallback text file for any element + filename = getElementDescriptionFilenameExt(FALLBACK_TEXT_FILENAME); + if (filename != NULL) + return filename; + return NULL; } @@ -5606,9 +5749,18 @@ static void InitDynamicEditorElementList(int **elements, int *num_elements) // find all elements used in current level for (y = 0; y < lev_fieldy; y++) + { for (x = 0; x < lev_fieldx; x++) - if (Tile[x][y] < NUM_FILE_ELEMENTS) // should always be true + { + if (Tile[x][y] >= NUM_FILE_ELEMENTS) // should never happen + continue; + + if (IS_MM_WALL(Tile[x][y])) + element_found[map_mm_wall_element(Tile[x][y])] = TRUE; + else element_found[Tile[x][y]] = TRUE; + } + } *num_elements = 0; @@ -5628,14 +5780,18 @@ static void InitDynamicEditorElementList(int **elements, int *num_elements) *num_elements = 0; - // add all elements used in current level (non-custom/group elements) + // add all elements used in current level (non-custom/group/empty elements) for (i = 0; i < NUM_FILE_ELEMENTS; i++) - if (element_found[i] && !(IS_CUSTOM_ELEMENT(i) || IS_GROUP_ELEMENT(i))) + if (element_found[i] && !(IS_CUSTOM_ELEMENT(i) || + IS_GROUP_ELEMENT(i) || + IS_EMPTY_ELEMENT(i))) (*elements)[(*num_elements)++] = i; - // add all elements used in current level (custom/group elements) + // add all elements used in current level (custom/group/empty elements) for (i = 0; i < NUM_FILE_ELEMENTS; i++) - if (element_found[i] && (IS_CUSTOM_ELEMENT(i) || IS_GROUP_ELEMENT(i))) + if (element_found[i] && (IS_CUSTOM_ELEMENT(i) || + IS_GROUP_ELEMENT(i) || + IS_EMPTY_ELEMENT(i))) (*elements)[(*num_elements)++] = i; while (*num_elements % 4) // pad with empty elements, if needed @@ -5813,10 +5969,10 @@ static void ReinitializeElementList(void) // determine size of element list for (i = 0; editor_elements_info[i].setup_value != NULL; i++) { - boolean found_inactive_cascade = FALSE; - if (*editor_elements_info[i].setup_value) { + boolean found_inactive_cascade = FALSE; + if (setup.editor.el_headlines) { // required for correct padding of palette headline buttons @@ -6423,11 +6579,9 @@ static void CreateCounterButtons(void) int graphic; struct GraphicInfo *gd; int gd_x1, gd_x2, gd_y1, gd_y2; - unsigned int event_mask; + unsigned int event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED; char infotext[max_infotext_len + 1]; - event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED; - if (i == ED_COUNTER_ID_SELECT_LEVEL) { graphic = (j == 0 ? @@ -6567,7 +6721,6 @@ static void CreateDrawingAreas(void) for (i = 0; i < ED_NUM_DRAWING_AREAS; i++) { struct GadgetInfo *gi; - unsigned int event_mask; int id = drawingarea_info[i].gadget_id; int x = SX + ED_AREA_SETTINGS_X(drawingarea_info[i]); int y = SY + ED_AREA_SETTINGS_Y(drawingarea_info[i]); @@ -6575,8 +6728,7 @@ static void CreateDrawingAreas(void) int area_ysize = drawingarea_info[i].area_ysize; int item_size = (id == GADGET_ID_DRAWING_LEVEL ? ed_tilesize : ED_DRAWINGAREA_TILE_SIZE); - - event_mask = + unsigned int event_mask = GD_EVENT_PRESSED | GD_EVENT_RELEASED | GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS | GD_EVENT_PIXEL_PRECISE; @@ -6623,7 +6775,7 @@ static void CreateTextInputGadgets(void) int gd_x2 = gd->src_x + gd->active_xoffset; int gd_y2 = gd->src_y + gd->active_yoffset; struct GadgetInfo *gi; - unsigned int event_mask; + unsigned int event_mask = GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING; char infotext[MAX_OUTPUT_LINESIZE + 1]; int id = textinput_info[i].gadget_id; int x, y; @@ -6650,8 +6802,6 @@ static void CreateTextInputGadgets(void) y = ED_SETTINGS_Y(textinput_info[i].y); } - event_mask = GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING; - sprintf(infotext, "Enter %s", textinput_info[i].infotext); infotext[max_infotext_len] = '\0'; @@ -6694,14 +6844,12 @@ static void CreateTextAreaGadgets(void) int gd_x2 = gd->src_x + gd->active_xoffset; int gd_y2 = gd->src_y + gd->active_yoffset; struct GadgetInfo *gi; - unsigned int event_mask; + unsigned int event_mask = GD_EVENT_TEXT_LEAVING; char infotext[MAX_OUTPUT_LINESIZE + 1]; int id = textarea_info[i].gadget_id; int area_xsize = textarea_info[i].xsize; int area_ysize = textarea_info[i].ysize; - event_mask = GD_EVENT_TEXT_LEAVING; - sprintf(infotext, "Enter %s", textarea_info[i].infotext); infotext[max_infotext_len] = '\0'; @@ -6745,11 +6893,12 @@ static void CreateSelectboxGadgets(void) int gd_y2 = gd->src_y + gd->active_yoffset; int selectbox_button_xsize = gd2->width; struct GadgetInfo *gi; - unsigned int event_mask; char infotext[MAX_OUTPUT_LINESIZE + 1]; int id = selectbox_info[i].gadget_id; int x = SX + ED_SETTINGS_X(selectbox_info[i].x); int y = SY + ED_SETTINGS_Y(selectbox_info[i].y); + unsigned int event_mask = + GD_EVENT_RELEASED | GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING; if (selectbox_info[i].size == -1) // dynamically determine size { @@ -6765,9 +6914,6 @@ static void CreateSelectboxGadgets(void) selectbox_info[i].size++; // add one character empty space } - event_mask = GD_EVENT_RELEASED | - GD_EVENT_TEXT_RETURN | GD_EVENT_TEXT_LEAVING; - // determine horizontal position to the right of specified gadget if (selectbox_info[i].gadget_id_align != GADGET_ID_NONE) x = (right_gadget_border[selectbox_info[i].gadget_id_align] + @@ -6820,7 +6966,7 @@ static void CreateTextbuttonGadgets(void) { int id = textbutton_info[i].gadget_id; int is_tab_button = - ((id >= GADGET_ID_LEVELINFO_LEVEL && id <= GADGET_ID_LEVELINFO_EDITOR) || + ((id >= GADGET_ID_LEVELCONFIG_LEVEL && id <= GADGET_ID_LEVELCONFIG_EDITOR) || (id >= GADGET_ID_PROPERTIES_INFO && id <= GADGET_ID_PROPERTIES_CHANGE)); int graphic = (is_tab_button ? IMG_EDITOR_TABBUTTON : IMG_EDITOR_TEXTBUTTON); @@ -6836,7 +6982,7 @@ static void CreateTextbuttonGadgets(void) int border_xsize = gd->border_size + gd->draw_xoffset; int border_ysize = gd->border_size; struct GadgetInfo *gi; - unsigned int event_mask; + unsigned int event_mask = GD_EVENT_RELEASED; char infotext[MAX_OUTPUT_LINESIZE + 1]; int x = SX + ED_SETTINGS_X(textbutton_info[i].x); int y = SY + ED_SETTINGS_Y(textbutton_info[i].y); @@ -6844,8 +6990,6 @@ static void CreateTextbuttonGadgets(void) if (textbutton_info[i].size == -1) // dynamically determine size textbutton_info[i].size = strlen(textbutton_info[i].text); - event_mask = GD_EVENT_RELEASED; - sprintf(infotext, "%s", textbutton_info[i].infotext); infotext[max_infotext_len] = '\0'; @@ -6898,7 +7042,6 @@ static void CreateTextbuttonGadgets(void) static void CreateGraphicbuttonGadgets(void) { struct GadgetInfo *gi; - unsigned int event_mask; int i; // create buttons for scrolling of drawing area and element list @@ -6913,8 +7056,7 @@ static void CreateGraphicbuttonGadgets(void) int gd_y1 = gd->src_y; int gd_x2 = gd->src_x + gd->pressed_xoffset; int gd_y2 = gd->src_y + gd->pressed_yoffset; - - event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED; + unsigned int event_mask = GD_EVENT_RELEASED; // determine horizontal position to the right of specified gadget if (graphicbutton_info[i].gadget_id_align != GADGET_ID_NONE) @@ -7006,7 +7148,7 @@ static void CreateScrollbarGadgets(void) int gd_y2 = gd->src_y + gd->pressed_yoffset; struct GadgetInfo *gi; int items_max, items_visible, item_position; - unsigned int event_mask; + unsigned int event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS; if (i == ED_SCROLLBAR_ID_LIST_VERTICAL) { @@ -7030,8 +7172,6 @@ static void CreateScrollbarGadgets(void) } } - event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS; - gi = CreateGadget(GDI_CUSTOM_ID, id, GDI_CUSTOM_TYPE_ID, i, GDI_IMAGE_ID, graphic, @@ -7067,11 +7207,8 @@ static void CreateScrollbarGadgets(void) static void CreateCheckbuttonGadgets(void) { struct GadgetInfo *gi; - unsigned int event_mask; int i; - event_mask = GD_EVENT_PRESSED; - for (i = 0; i < ED_NUM_CHECKBUTTONS; i++) { int id = checkbutton_info[i].gadget_id; @@ -7088,6 +7225,7 @@ static void CreateCheckbuttonGadgets(void) int gd_y2a = gd->src_y + gd->active_yoffset + gd->pressed_yoffset; int x = SX + ED_SETTINGS_X(checkbutton_info[i].x); int y = SY + ED_SETTINGS_Y(checkbutton_info[i].y); + unsigned int event_mask = GD_EVENT_PRESSED; // determine horizontal position to the right of specified gadget if (checkbutton_info[i].gadget_id_align != GADGET_ID_NONE) @@ -7139,16 +7277,14 @@ static void CreateRadiobuttonGadgets(void) int gd_x2a = gd->src_x + gd->active_xoffset + gd->pressed_xoffset; int gd_y2a = gd->src_y + gd->active_yoffset + gd->pressed_yoffset; struct GadgetInfo *gi; - unsigned int event_mask; int i; - event_mask = GD_EVENT_PRESSED; - for (i = 0; i < ED_NUM_RADIOBUTTONS; i++) { int id = radiobutton_info[i].gadget_id; int x = SX + ED_SETTINGS_X(radiobutton_info[i].x); int y = SY + ED_SETTINGS_Y(radiobutton_info[i].y); + unsigned int event_mask = GD_EVENT_PRESSED; int checked = (*radiobutton_info[i].value == radiobutton_info[i].checked_value); @@ -7217,6 +7353,8 @@ void CreateLevelEditorGadgets(void) use_permanent_palette = !editor.palette.show_as_separate_screen; + InitGadgetScreenBorders(-1, INFOTEXT_YPOS); + ReinitializeElementList(); CreateControlButtons(); @@ -7409,9 +7547,13 @@ static void MapTextAreaGadget(int id) int yoffset_above = font_height + ED_GADGET_LINE_DISTANCE; int x_above = ED_SETTINGS_X(textarea_info[id].x); int y_above = ED_SETTINGS_Y(textarea_info[id].y) - yoffset_above; + char *text_above = textarea_info[id].text_above; + + if (gi->textarea.cropped && textarea_info[id].text_above_cropped) + text_above = textarea_info[id].text_above_cropped; - if (textarea_info[id].text_above) - DrawTextS(x_above, y_above, font_nr, textarea_info[id].text_above); + if (text_above) + DrawTextS(x_above, y_above, font_nr, text_above); ModifyGadget(gi, GDI_TEXT_VALUE, textarea_info[id].value, GDI_END); @@ -7540,7 +7682,7 @@ static void MapCheckbuttonGadget(int id) // set position for gadgets with dynamically determined position if (checkbutton_info[id].x != -1) // do not change dynamic positions - ModifyGadget(gi, GDI_X, SX + ED_SETTINGS_X(checkbutton_info[id].x),GDI_END); + ModifyGadget(gi, GDI_X, SX + ED_SETTINGS_X(checkbutton_info[id].x), GDI_END); ModifyGadget(gi, GDI_Y, SY + ED_SETTINGS_Y(checkbutton_info[id].y), GDI_END); x_left = gi->x - xoffset_left; @@ -7623,6 +7765,14 @@ static void MapLevelEditorToolboxCustomGadgets(void) MapOrUnmapLevelEditorToolboxCustomGadgets(TRUE); } +static void MapLevelEditorToolboxCustomGadgetsIfNeeded(void) +{ + if (IS_CUSTOM_ELEMENT(properties_element) || + IS_GROUP_ELEMENT(properties_element) || + IS_EMPTY_ELEMENT(properties_element)) + MapLevelEditorToolboxCustomGadgets(); +} + static void UnmapLevelEditorToolboxCustomGadgets(void) { MapOrUnmapLevelEditorToolboxCustomGadgets(FALSE); @@ -7711,8 +7861,8 @@ static void DrawEditModeWindowExt(boolean remap_toolbox_gadgets) RedrawDrawingElements(); } - if (edit_mode == ED_MODE_INFO) - DrawLevelInfoWindow(); + if (edit_mode == ED_MODE_LEVELCONFIG) + DrawLevelConfigWindow(); else if (edit_mode == ED_MODE_PROPERTIES) DrawPropertiesWindow(); else if (edit_mode == ED_MODE_PALETTE) @@ -7812,7 +7962,7 @@ static boolean PrepareSavingIntoPersonalLevelSet(void) return TRUE; } -static void ModifyLevelInfoForSavingIntoPersonalLevelSet(char *former_name) +static void ModifyLevelConfigForSavingIntoPersonalLevelSet(char *former_name) { static char *filename_levelinfo = NULL, *mod_name = NULL; FILE *file; @@ -8045,6 +8195,12 @@ static boolean CopyCustomElement(int element_old, int element_new, return FALSE; } + else if (IS_EMPTY_ELEMENT(element_old) && !IS_EMPTY_ELEMENT(element_new)) + { + Request("Please choose empty element!", REQ_CONFIRM); + + return FALSE; + } else { level.changed = TRUE; @@ -8187,7 +8343,8 @@ static void CopyCustomElementPropertiesToEditor(int element) // set "change by direct action" selectbox help value custom_element_change.direct_action = - (HAS_CHANGE_EVENT(element, CE_TOUCHED_BY_PLAYER) ? CE_TOUCHED_BY_PLAYER : + (HAS_CHANGE_EVENT(element, CE_NEXT_TO_PLAYER) ? CE_NEXT_TO_PLAYER : + HAS_CHANGE_EVENT(element, CE_TOUCHED_BY_PLAYER) ? CE_TOUCHED_BY_PLAYER : HAS_CHANGE_EVENT(element, CE_PRESSED_BY_PLAYER) ? CE_PRESSED_BY_PLAYER : HAS_CHANGE_EVENT(element, CE_SWITCHED_BY_PLAYER) ? CE_SWITCHED_BY_PLAYER : HAS_CHANGE_EVENT(element, CE_SNAPPED_BY_PLAYER) ? CE_SNAPPED_BY_PLAYER : @@ -8211,7 +8368,8 @@ static void CopyCustomElementPropertiesToEditor(int element) // set "change by other element action" selectbox help value custom_element_change.other_action = - (HAS_CHANGE_EVENT(element, CE_PLAYER_TOUCHES_X) ? CE_PLAYER_TOUCHES_X : + (HAS_CHANGE_EVENT(element, CE_PLAYER_NEXT_TO_X) ? CE_PLAYER_NEXT_TO_X : + HAS_CHANGE_EVENT(element, CE_PLAYER_TOUCHES_X) ? CE_PLAYER_TOUCHES_X : HAS_CHANGE_EVENT(element, CE_PLAYER_PRESSES_X) ? CE_PLAYER_PRESSES_X : HAS_CHANGE_EVENT(element, CE_PLAYER_SWITCHES_X) ? CE_PLAYER_SWITCHES_X : HAS_CHANGE_EVENT(element, CE_PLAYER_SNAPS_X) ? CE_PLAYER_SNAPS_X : @@ -8221,6 +8379,7 @@ static void CopyCustomElementPropertiesToEditor(int element) HAS_CHANGE_EVENT(element, CE_PLAYER_DIGS_X) ? CE_PLAYER_DIGS_X : HAS_CHANGE_EVENT(element, CE_PLAYER_COLLECTS_X) ? CE_PLAYER_COLLECTS_X : HAS_CHANGE_EVENT(element, CE_PLAYER_DROPS_X) ? CE_PLAYER_DROPS_X : + HAS_CHANGE_EVENT(element, CE_NEXT_TO_X) ? CE_NEXT_TO_X : HAS_CHANGE_EVENT(element, CE_TOUCHING_X) ? CE_TOUCHING_X : HAS_CHANGE_EVENT(element, CE_HITTING_X) ? CE_HITTING_X : HAS_CHANGE_EVENT(element, CE_DIGGING_X) ? CE_DIGGING_X : @@ -8245,9 +8404,14 @@ static void CopyGroupElementPropertiesToEditor(int element) custom_element = element_info[element]; // needed for description } +static void CopyEmptyElementPropertiesToEditor(int element) +{ + custom_element = element_info[element]; +} + static void CopyClassicElementPropertiesToEditor(int element) { - if (ELEM_IS_PLAYER(element) || COULD_MOVE_INTO_ACID(element)) + if (IS_PLAYER_ELEMENT(element) || COULD_MOVE_INTO_ACID(element)) custom_element_properties[EP_CAN_MOVE_INTO_ACID] = getMoveIntoAcidProperty(&level, element); @@ -8262,6 +8426,8 @@ static void CopyElementPropertiesToEditor(int element) CopyCustomElementPropertiesToEditor(element); else if (IS_GROUP_ELEMENT(element)) CopyGroupElementPropertiesToEditor(element); + else if (IS_EMPTY_ELEMENT(element)) + CopyEmptyElementPropertiesToEditor(element); else CopyClassicElementPropertiesToEditor(element); } @@ -8349,6 +8515,7 @@ static void CopyCustomElementPropertiesToGame(int element) // ---------- element settings: advanced (custom elements) ------------------ // set player change event from checkbox and selectbox + custom_element_change_events[CE_NEXT_TO_PLAYER] = FALSE; custom_element_change_events[CE_TOUCHED_BY_PLAYER] = FALSE; custom_element_change_events[CE_PRESSED_BY_PLAYER] = FALSE; custom_element_change_events[CE_SWITCHED_BY_PLAYER] = FALSE; @@ -8373,6 +8540,7 @@ static void CopyCustomElementPropertiesToGame(int element) custom_element_change_events[CE_BY_DIRECT_ACTION]; // set other element action change event from checkbox and selectbox + custom_element_change_events[CE_PLAYER_NEXT_TO_X] = FALSE; custom_element_change_events[CE_PLAYER_TOUCHES_X] = FALSE; custom_element_change_events[CE_PLAYER_PRESSES_X] = FALSE; custom_element_change_events[CE_PLAYER_SWITCHES_X] = FALSE; @@ -8383,6 +8551,7 @@ static void CopyCustomElementPropertiesToGame(int element) custom_element_change_events[CE_PLAYER_DIGS_X] = FALSE; custom_element_change_events[CE_PLAYER_COLLECTS_X] = FALSE; custom_element_change_events[CE_PLAYER_DROPS_X] = FALSE; + custom_element_change_events[CE_NEXT_TO_X] = FALSE; custom_element_change_events[CE_TOUCHING_X] = FALSE; custom_element_change_events[CE_HITTING_X] = FALSE; custom_element_change_events[CE_DIGGING_X] = FALSE; @@ -8431,9 +8600,24 @@ static void CopyGroupElementPropertiesToGame(int element) InitElementPropertiesGfxElement(); } +static void CopyEmptyElementPropertiesToGame(int element) +{ + // mark that this empty element has been modified + custom_element.modified_settings = TRUE; + level.changed = TRUE; + + if (level.use_custom_template) + AskToCopyAndModifyLevelTemplate(); + + element_info[element] = custom_element; + + // needed here to restore runtime value "element_info[element].gfx_element" + InitElementPropertiesGfxElement(); +} + static void CopyClassicElementPropertiesToGame(int element) { - if (ELEM_IS_PLAYER(element) || COULD_MOVE_INTO_ACID(element)) + if (IS_PLAYER_ELEMENT(element) || COULD_MOVE_INTO_ACID(element)) setMoveIntoAcidProperty(&level, element, custom_element_properties[EP_CAN_MOVE_INTO_ACID]); @@ -8448,6 +8632,8 @@ static void CopyElementPropertiesToGame(int element) CopyCustomElementPropertiesToGame(element); else if (IS_GROUP_ELEMENT(element)) CopyGroupElementPropertiesToGame(element); + else if (IS_EMPTY_ELEMENT(element)) + CopyEmptyElementPropertiesToGame(element); else CopyClassicElementPropertiesToGame(element); } @@ -8625,6 +8811,15 @@ static void DrawEditorDoorContent(void) // draw all toolbox gadgets to editor doors MapControlButtons(); + // when returning from test game to properties page, redraw toolbox gadgets + if (edit_mode == ED_MODE_PROPERTIES) + { + UnmapLevelEditorToolboxDrawingGadgets(); + UnmapLevelEditorToolboxCustomGadgets(); + + MapLevelEditorToolboxCustomGadgetsIfNeeded(); + } + // draw all palette gadgets to editor doors ModifyEditorElementList(); RedrawDrawingElements(); @@ -8667,7 +8862,7 @@ void DrawLevelEd(void) else { edit_mode = ED_MODE_DRAWING; - edit_mode_levelinfo = ED_MODE_LEVELINFO_LEVEL; + edit_mode_levelconfig = ED_MODE_LEVELCONFIG_LEVEL; edit_mode_properties = ED_MODE_PROPERTIES_INFO; ResetUndoBuffer(); @@ -8723,8 +8918,8 @@ static void AdjustDrawingAreaGadgets(void) if (suppressBorderElement()) { - ed_xsize = max_ed_fieldx; - ed_ysize = max_ed_fieldy; + ed_xsize = lev_fieldx; + ed_ysize = lev_fieldy; } // check if we need any scrollbars @@ -9050,7 +9245,7 @@ static int getTabulatorBarHeight(void) static Pixel getTabulatorBarColor(void) { - struct GadgetInfo *gd_gi1 = level_editor_gadget[GADGET_ID_LEVELINFO_LEVEL]; + struct GadgetInfo *gd_gi1 = level_editor_gadget[GADGET_ID_LEVELCONFIG_LEVEL]; struct GadgetDesign *gd = &gd_gi1->alt_design[GD_BUTTON_UNPRESSED]; int gd_x = gd->x + gd_gi1->border.width / 2; int gd_y = gd->y + gd_gi1->height - 1; @@ -9058,19 +9253,19 @@ static Pixel getTabulatorBarColor(void) return GetPixel(gd->bitmap, gd_x, gd_y); } -static void DrawLevelInfoTabulatorGadgets(void) +static void DrawLevelConfigTabulatorGadgets(void) { - struct GadgetInfo *gd_gi1 = level_editor_gadget[GADGET_ID_LEVELINFO_LEVEL]; + struct GadgetInfo *gd_gi1 = level_editor_gadget[GADGET_ID_LEVELCONFIG_LEVEL]; Pixel tab_color = getTabulatorBarColor(); - int id_first = ED_TAB_BUTTON_ID_LEVELINFO_FIRST; - int id_last = ED_TAB_BUTTON_ID_LEVELINFO_LAST; + int id_first = ED_TAB_BUTTON_ID_LEVELCONFIG_FIRST; + int id_last = ED_TAB_BUTTON_ID_LEVELCONFIG_LAST; int i; for (i = id_first; i <= id_last; i++) { int gadget_id = textbutton_info[i].gadget_id; struct GadgetInfo *gi = level_editor_gadget[gadget_id]; - boolean active = (i != edit_mode_levelinfo); + boolean active = (i != edit_mode_levelconfig); // draw background line below tabulator button ClearRectangleOnBackground(drawto, gi->x, gi->y + gi->height, gi->width, 1); @@ -9103,7 +9298,7 @@ static void DrawPropertiesTabulatorGadgets(void) int i; // draw two config tabulators for player elements - if (ELEM_IS_PLAYER(properties_element)) + if (IS_PLAYER_ELEMENT(properties_element)) id_last = ED_TEXTBUTTON_ID_PROPERTIES_CONFIG_2; // draw two config and one "change" tabulator for custom elements @@ -9118,7 +9313,7 @@ static void DrawPropertiesTabulatorGadgets(void) // use "config 1" and "config 2" instead of "config" for players and CEs if (i == ED_TEXTBUTTON_ID_PROPERTIES_CONFIG && - (ELEM_IS_PLAYER(properties_element) || + (IS_PLAYER_ELEMENT(properties_element) || IS_CUSTOM_ELEMENT(properties_element))) continue; @@ -9159,7 +9354,7 @@ static int PrintElementDescriptionFromFile(char *filename, int font_nr, TRUE, FALSE, FALSE); } -static void DrawLevelInfoLevel(void) +static void DrawLevelConfigLevel(void) { int i; @@ -9188,7 +9383,7 @@ static char *getLevelSubdirFromSaveMode(int save_mode) return leveldir_current->subdir; } -static void DrawLevelInfoLevelSet_DirectoryInfo(void) +static void DrawLevelConfigLevelSet_DirectoryInfo(void) { char *directory_text = "Level set directory:"; char *directory_name = getLevelSubdirFromSaveMode(levelset_save_mode); @@ -9203,7 +9398,7 @@ static void DrawLevelInfoLevelSet_DirectoryInfo(void) PrintInfoText(directory_name, font2_nr, x, y); } -static void DrawLevelInfoLevelSet(void) +static void DrawLevelConfigLevelSet(void) { boolean artwork_exists = checkIfCustomArtworkExistsForCurrentLevelSet(); boolean template_exists = fileExists(getLocalLevelTemplateFilename()); @@ -9236,10 +9431,10 @@ static void DrawLevelInfoLevelSet(void) MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_LEVELSET); // draw info text - DrawLevelInfoLevelSet_DirectoryInfo(); + DrawLevelConfigLevelSet_DirectoryInfo(); } -static void DrawLevelInfoEditor(void) +static void DrawLevelConfigEditor(void) { int i; @@ -9262,7 +9457,7 @@ static void DrawLevelInfoEditor(void) MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_AS_TEMPLATE_2); } -static void DrawLevelInfoWindow(void) +static void DrawLevelConfigWindow(void) { char *text = "Global Settings"; int font_nr = FONT_TITLE_1; @@ -9281,14 +9476,14 @@ static void DrawLevelInfoWindow(void) DrawText(sx, sy, text, font_nr); - DrawLevelInfoTabulatorGadgets(); + DrawLevelConfigTabulatorGadgets(); - if (edit_mode_levelinfo == ED_MODE_LEVELINFO_LEVEL) - DrawLevelInfoLevel(); - else if (edit_mode_levelinfo == ED_MODE_LEVELINFO_LEVELSET) - DrawLevelInfoLevelSet(); - else if (edit_mode_levelinfo == ED_MODE_LEVELINFO_EDITOR) - DrawLevelInfoEditor(); + if (edit_mode_levelconfig == ED_MODE_LEVELCONFIG_LEVEL) + DrawLevelConfigLevel(); + else if (edit_mode_levelconfig == ED_MODE_LEVELCONFIG_LEVELSET) + DrawLevelConfigLevelSet(); + else if (edit_mode_levelconfig == ED_MODE_LEVELCONFIG_EDITOR) + DrawLevelConfigEditor(); } static void DrawCustomContentArea(void) @@ -9403,7 +9598,7 @@ static void DrawMagicBallContentAreas(void) DrawText(x, y + 2 * tilesize, "active", font_nr); } -static void DrawAndroidElementArea(int element) +static void DrawAndroidElementArea(void) { int id = ED_DRAWING_ID_ANDROID_CONTENT; int num_elements = level.num_android_clone_elements; @@ -9431,7 +9626,7 @@ static void DrawAndroidElementArea(int element) MapDrawingArea(id); } -static void DrawGroupElementArea(int element) +static void DrawGroupElementArea(void) { int id = ED_DRAWING_ID_GROUP_CONTENT; int num_elements = group_element_info.num_elements; @@ -9488,13 +9683,40 @@ static void DrawPlayerInitialInventoryArea(int element) MapDrawingArea(id); } +static void DrawMMBallContentArea(void) +{ + int id = ED_DRAWING_ID_MM_BALL_CONTENT; + int num_elements = level.num_mm_ball_contents; + int border_size = ED_DRAWINGAREA_BORDER_SIZE; + int sx = SX + ED_AREA_SETTINGS_X(drawingarea_info[id]) - border_size; + int sy = SY + ED_AREA_SETTINGS_Y(drawingarea_info[id]) - border_size; + int xsize = MAX_MM_BALL_CONTENTS; + int ysize = 1; + + if (drawingarea_info[id].text_left != NULL) + sx += getTextWidthForDrawingArea(drawingarea_info[id].text_left); + + UnmapDrawingArea(id); + + ModifyEditorDrawingArea(id, num_elements, 1); + + // delete content areas in case of reducing number of them + DrawBackground(sx, sy, + xsize * ED_DRAWINGAREA_TILE_SIZE + 2 * border_size, + ysize * ED_DRAWINGAREA_TILE_SIZE + 2 * border_size); + + MapDrawingArea(id); +} + static void DrawEnvelopeTextArea(int envelope_nr) { int id = ED_TEXTAREA_ID_ENVELOPE_INFO; struct GadgetInfo *gi = level_editor_gadget[textarea_info[id].gadget_id]; UnmapGadget(gi); - DrawBackground(gi->x, gi->y, gi->width, gi->height); + + DrawBackground(gi->x, gi->y, + gi->textarea.crop_width, gi->textarea.crop_height); if (envelope_nr != -1) textarea_info[id].value = level.envelope[envelope_nr].text; @@ -9568,13 +9790,16 @@ static void DrawPropertiesInfo(void) { -1, NULL } }; char *filename = getElementDescriptionFilename(properties_element); - char *percentage_text = "In this level: "; + char *num_elements_text = "In this level: "; + char *num_similar_text = "Similar tiles: "; char *properties_text = "Standard properties: "; char *description_text = "Description:"; char *no_description_text = "No description available."; char *none_text = "None"; float percentage; - int num_elements_in_level; + int num_elements_in_level = 0; + int num_similar_in_level = 0; + int num_hires_tiles_in_level = 0; int num_standard_properties = 0; int font1_nr = FONT_TEXT_1; int font2_nr = FONT_TEXT_2; @@ -9583,7 +9808,8 @@ static void DrawPropertiesInfo(void) int font2_height = getFontHeight(font2_nr); int line1_height = font1_height + ED_GADGET_LINE_DISTANCE; int font2_yoffset = (font1_height - font2_height) / 2; - int percentage_text_len = strlen(percentage_text) * font1_width; + int num_elements_text_len = strlen(num_elements_text) * font1_width; + int num_similar_text_len = strlen(num_similar_text) * font1_width; int properties_text_len = strlen(properties_text) * font1_width; int xpos = ED_ELEMENT_SETTINGS_X(0); int ypos = ED_ELEMENT_SETTINGS_Y(0) + ED_GADGET_SMALL_DISTANCE; @@ -9602,22 +9828,66 @@ static void DrawPropertiesInfo(void) // ----- print number of elements / percentage of this element in level - num_elements_in_level = 0; - for (y = 0; y < lev_fieldy; y++) + for (y = 0; y < lev_fieldy; y++) + { for (x = 0; x < lev_fieldx; x++) + { if (Tile[x][y] == properties_element) + { num_elements_in_level++; + } + else if (IS_MM_WALL(Tile[x][y]) && + map_mm_wall_element(Tile[x][y]) == properties_element) + { + num_hires_tiles_in_level += numHiresTiles(Tile[x][y]); + } + } + } + percentage = num_elements_in_level * 100.0 / (lev_fieldx * lev_fieldy); - DrawTextS(xpos, ypos, font1_nr, percentage_text); + DrawTextS(xpos, ypos, font1_nr, num_elements_text); - if (num_elements_in_level > 0) - DrawTextF(xpos + percentage_text_len, ypos + font2_yoffset, font2_nr, + if (num_hires_tiles_in_level > 0) + DrawTextF(xpos + num_elements_text_len, ypos + font2_yoffset, font2_nr, + "%d wall tiles", num_hires_tiles_in_level); + else if (num_elements_in_level > 0) + DrawTextF(xpos + num_elements_text_len, ypos + font2_yoffset, font2_nr, "%d (%.2f %%)", num_elements_in_level, percentage); else - DrawTextF(xpos + percentage_text_len, ypos + font2_yoffset, font2_nr, + DrawTextF(xpos + num_elements_text_len, ypos + font2_yoffset, font2_nr, none_text); + // ----- print number of similar elements / percentage of them in level + + for (y = 0; y < lev_fieldy; y++) + { + for (x = 0; x < lev_fieldx; x++) + { + if (strEqual(element_info[Tile[x][y]].class_name, + element_info[properties_element].class_name)) + { + num_similar_in_level++; + } + } + } + + if (num_similar_in_level != num_elements_in_level) + { + ypos += 1 * MAX(font1_height, font2_height); + + percentage = num_similar_in_level * 100.0 / (lev_fieldx * lev_fieldy); + + DrawTextS(xpos, ypos, font1_nr, num_similar_text); + + if (num_similar_in_level > 0) + DrawTextF(xpos + num_similar_text_len, ypos + font2_yoffset, font2_nr, + "%d (%.2f %%)", num_similar_in_level, percentage); + else + DrawTextF(xpos + num_similar_text_len, ypos + font2_yoffset, font2_nr, + none_text); + } + ypos += 2 * MAX(font1_height, font2_height); // ----- print standard properties of this element @@ -9666,6 +9936,7 @@ static void DrawPropertiesInfo(void) #define TEXT_DURATION "Duration when activated" #define TEXT_DELAY_ON "Delay before activating" #define TEXT_DELAY_OFF "Delay before deactivating" +#define TEXT_DELAY_CHANGING "Delay before changing" #define TEXT_DELAY_EXPLODING "Delay before exploding" #define TEXT_DELAY_MOVING "Delay before moving" #define TEXT_BALL_DELAY "Element generation delay" @@ -9730,9 +10001,9 @@ static struct { EL_NUT, &level.score[SC_NUT], TEXT_CRACKING }, { EL_DYNAMITE, &level.score[SC_DYNAMITE], TEXT_COLLECTING }, { EL_EM_DYNAMITE, &level.score[SC_DYNAMITE], TEXT_COLLECTING }, - { EL_DYNABOMB_INCREASE_NUMBER,&level.score[SC_DYNAMITE],TEXT_COLLECTING }, - { EL_DYNABOMB_INCREASE_SIZE, &level.score[SC_DYNAMITE],TEXT_COLLECTING }, - { EL_DYNABOMB_INCREASE_POWER, &level.score[SC_DYNAMITE],TEXT_COLLECTING }, + { EL_DYNABOMB_INCREASE_NUMBER,&level.score[SC_DYNAMITE], TEXT_COLLECTING }, + { EL_DYNABOMB_INCREASE_SIZE, &level.score[SC_DYNAMITE], TEXT_COLLECTING }, + { EL_DYNABOMB_INCREASE_POWER, &level.score[SC_DYNAMITE], TEXT_COLLECTING }, { EL_SHIELD_NORMAL, &level.score[SC_SHIELD], TEXT_COLLECTING }, { EL_SHIELD_DEADLY, &level.score[SC_SHIELD], TEXT_COLLECTING }, { EL_EXTRA_TIME, &level.extra_time_score, TEXT_COLLECTING }, @@ -9797,7 +10068,7 @@ static struct { EL_EMC_MAGNIFIER, &level.magnify_time, TEXT_DURATION }, { EL_MM_FUSE_ACTIVE, &level.mm_time_fuse, TEXT_DELAY_OFF }, { EL_MM_BOMB, &level.mm_time_bomb, TEXT_DELAY_EXPLODING }, - { EL_MM_GRAY_BALL, &level.mm_time_ball, TEXT_DELAY_ON }, + { EL_MM_GRAY_BALL, &level.mm_time_ball, TEXT_DELAY_CHANGING }, { EL_MM_STEEL_BLOCK, &level.mm_time_block, TEXT_DELAY_MOVING }, { EL_MM_WOODEN_BLOCK, &level.mm_time_block, TEXT_DELAY_MOVING }, @@ -9811,11 +10082,13 @@ static boolean checkPropertiesConfig(int element) if (IS_GEM(element) || IS_CUSTOM_ELEMENT(element) || IS_GROUP_ELEMENT(element) || + IS_EMPTY_ELEMENT(element) || IS_BALLOON_ELEMENT(element) || IS_ENVELOPE(element) || + IS_MM_ENVELOPE(element) || IS_MM_MCDUFFIN(element) || IS_DF_LASER(element) || - ELEM_IS_PLAYER(element) || + IS_PLAYER_ELEMENT(element) || HAS_EDITOR_CONTENT(element) || CAN_GROW(element) || COULD_MOVE_INTO_ACID(element) || @@ -9969,10 +10242,19 @@ static void DrawPropertiesConfig(void) MapCheckbuttonGadget(ED_CHECKBUTTON_ID_INITIAL_BALL_ACTIVE); } else if (properties_element == EL_EMC_ANDROID) - DrawAndroidElementArea(properties_element); + DrawAndroidElementArea(); + else if (properties_element == EL_MM_GRAY_BALL) + { + MapCounterButtons(ED_COUNTER_ID_MM_BALL_CONTENT); + MapSelectboxGadget(ED_SELECTBOX_ID_MM_BALL_CHOICE_MODE); + MapCheckbuttonGadget(ED_CHECKBUTTON_ID_ROTATE_MM_BALL_CONTENT); + MapCheckbuttonGadget(ED_CHECKBUTTON_ID_EXPLODE_MM_BALL); + + DrawMMBallContentArea(); + } } - if (ELEM_IS_PLAYER(properties_element)) + if (IS_PLAYER_ELEMENT(properties_element)) { int player_nr = GET_PLAYER_NR(properties_element); @@ -10035,6 +10317,7 @@ static void DrawPropertiesConfig(void) // draw checkbutton gadgets MapCheckbuttonGadget(ED_CHECKBUTTON_ID_USE_INITIAL_INVENTORY); MapCheckbuttonGadget(ED_CHECKBUTTON_ID_FINISH_DIG_COLLECT); + MapCheckbuttonGadget(ED_CHECKBUTTON_ID_KEEP_WALKABLE_CE); // draw counter gadgets MapCounterButtons(ED_COUNTER_ID_INVENTORY_SIZE); @@ -10051,7 +10334,7 @@ static void DrawPropertiesConfig(void) MapCheckbuttonGadget(ED_CHECKBUTTON_ID_EM_EXPLODES_BY_FIRE); if (COULD_MOVE_INTO_ACID(properties_element) && - !ELEM_IS_PLAYER(properties_element) && + !IS_PLAYER_ELEMENT(properties_element) && (!IS_CUSTOM_ELEMENT(properties_element) || edit_mode_properties == ED_MODE_PROPERTIES_CONFIG_2)) { @@ -10109,13 +10392,14 @@ static void DrawPropertiesConfig(void) if (IS_BALLOON_ELEMENT(properties_element)) MapSelectboxGadget(ED_SELECTBOX_ID_WIND_DIRECTION); - if (IS_ENVELOPE(properties_element)) + if (IS_ENVELOPE(properties_element) || + IS_MM_ENVELOPE(properties_element)) { int counter1_id = ED_COUNTER_ID_ENVELOPE_XSIZE; int counter2_id = ED_COUNTER_ID_ENVELOPE_YSIZE; int button1_id = ED_CHECKBUTTON_ID_ENVELOPE_AUTOWRAP; int button2_id = ED_CHECKBUTTON_ID_ENVELOPE_CENTERED; - int envelope_nr = properties_element - EL_ENVELOPE_1; + int envelope_nr = ENVELOPE_NR(properties_element); counterbutton_info[counter1_id].value = &level.envelope[envelope_nr].xsize; counterbutton_info[counter2_id].value = &level.envelope[envelope_nr].ysize; @@ -10223,7 +10507,7 @@ static void DrawPropertiesConfig(void) MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_AS_TEMPLATE_1); // draw drawing area gadgets - DrawGroupElementArea(properties_element); + DrawGroupElementArea(); // draw text input gadgets MapTextInputGadget(ED_TEXTINPUT_ID_ELEMENT_NAME); @@ -10233,6 +10517,23 @@ static void DrawPropertiesConfig(void) draw_footer_line = TRUE; } + else if (IS_EMPTY_ELEMENT(properties_element)) + { + // draw stickybutton gadget + MapCheckbuttonGadget(ED_CHECKBUTTON_ID_STICK_ELEMENT); + + // draw checkbutton gadgets + MapCheckbuttonGadget(ED_CHECKBUTTON_ID_CUSTOM_USE_GRAPHIC); + MapCheckbuttonGadget(ED_CHECKBUTTON_ID_CUSTOM_USE_TEMPLATE_1); + + // draw textbutton gadgets + MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_AS_TEMPLATE_1); + + // draw drawing area gadgets + MapDrawingArea(ED_DRAWING_ID_CUSTOM_GRAPHIC); + + draw_footer_line = TRUE; + } // draw little footer border line above CE/GE use/save template gadgets if (draw_footer_line) @@ -10323,12 +10624,12 @@ static void DrawEditorElementName(int x, int y, int font_nr) int font_height = getFontHeight(font_nr); int max_text_width = SXSIZE - x - ED_ELEMENT_SETTINGS_X(0); int max_chars_per_line = max_text_width / font_width; - char buffer[max_chars_per_line + 1]; if (strlen(element_name) <= max_chars_per_line) DrawTextS(x, y, font_nr, element_name); else { + char buffer[max_chars_per_line + 1]; int next_pos = max_chars_per_line; strncpy(buffer, element_name, max_chars_per_line); @@ -10389,12 +10690,12 @@ static void DrawPropertiesWindow(void) edit_mode_properties = ED_MODE_PROPERTIES_CONFIG_2; if (edit_mode_properties > ED_MODE_PROPERTIES_CONFIG && - !ELEM_IS_PLAYER(properties_element) && + !IS_PLAYER_ELEMENT(properties_element) && !IS_CUSTOM_ELEMENT(properties_element)) edit_mode_properties = ED_MODE_PROPERTIES_CONFIG; if (edit_mode_properties == ED_MODE_PROPERTIES_CONFIG && - (ELEM_IS_PLAYER(properties_element) || + (IS_PLAYER_ELEMENT(properties_element) || IS_CUSTOM_ELEMENT(properties_element))) edit_mode_properties = ED_MODE_PROPERTIES_CONFIG_1; @@ -10404,9 +10705,7 @@ static void DrawPropertiesWindow(void) UnmapLevelEditorToolboxDrawingGadgets(); UnmapLevelEditorToolboxCustomGadgets(); - if (IS_CUSTOM_ELEMENT(properties_element) || - IS_GROUP_ELEMENT(properties_element)) - MapLevelEditorToolboxCustomGadgets(); + MapLevelEditorToolboxCustomGadgetsIfNeeded(); SetMainBackgroundImage(IMG_BACKGROUND_EDITOR); ClearField(); @@ -10715,13 +11014,7 @@ static int getChipFromOpenDirectionNotEmpty(int direction, int element_old) static int getClosedTube(int x, int y) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; int element_old = IntelliDrawBuffer[x][y]; int direction_old = getOpenDirectionFromTube(element_old); int direction_new = MV_NONE; @@ -10729,8 +11022,8 @@ static int getClosedTube(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int dir = MV_DIR_FROM_BIT(i); int dir_opposite = MV_DIR_OPPOSITE(dir); @@ -10745,13 +11038,7 @@ static int getClosedTube(int x, int y) static int getClosedBelt(int x, int y) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; int element_old = IntelliDrawBuffer[x][y]; int nr = getBeltNrFromBeltElement(element_old); int direction_old = getOpenDirectionFromBelt(element_old); @@ -10760,8 +11047,8 @@ static int getClosedBelt(int x, int y) for (i = MV_BIT_LEFT; i <= MV_BIT_RIGHT; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int dir = MV_DIR_FROM_BIT(i); int dir_opposite = MV_DIR_OPPOSITE(dir); @@ -10776,13 +11063,7 @@ static int getClosedBelt(int x, int y) static int getClosedPool(int x, int y) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; int element_old = IntelliDrawBuffer[x][y]; int direction_old = getOpenDirectionFromPool(element_old); int direction_new = MV_NONE; @@ -10790,8 +11071,8 @@ static int getClosedPool(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int dir = MV_DIR_FROM_BIT(i); int dir_opposite = MV_DIR_OPPOSITE(dir); @@ -10807,13 +11088,7 @@ static int getClosedPool(int x, int y) static int getClosedPillar(int x, int y) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; int element_old = IntelliDrawBuffer[x][y]; int direction_old = getOpenDirectionFromPillar(element_old); int direction_new = MV_NONE; @@ -10821,8 +11096,8 @@ static int getClosedPillar(int x, int y) for (i = MV_BIT_UP; i <= MV_BIT_DOWN; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int dir = MV_DIR_FROM_BIT(i); int dir_opposite = MV_DIR_OPPOSITE(dir); @@ -10837,13 +11112,7 @@ static int getClosedPillar(int x, int y) static int getClosedSteel2(int x, int y) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; int element_old = IntelliDrawBuffer[x][y]; int direction_old = getOpenDirectionFromSteel2(element_old); int direction_new = MV_NONE; @@ -10851,8 +11120,8 @@ static int getClosedSteel2(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int dir = MV_DIR_FROM_BIT(i); int dir_opposite = MV_DIR_OPPOSITE(dir); @@ -10867,13 +11136,7 @@ static int getClosedSteel2(int x, int y) static int getClosedChip(int x, int y) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; int element_old = IntelliDrawBuffer[x][y]; int direction_old = getOpenDirectionFromChip(element_old); int direction_new = MV_NONE; @@ -10881,8 +11144,8 @@ static int getClosedChip(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int dir = MV_DIR_FROM_BIT(i); int dir_opposite = MV_DIR_OPPOSITE(dir); @@ -10962,16 +11225,10 @@ static void MergeAndCloseNeighbourElements(int x1, int y1, int *element1, SetElementSimple(x2, y2, *element2, change_level); } -static void SetElementIntelliDraw(int x, int y, int new_element, +static void SetElementIntelliDraw(int x, int y, int dx, int dy, int new_element, boolean change_level, int button) { - static int xy[4][2] = - { - { -1, 0 }, - { +1, 0 }, - { 0, -1 }, - { 0, +1 } - }; + struct XY *xy = xy_directions; static int last_x = -1; static int last_y = -1; @@ -10997,8 +11254,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_TUBE(IntelliDrawBuffer[last_x][last_y])) @@ -11035,8 +11292,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = MV_BIT_LEFT; i <= MV_BIT_RIGHT; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_BELT(IntelliDrawBuffer[last_x][last_y])) @@ -11073,8 +11330,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_ACID_POOL_OR_ACID(IntelliDrawBuffer[last_x][last_y])) @@ -11116,8 +11373,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = MV_BIT_UP; i <= MV_BIT_DOWN; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_EMC_PILLAR(IntelliDrawBuffer[last_x][last_y])) @@ -11153,8 +11410,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_DC_STEELWALL_2(IntelliDrawBuffer[last_x][last_y])) @@ -11188,8 +11445,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_SP_CHIP(IntelliDrawBuffer[last_x][last_y])) @@ -11249,8 +11506,8 @@ static void SetElementIntelliDraw(int x, int y, int new_element, for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (last_x == xx && last_y == yy && IN_LEV_FIELD(last_x, last_y) && IS_IN_GROUP_EL(IntelliDrawBuffer[last_x][last_y], new_element)) @@ -11451,6 +11708,12 @@ static void SetElementIntelliDraw(int x, int y, int new_element, EL_DF_RECEIVER_DOWN, EL_DF_RECEIVER_LEFT }, + { + EL_DF_SLOPE_1, + EL_DF_SLOPE_4, + EL_DF_SLOPE_3, + EL_DF_SLOPE_2 + }, { -1, @@ -11667,6 +11930,24 @@ static void SetElementIntelliDraw(int x, int y, int new_element, EL_DF_MIRROR_ROTATING_3, EL_DF_MIRROR_ROTATING_2 }, + { + EL_DF_MIRROR_FIXED_1, + EL_DF_MIRROR_FIXED_16, + EL_DF_MIRROR_FIXED_15, + EL_DF_MIRROR_FIXED_14, + EL_DF_MIRROR_FIXED_13, + EL_DF_MIRROR_FIXED_12, + EL_DF_MIRROR_FIXED_11, + EL_DF_MIRROR_FIXED_10, + EL_DF_MIRROR_FIXED_9, + EL_DF_MIRROR_FIXED_8, + EL_DF_MIRROR_FIXED_7, + EL_DF_MIRROR_FIXED_6, + EL_DF_MIRROR_FIXED_5, + EL_DF_MIRROR_FIXED_4, + EL_DF_MIRROR_FIXED_3, + EL_DF_MIRROR_FIXED_2 + }, { -1, @@ -11740,7 +12021,10 @@ static void SetElementIntelliDraw(int x, int y, int new_element, } } - SetElementSimple(x, y, new_element, change_level); + if (IS_MM_WALL_EDITOR(new_element)) + SetElementSimpleExt(x, y, dx, dy, new_element, change_level); + else + SetElementSimple(x, y, new_element, change_level); last_x = x; last_y = y; @@ -11754,7 +12038,7 @@ static void ResetIntelliDraw(void) for (y = 0; y < lev_fieldy; y++) IntelliDrawBuffer[x][y] = Tile[x][y]; - SetElementIntelliDraw(-1, -1, EL_UNDEFINED, FALSE, -1); + SetElementIntelliDraw(-1, -1, -1, -1, EL_UNDEFINED, FALSE, -1); } static boolean draw_mode_hires = FALSE; @@ -11769,6 +12053,14 @@ static boolean isHiresDrawElement(int element) return (IS_MM_WALL_EDITOR(element) || element == EL_EMPTY); } +static int numHiresTiles(int element) +{ + if (IS_MM_WALL(element)) + return get_number_of_bits(MM_WALL_BITS(element)); + + return 1; +} + static void SetDrawModeHiRes(int element) { draw_mode_hires = @@ -11796,8 +12088,8 @@ static void SetElementExt(int x, int y, int dx, int dy, int element, { if (element < 0) SetElementSimple(x, y, Tile[x][y], change_level); - else if (GetKeyModState() & KMOD_Shift && !IS_MM_WALL_EDITOR(element)) - SetElementIntelliDraw(x, y, element, change_level, button); + else if (GetKeyModState() & KMOD_Shift) + SetElementIntelliDraw(x, y, dx, dy, element, change_level, button); else SetElementSimpleExt(x, y, dx, dy, element, change_level); } @@ -11999,7 +12291,7 @@ static void DrawCircle(int from_x, int from_y, int to_x, int to_y, DrawArcExt(from_x, from_y, to_x2, to_y2, element, change_level); DrawArcExt(from_x, from_y, mirror_to_x2, to_y2, element, change_level); DrawArcExt(from_x, from_y, to_x2, mirror_to_y2, element, change_level); - DrawArcExt(from_x, from_y, mirror_to_x2, mirror_to_y2, element,change_level); + DrawArcExt(from_x, from_y, mirror_to_x2, mirror_to_y2, element, change_level); } #endif @@ -12056,6 +12348,9 @@ static void SelectArea(int from_x, int from_y, int to_x, int to_y, #define CB_BRUSH_TO_CLIPBOARD 7 #define CB_BRUSH_TO_CLIPBOARD_SMALL 8 #define CB_UPDATE_BRUSH_POSITION 9 +#define CB_FLIP_BRUSH_X 10 +#define CB_FLIP_BRUSH_Y 11 +#define CB_FLIP_BRUSH_XY 12 #define MAX_CB_PART_SIZE 10 #define MAX_CB_LINE_SIZE (MAX_LEV_FIELDX + 1) // text plus newline @@ -12064,6 +12359,185 @@ static void SelectArea(int from_x, int from_y, int to_x, int to_y, MAX_CB_NUM_LINES * \ MAX_CB_PART_SIZE) +static int getFlippedTileExt(int map[], int element) +{ + int i; + + for (i = 0; map[i] != -1; i++) + if (map[i] == element) + return map[i ^ 1]; // get flipped element by flipping LSB of index + + return element; +} + +static int getFlippedTileX(int element) +{ + int map[] = + { + EL_BD_BUTTERFLY_LEFT, EL_BD_BUTTERFLY_RIGHT, + EL_BD_FIREFLY_LEFT, EL_BD_FIREFLY_RIGHT, + EL_BUG_LEFT, EL_BUG_RIGHT, + EL_SPACESHIP_LEFT, EL_SPACESHIP_RIGHT, + EL_PACMAN_LEFT, EL_PACMAN_RIGHT, + EL_ARROW_LEFT, EL_ARROW_RIGHT, + EL_MOLE_LEFT, EL_MOLE_RIGHT, + EL_BALLOON_SWITCH_LEFT, EL_BALLOON_SWITCH_RIGHT, + EL_YAMYAM_LEFT, EL_YAMYAM_RIGHT, + EL_SP_PORT_LEFT, EL_SP_PORT_RIGHT, + EL_SP_GRAVITY_PORT_LEFT, EL_SP_GRAVITY_PORT_RIGHT, + EL_SP_GRAVITY_ON_PORT_LEFT, EL_SP_GRAVITY_ON_PORT_RIGHT, + EL_SP_GRAVITY_OFF_PORT_LEFT, EL_SP_GRAVITY_OFF_PORT_RIGHT, + EL_CONVEYOR_BELT_1_LEFT, EL_CONVEYOR_BELT_1_RIGHT, + EL_CONVEYOR_BELT_2_LEFT, EL_CONVEYOR_BELT_2_RIGHT, + EL_CONVEYOR_BELT_3_LEFT, EL_CONVEYOR_BELT_3_RIGHT, + EL_CONVEYOR_BELT_4_LEFT, EL_CONVEYOR_BELT_4_RIGHT, + EL_SPRING_LEFT, EL_SPRING_RIGHT, + EL_SP_CHIP_LEFT, EL_SP_CHIP_RIGHT, + EL_TUBE_VERTICAL_LEFT, EL_TUBE_VERTICAL_RIGHT, + EL_TUBE_LEFT_UP, EL_TUBE_RIGHT_UP, + EL_TUBE_LEFT_DOWN, EL_TUBE_RIGHT_DOWN, + EL_DC_STEELWALL_1_LEFT, EL_DC_STEELWALL_1_RIGHT, + EL_DC_STEELWALL_1_TOPLEFT, EL_DC_STEELWALL_1_TOPRIGHT, + EL_DC_STEELWALL_1_BOTTOMLEFT, EL_DC_STEELWALL_1_BOTTOMRIGHT, + EL_DC_STEELWALL_1_TOPLEFT_2, EL_DC_STEELWALL_1_TOPRIGHT_2, + EL_DC_STEELWALL_1_BOTTOMLEFT_2, EL_DC_STEELWALL_1_BOTTOMRIGHT_2, + EL_DC_STEELWALL_2_LEFT, EL_DC_STEELWALL_2_RIGHT, + EL_ACID_POOL_TOPLEFT, EL_ACID_POOL_TOPRIGHT, + EL_ACID_POOL_BOTTOMLEFT, EL_ACID_POOL_BOTTOMRIGHT, + + -1 + }; + + return getFlippedTileExt(map, element); +} + +static int getFlippedTileY(int element) +{ + int map[] = + { + EL_BD_BUTTERFLY_UP, EL_BD_BUTTERFLY_DOWN, + EL_BD_FIREFLY_UP, EL_BD_FIREFLY_DOWN, + EL_BUG_UP, EL_BUG_DOWN, + EL_SPACESHIP_UP, EL_SPACESHIP_DOWN, + EL_PACMAN_UP, EL_PACMAN_DOWN, + EL_ARROW_UP, EL_ARROW_DOWN, + EL_MOLE_UP, EL_MOLE_DOWN, + EL_BALLOON_SWITCH_UP, EL_BALLOON_SWITCH_DOWN, + EL_YAMYAM_UP, EL_YAMYAM_DOWN, + EL_SP_PORT_UP, EL_SP_PORT_DOWN, + EL_SP_GRAVITY_PORT_UP, EL_SP_GRAVITY_PORT_DOWN, + EL_SP_GRAVITY_ON_PORT_UP, EL_SP_GRAVITY_ON_PORT_DOWN, + EL_SP_GRAVITY_OFF_PORT_UP, EL_SP_GRAVITY_OFF_PORT_DOWN, + EL_SP_CHIP_TOP, EL_SP_CHIP_BOTTOM, + EL_TUBE_HORIZONTAL_UP, EL_TUBE_HORIZONTAL_DOWN, + EL_TUBE_LEFT_UP, EL_TUBE_LEFT_DOWN, + EL_TUBE_RIGHT_UP, EL_TUBE_RIGHT_DOWN, + EL_DC_STEELWALL_1_TOP, EL_DC_STEELWALL_1_BOTTOM, + EL_DC_STEELWALL_1_TOPLEFT, EL_DC_STEELWALL_1_BOTTOMLEFT, + EL_DC_STEELWALL_1_TOPRIGHT, EL_DC_STEELWALL_1_BOTTOMRIGHT, + EL_DC_STEELWALL_1_TOPLEFT_2, EL_DC_STEELWALL_1_BOTTOMLEFT_2, + EL_DC_STEELWALL_1_TOPRIGHT_2, EL_DC_STEELWALL_1_BOTTOMRIGHT_2, + EL_DC_STEELWALL_2_TOP, EL_DC_STEELWALL_2_BOTTOM, + EL_EMC_WALL_1, EL_EMC_WALL_3, + + -1 + }; + + return getFlippedTileExt(map, element); +} + +static int getFlippedTileXY(int element) +{ + int map[] = + { + EL_BD_BUTTERFLY_LEFT, EL_BD_BUTTERFLY_UP, + EL_BD_BUTTERFLY_RIGHT, EL_BD_BUTTERFLY_DOWN, + EL_BD_FIREFLY_LEFT, EL_BD_FIREFLY_UP, + EL_BD_FIREFLY_RIGHT, EL_BD_FIREFLY_DOWN, + EL_BUG_LEFT, EL_BUG_UP, + EL_BUG_RIGHT, EL_BUG_DOWN, + EL_SPACESHIP_LEFT, EL_SPACESHIP_UP, + EL_SPACESHIP_RIGHT, EL_SPACESHIP_DOWN, + EL_PACMAN_LEFT, EL_PACMAN_UP, + EL_PACMAN_RIGHT, EL_PACMAN_DOWN, + EL_ARROW_LEFT, EL_ARROW_UP, + EL_ARROW_RIGHT, EL_ARROW_DOWN, + EL_MOLE_LEFT, EL_MOLE_UP, + EL_MOLE_RIGHT, EL_MOLE_DOWN, + EL_BALLOON_SWITCH_LEFT, EL_BALLOON_SWITCH_UP, + EL_BALLOON_SWITCH_RIGHT, EL_BALLOON_SWITCH_DOWN, + EL_YAMYAM_LEFT, EL_YAMYAM_UP, + EL_YAMYAM_RIGHT, EL_YAMYAM_DOWN, + EL_SP_PORT_LEFT, EL_SP_PORT_UP, + EL_SP_PORT_RIGHT, EL_SP_PORT_DOWN, + EL_SP_GRAVITY_PORT_LEFT, EL_SP_GRAVITY_PORT_UP, + EL_SP_GRAVITY_PORT_RIGHT, EL_SP_GRAVITY_PORT_DOWN, + EL_SP_GRAVITY_ON_PORT_LEFT, EL_SP_GRAVITY_ON_PORT_UP, + EL_SP_GRAVITY_ON_PORT_RIGHT, EL_SP_GRAVITY_ON_PORT_DOWN, + EL_SP_GRAVITY_OFF_PORT_LEFT, EL_SP_GRAVITY_OFF_PORT_UP, + EL_SP_GRAVITY_OFF_PORT_RIGHT, EL_SP_GRAVITY_OFF_PORT_DOWN, + EL_SP_CHIP_LEFT, EL_SP_CHIP_TOP, + EL_SP_CHIP_RIGHT, EL_SP_CHIP_BOTTOM, + EL_TUBE_VERTICAL, EL_TUBE_HORIZONTAL, + EL_TUBE_VERTICAL_LEFT, EL_TUBE_HORIZONTAL_UP, + EL_TUBE_VERTICAL_RIGHT, EL_TUBE_HORIZONTAL_DOWN, + EL_TUBE_LEFT_DOWN, EL_TUBE_RIGHT_UP, + EL_DC_STEELWALL_1_LEFT, EL_DC_STEELWALL_1_TOP, + EL_DC_STEELWALL_1_RIGHT, EL_DC_STEELWALL_1_BOTTOM, + EL_DC_STEELWALL_1_HORIZONTAL, EL_DC_STEELWALL_1_VERTICAL, + EL_DC_STEELWALL_1_TOPRIGHT, EL_DC_STEELWALL_1_BOTTOMLEFT, + EL_DC_STEELWALL_1_TOPRIGHT_2, EL_DC_STEELWALL_1_BOTTOMLEFT_2, + EL_DC_STEELWALL_2_LEFT, EL_DC_STEELWALL_2_TOP, + EL_DC_STEELWALL_2_RIGHT, EL_DC_STEELWALL_2_BOTTOM, + EL_DC_STEELWALL_2_HORIZONTAL, EL_DC_STEELWALL_2_VERTICAL, + EL_EXPANDABLE_WALL_HORIZONTAL, EL_EXPANDABLE_WALL_VERTICAL, + EL_EXPANDABLE_STEELWALL_HORIZONTAL, EL_EXPANDABLE_STEELWALL_VERTICAL, + + -1 + }; + + return getFlippedTileExt(map, element); +} + +static int getFlippedTile(int element, int mode) +{ + if (IS_MM_ELEMENT(element)) + { + // get MM game element + element = map_element_RND_to_MM(element); + + // get flipped game element + element = (mode == CB_FLIP_BRUSH_X ? getFlippedTileX_MM(element) : + mode == CB_FLIP_BRUSH_Y ? getFlippedTileY_MM(element) : + mode == CB_FLIP_BRUSH_XY ? getFlippedTileXY_MM(element) : + element); + + // get RND game element again + element = map_element_MM_to_RND(element); + } + else + { + // get flipped game element + element = (mode == CB_FLIP_BRUSH_X ? getFlippedTileX(element) : + mode == CB_FLIP_BRUSH_Y ? getFlippedTileY(element) : + mode == CB_FLIP_BRUSH_XY ? getFlippedTileXY(element) : + element); + } + + return element; +} + +static void SwapFlippedTiles(short *tile1, short *tile2, int mode) +{ + // flip tiles + short tile1_flipped = getFlippedTile(*tile1, mode); + short tile2_flipped = getFlippedTile(*tile2, mode); + + // swap tiles + *tile1 = tile2_flipped; + *tile2 = tile1_flipped; +} + static void DrawBrushElement(int sx, int sy, int element, boolean change_level) { DrawLineElement(sx, sy, element, change_level); @@ -12302,7 +12776,7 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y, { int element = Tile[x][y]; - if (!IS_EM_ELEMENT(element) && !ELEM_IS_PLAYER(element)) + if (!IS_EM_ELEMENT(element) && !IS_PLAYER_ELEMENT(element)) use_em_engine = FALSE; if (!IS_SP_ELEMENT(element)) @@ -12424,6 +12898,37 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y, delete_old_brush = TRUE; } + else if (mode == CB_FLIP_BRUSH_X) + { + for (y = 0; y < brush_height; y++) + for (x = 0; x < (brush_width + 1) / 2; x++) + SwapFlippedTiles(&brush_buffer[x][y], + &brush_buffer[brush_width - x - 1][y], mode); + + CopyBrushExt(last_cursor_x, last_cursor_y, 0, 0, 0, CB_BRUSH_TO_CURSOR); + } + else if (mode == CB_FLIP_BRUSH_Y) + { + for (y = 0; y < (brush_height + 1) / 2; y++) + for (x = 0; x < brush_width; x++) + SwapFlippedTiles(&brush_buffer[x][y], + &brush_buffer[x][brush_height - y - 1], mode); + + CopyBrushExt(last_cursor_x, last_cursor_y, 0, 0, 0, CB_BRUSH_TO_CURSOR); + } + else if (mode == CB_FLIP_BRUSH_XY) + { + CopyBrushExt(0, 0, 0, 0, 0, CB_DELETE_OLD_CURSOR); + + for (y = 0; y < MAX(brush_width, brush_height); y++) + for (x = 0; x <= y; x++) + SwapFlippedTiles(&brush_buffer[x][y], + &brush_buffer[y][x], mode); + + swap_numbers(&brush_width, &brush_height); + + CopyBrushExt(last_cursor_x, last_cursor_y, 0, 0, 0, CB_BRUSH_TO_CURSOR); + } if (mode == CB_UPDATE_BRUSH_POSITION) { @@ -12458,6 +12963,22 @@ static void DeleteBrushFromCursor(void) CopyBrushExt(0, 0, 0, 0, 0, CB_DELETE_OLD_CURSOR); } +static void FlipBrushX(void) +{ + CopyBrushExt(0, 0, 0, 0, 0, CB_FLIP_BRUSH_X); +} + +static void FlipBrushY(void) +{ + CopyBrushExt(0, 0, 0, 0, 0, CB_FLIP_BRUSH_Y); +} + +static void RotateBrush(void) +{ + CopyBrushExt(0, 0, 0, 0, 0, CB_FLIP_BRUSH_XY); + CopyBrushExt(0, 0, 0, 0, 0, CB_FLIP_BRUSH_X); +} + void DumpBrush(void) { CopyBrushExt(0, 0, 0, 0, 0, CB_DUMP_BRUSH); @@ -12535,7 +13056,7 @@ static int DrawLevelText(int sx, int sy, char letter, int mode) static int start_sx; static int last_sx, last_sy; static boolean typing = FALSE; - int letter_element = EL_CHAR_ASCII0 + letter; + int letter_element; int lx = 0, ly = 0; // map lower case letters to upper case and convert special characters @@ -12786,7 +13307,8 @@ static void WrapLevel(int dx, int dy) CopyLevelToUndoBuffer(UNDO_ACCUMULATE); } -static void DrawAreaElementHighlight(boolean highlighted) +static void DrawAreaElementHighlight(boolean highlighted, + boolean highlighted_similar) { DrawEditorLevel(ed_fieldx, ed_fieldy, level_xpos, level_ypos); @@ -12799,26 +13321,69 @@ static void DrawAreaElementHighlight(boolean highlighted) { for (y = 0; y < ed_fieldy; y++) { + boolean highlight = FALSE; int lx = x + level_xpos; int ly = y + level_ypos; if (!IN_LEV_FIELD(lx, ly)) continue; - if (Tile[lx][ly] != new_element1) + // check if element is the same + if (Tile[lx][ly] == new_element1) + highlight = TRUE; + + // check if element is similar + if (highlighted_similar && + strEqual(element_info[Tile[lx][ly]].class_name, + element_info[new_element1].class_name)) + highlight = TRUE; + + // check if element is matching MM style wall + if (IS_MM_WALL(Tile[lx][ly]) && + map_mm_wall_element(Tile[lx][ly]) == new_element1) + highlight = TRUE; + + if (!highlight) continue; - int sx = SX + x * ed_tilesize; - int sy = SY + y * ed_tilesize; - int from_sx = sx; - int from_sy = sy; - int to_sx = sx + ed_tilesize - 1; - int to_sy = sy + ed_tilesize - 1; - - DrawSimpleWhiteLine(drawto, from_sx, from_sy, to_sx, from_sy); - DrawSimpleWhiteLine(drawto, to_sx, from_sy, to_sx, to_sy); - DrawSimpleWhiteLine(drawto, to_sx, to_sy, from_sx, to_sy); - DrawSimpleWhiteLine(drawto, from_sx, to_sy, from_sx, from_sy); + if (IS_MM_WALL(Tile[lx][ly]) && !highlighted_similar) + { + int i; + + for (i = 0; i < 4; i++) + { + if (!(MM_WALL_BITS(Tile[lx][ly]) & (1 << i))) + continue; + + int xx = x * 2 + (i % 2); + int yy = y * 2 + (i / 2); + int sx = SX + xx * ed_tilesize / 2; + int sy = SY + yy * ed_tilesize / 2; + int from_sx = sx; + int from_sy = sy; + int to_sx = sx + ed_tilesize / 2 - 1; + int to_sy = sy + ed_tilesize / 2 - 1; + + DrawSimpleWhiteLine(drawto, from_sx, from_sy, to_sx, from_sy); + DrawSimpleWhiteLine(drawto, to_sx, from_sy, to_sx, to_sy); + DrawSimpleWhiteLine(drawto, to_sx, to_sy, from_sx, to_sy); + DrawSimpleWhiteLine(drawto, from_sx, to_sy, from_sx, from_sy); + } + } + else + { + int sx = SX + x * ed_tilesize; + int sy = SY + y * ed_tilesize; + int from_sx = sx; + int from_sy = sy; + int to_sx = sx + ed_tilesize - 1; + int to_sy = sy + ed_tilesize - 1; + + DrawSimpleWhiteLine(drawto, from_sx, from_sy, to_sx, from_sy); + DrawSimpleWhiteLine(drawto, to_sx, from_sy, to_sx, to_sy); + DrawSimpleWhiteLine(drawto, to_sx, to_sy, from_sx, to_sy); + DrawSimpleWhiteLine(drawto, from_sx, to_sy, from_sx, from_sy); + } } } } @@ -12838,8 +13403,10 @@ static void CopyLevelTemplateToUserLevelSet(char *levelset_subdir) static void HandleDrawingAreas(struct GadgetInfo *gi) { static boolean started_inside_drawing_area = FALSE; - static int last_sx = -1, last_sy = -1; - static int last_sx2 = -1, last_sy2 = -1; + static int last_sx = -1; + static int last_sy = -1; + static int last_sx2 = -1; + static int last_sy2 = -1; int id = gi->custom_id; int type_id = gi->custom_type_id; boolean button_press_event; @@ -12859,8 +13426,6 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) int dx = sx2 % 2; int dy = sy2 % 2; int lx = 0, ly = 0; - int min_lx = 0, min_ly = 0; - int max_lx = lev_fieldx - 1, max_ly = lev_fieldy - 1; int x, y; button_press_event = (gi->event.type == GD_EVENT_PRESSED); @@ -12872,6 +13437,9 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) if (draw_level) { + int min_lx = 0, min_ly = 0; + int max_lx = lev_fieldx - 1, max_ly = lev_fieldy - 1; + // get positions inside level field lx = sx + level_xpos; ly = sy + level_ypos; @@ -12900,14 +13468,7 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) sy2 = sy * 2 + dy; } - if (button_release_event) - { - last_sx = -1; - last_sy = -1; - last_sx2 = -1; - last_sy2 = -1; - } - else if (!button_press_event) + if (!button_press_event && !button_release_event) { int old_element = (IN_LEV_FIELD(lx, ly) ? Tile[lx][ly] : EL_UNDEFINED); boolean hires_drawing = (level.game_engine_type == GAME_ENGINE_TYPE_MM && @@ -12934,9 +13495,6 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) if (!IS_VALID_BUTTON(button)) return; - if (!button && !button_release_event) - return; - // handle info callback for each invocation of action callback gi->callback_info(gi); @@ -12972,10 +13530,9 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) if (edit_mode == ED_MODE_DRAWING && draw_with_brush && !inside_drawing_area) DeleteBrushFromCursor(); - } - if (!button || button_release_event) break; + } if (draw_with_brush) { @@ -12985,7 +13542,7 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) { SetDrawModeHiRes(new_element); - if (ELEM_IS_PLAYER(new_element)) + if (IS_PLAYER_ELEMENT(new_element) || IS_MM_MCDUFFIN(new_element)) { // remove player at old position for (y = 0; y < lev_fieldy; y++) @@ -12994,7 +13551,8 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) { int old_element = Tile[x][y]; - if (ELEM_IS_PLAYER(old_element)) + if (IS_PLAYER_ELEMENT(old_element) && + IS_PLAYER_ELEMENT(new_element)) { int replaced_with_element = (old_element == EL_SOKOBAN_FIELD_PLAYER && @@ -13014,6 +13572,12 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) SetElement(x, y, replaced_with_element); } + else if (IS_MM_MCDUFFIN(old_element) && + IS_MM_MCDUFFIN(new_element)) + { + // remove McDuffin at old position + SetElement(x, y, EL_EMPTY); + } } } } @@ -13060,22 +13624,19 @@ static void HandleDrawingAreas(struct GadgetInfo *gi) if (button_release_event) CopyLevelToUndoBuffer(UNDO_IMMEDIATE); - if (button) - { - SetDrawModeHiRes(new_element); + SetDrawModeHiRes(new_element); - if (getDrawModeHiRes()) - { - sx = sx2; - sy = sy2; - } + if (getDrawModeHiRes()) + { + sx = sx2; + sy = sy2; + } - if (!button_press_event) - DrawLine(last_sx, last_sy, sx, sy, new_element, TRUE); + if (!button_press_event) + DrawLine(last_sx, last_sy, sx, sy, new_element, TRUE); - last_sx = sx; - last_sy = sy; - } + last_sx = sx; + last_sy = sy; } break; @@ -13258,11 +13819,11 @@ static void HandleCounterButtons(struct GadgetInfo *gi) break; case ED_COUNTER_ID_ANDROID_CONTENT: - DrawAndroidElementArea(properties_element); + DrawAndroidElementArea(); break; case ED_COUNTER_ID_GROUP_CONTENT: - DrawGroupElementArea(properties_element); + DrawGroupElementArea(); CopyGroupElementPropertiesToGame(properties_element); break; @@ -13270,6 +13831,10 @@ static void HandleCounterButtons(struct GadgetInfo *gi) DrawPlayerInitialInventoryArea(properties_element); break; + case ED_COUNTER_ID_MM_BALL_CONTENT: + DrawMMBallContentArea(); + break; + case ED_COUNTER_ID_ENVELOPE_XSIZE: case ED_COUNTER_ID_ENVELOPE_YSIZE: DrawEnvelopeTextArea(-1); @@ -13347,7 +13912,7 @@ static void HandleSelectboxGadgets(struct GadgetInfo *gi) if (type_id == ED_SELECTBOX_ID_LEVELSET_SAVE_MODE) { - DrawLevelInfoWindow(); + DrawLevelConfigWindow(); } else if (type_id == ED_SELECTBOX_ID_SELECT_CHANGE_PAGE) { @@ -13392,12 +13957,12 @@ static void HandleTextbuttonGadgets(struct GadgetInfo *gi) int type_id = gi->custom_type_id; int i; - if (type_id >= ED_TAB_BUTTON_ID_LEVELINFO_FIRST && - type_id <= ED_TAB_BUTTON_ID_LEVELINFO_LAST) + if (type_id >= ED_TAB_BUTTON_ID_LEVELCONFIG_FIRST && + type_id <= ED_TAB_BUTTON_ID_LEVELCONFIG_LAST) { - edit_mode_levelinfo = gi->custom_type_id; + edit_mode_levelconfig = gi->custom_type_id; - DrawLevelInfoWindow(); + DrawLevelConfigWindow(); } else if (type_id >= ED_TAB_BUTTON_ID_PROPERTIES_FIRST && type_id <= ED_TAB_BUTTON_ID_PROPERTIES_LAST) @@ -13625,9 +14190,11 @@ static void HandleCheckbuttons(struct GadgetInfo *gi) boolean template_related_changes_found = FALSE; int i; - // check if any custom or group elements have been changed + // check if any custom, group or empty elements have been changed for (i = 0; i < NUM_FILE_ELEMENTS; i++) - if ((IS_CUSTOM_ELEMENT(i) || IS_GROUP_ELEMENT(i)) && + if ((IS_CUSTOM_ELEMENT(i) || + IS_GROUP_ELEMENT(i) || + IS_EMPTY_ELEMENT(i)) && element_info[i].modified_settings) template_related_changes_found = TRUE; @@ -14051,12 +14618,12 @@ static void HandleControlButtons(struct GadgetInfo *gi) break; - case GADGET_ID_INFO: - if (edit_mode != ED_MODE_INFO) + case GADGET_ID_CONF: + if (edit_mode != ED_MODE_LEVELCONFIG) { last_edit_mode = edit_mode; - ChangeEditModeWindow(ED_MODE_INFO); + ChangeEditModeWindow(ED_MODE_LEVELCONFIG); } else { @@ -14097,7 +14664,7 @@ static void HandleControlButtons(struct GadgetInfo *gi) Request("Save this level and kill the old?", REQ_ASK)) { if (leveldir_former->readonly) - ModifyLevelInfoForSavingIntoPersonalLevelSet(leveldir_former->name); + ModifyLevelConfigForSavingIntoPersonalLevelSet(leveldir_former->name); SetAutomaticNumberOfGemsNeeded(); @@ -14162,7 +14729,8 @@ static void HandleControlButtons(struct GadgetInfo *gi) id <= GADGET_ID_ELEMENTLIST_LAST) { int element_position = id - GADGET_ID_ELEMENTLIST_FIRST; - int new_element = editor_elements[element_position + element_shift]; + + new_element = editor_elements[element_position + element_shift]; if (IS_EDITOR_CASCADE(new_element)) { @@ -14352,8 +14920,8 @@ void HandleLevelEditorKeyInput(Key key) case KSYM_Escape: if (edit_mode == ED_MODE_DRAWING) RequestExitLevelEditor(setup.ask_on_escape_editor, TRUE); - else if (edit_mode == ED_MODE_INFO) - HandleControlButtons(level_editor_gadget[GADGET_ID_INFO]); + else if (edit_mode == ED_MODE_LEVELCONFIG) + HandleControlButtons(level_editor_gadget[GADGET_ID_CONF]); else if (edit_mode == ED_MODE_PROPERTIES) HandleControlButtons(level_editor_gadget[GADGET_ID_PROPERTIES]); else if (edit_mode == ED_MODE_PALETTE) @@ -14395,6 +14963,16 @@ void HandleLevelEditorKeyInput(Key key) if (letter && letter == controlbutton_info[i].shortcut) if (!anyTextGadgetActive()) ClickOnGadget(level_editor_gadget[i], button); + + if (draw_with_brush) + { + if (letter == 'x') + FlipBrushX(); + else if (letter == 'y') + FlipBrushY(); + else if (letter == 'z') + RotateBrush(); + } } static void HandleLevelEditorIdle_Properties(void) @@ -14402,11 +14980,12 @@ static void HandleLevelEditorIdle_Properties(void) int element_border = graphic_info[IMG_EDITOR_ELEMENT_BORDER].border_size; int x = editor.settings.element_graphic.x + element_border; int y = editor.settings.element_graphic.y + element_border; - static unsigned int action_delay = 0; - unsigned int action_delay_value = GameFrameDelay; + static DelayCounter action_delay = { 0 }; int i; - if (!DelayReached(&action_delay, action_delay_value)) + action_delay.value = GameFrameDelay; + + if (!DelayReached(&action_delay)) return; for (i = 0; i < ED_NUM_SELECTBOX; i++) @@ -14427,16 +15006,20 @@ static void HandleLevelEditorIdle_Properties(void) static void HandleLevelEditorIdle_Drawing(void) { static boolean last_highlighted = FALSE; + static boolean last_highlighted_similar = FALSE; boolean highlighted = (GetKeyModState() & KMOD_Alt); + boolean highlighted_similar = (GetKeyModState() & KMOD_Shift); - if (highlighted != last_highlighted) + if (highlighted != last_highlighted || + (highlighted && highlighted_similar != last_highlighted_similar)) { - DrawAreaElementHighlight(highlighted); - - last_highlighted = highlighted; + DrawAreaElementHighlight(highlighted, highlighted_similar); redraw_mask |= REDRAW_FIELD; } + + last_highlighted = highlighted; + last_highlighted_similar = highlighted_similar; } void HandleLevelEditorIdle(void) @@ -14455,7 +15038,6 @@ static void ClearEditorGadgetInfoText(void) void PrintEditorGadgetInfoText(struct GadgetInfo *gi) { char infotext[MAX_OUTPUT_LINESIZE + 1]; - char shortcut[MAX_OUTPUT_LINESIZE + 1]; int max_infotext_len = getMaxInfoTextLength(); if (gi == NULL || strlen(gi->info_text) == 0) @@ -14470,6 +15052,8 @@ void PrintEditorGadgetInfoText(struct GadgetInfo *gi) if (key) { + char shortcut[MAX_OUTPUT_LINESIZE + 1]; + if (gi->custom_id == GADGET_ID_SINGLE_ITEMS) sprintf(shortcut, " ('.' or '%c')", key); else if (gi->custom_id == GADGET_ID_PICK_ELEMENT) @@ -14513,7 +15097,6 @@ void HandleEditorGadgetInfoText(void *ptr) static void HandleDrawingAreaInfo(struct GadgetInfo *gi) { - static int start_lx, start_ly; int id = gi->custom_id; int type_id = gi->custom_type_id; int sx = gi->event.x; @@ -14526,7 +15109,6 @@ static void HandleDrawingAreaInfo(struct GadgetInfo *gi) int actual_drawing_function = drawing_function; int max_infotext_len = getMaxInfoTextLength(); char infotext[MAX_OUTPUT_LINESIZE + 1]; - char *text; infotext[0] = '\0'; // start with empty info text @@ -14563,10 +15145,14 @@ static void HandleDrawingAreaInfo(struct GadgetInfo *gi) sy = ly - level_ypos; } - if (IN_ED_FIELD(sx,sy) && IN_LEV_FIELD(lx, ly)) + if (IN_ED_FIELD(sx, sy) && IN_LEV_FIELD(lx, ly)) { if (button_status) // if (gi->state == GD_BUTTON_PRESSED) { + static int start_lx = 0; + static int start_ly = 0; + char *text; + if (gi->event.type == GD_EVENT_PRESSED) { start_lx = lx; @@ -14697,7 +15283,7 @@ void RequestExitLevelEditor(boolean ask_if_level_has_changed, vp_door_2->height == VYSIZE) CloseDoor(DOOR_CLOSE_ALL | DOOR_NO_DELAY); else - SetDoorState(DOOR_CLOSE_2); + SetDoorState(DOOR_CLOSE_ALL); BackToFront(); diff --git a/src/engines.h b/src/engines.h index 4e09b72a..f2f9fe2f 100644 --- a/src/engines.h +++ b/src/engines.h @@ -36,7 +36,7 @@ int getScreenFieldSizeY(void); void PlayLevelSound_EM(int, int, int, int); void InitGraphicInfo_EM(void); -boolean CheckSingleStepMode_EM(byte action[], int, boolean, boolean, boolean); +boolean CheckSingleStepMode_EM(int, boolean, boolean, boolean); void SetGfxAnimation_EM(struct GraphicInfo_EM *, int, int, int, int); void getGraphicSourceObjectExt_EM(struct GraphicInfo_EM *, int, int, int, int); @@ -49,7 +49,7 @@ void getGraphicSourcePlayerExt_EM(struct GraphicInfo_EM *, int, int, int); void CheckSingleStepMode_SP(boolean, boolean); -void getGraphicSource_SP(struct GraphicInfo_SP *, int, int, int, int); +void getGraphicSource_SP(struct GraphicInfo_SP *, int, int); int getGraphicInfo_Delay(int); boolean isNextAnimationFrame_SP(int, int); @@ -59,15 +59,25 @@ boolean isNextAnimationFrame_SP(int, int); // ============================================================================ void SetDrawtoField(int); +void BackToFront(void); int el2img_mm(int); +int el_act2img_mm(int, int); void CheckSingleStepMode_MM(boolean, boolean); +void ShowEnvelope(int); int getGraphicAnimationFrame(int, int); +int getGraphicAnimationFrameXY(int, int, int); + void getGraphicSource(int, int, Bitmap **, int *, int *); void getMiniGraphicSource(int, Bitmap **, int *, int *); void getSizedGraphicSource(int, int, int, Bitmap **, int *, int *); +boolean getGraphicInfo_NewFrame(int, int, int); + +void AdvanceFrameCounter(void); +void AdvanceGfxFrame(void); +int getAnimationFrame(int, int, int, int, int); #endif // ENGINES_H diff --git a/src/events.c b/src/events.c index fe82932a..2e3c7ca9 100644 --- a/src/events.c +++ b/src/events.c @@ -36,10 +36,11 @@ static boolean cursor_inside_playfield = FALSE; static int cursor_mode_last = CURSOR_DEFAULT; -static unsigned int special_cursor_delay = 0; -static unsigned int special_cursor_delay_value = 1000; +static DelayCounter special_cursor_delay = { 1000 }; +static boolean special_cursor_enabled = FALSE; static boolean stop_processing_events = FALSE; +static boolean is_global_anim_event = FALSE; // forward declarations for internal use @@ -48,6 +49,11 @@ static void HandleNoEvent(void); static void HandleEventActions(void); +void SetPlayfieldMouseCursorEnabled(boolean enabled) +{ + special_cursor_enabled = enabled; +} + // event filter to set mouse x/y position (for pointer class global animations) // (this is especially required to ensure smooth global animation mouse pointer // movement when the screen is updated without handling events; this can happen @@ -108,7 +114,7 @@ static int FilterEvents(const Event *event) { SetMouseCursor(CURSOR_DEFAULT); - DelayReached(&special_cursor_delay, 0); + ResetDelayCounter(&special_cursor_delay); cursor_mode_last = CURSOR_DEFAULT; } @@ -205,8 +211,7 @@ void StopProcessingEvents(void) static void HandleEvents(void) { Event event; - unsigned int event_frame_delay = 0; - unsigned int event_frame_delay_value = GAME_FRAME_DELAY; + DelayCounter event_frame_delay = { GAME_FRAME_DELAY }; ResetDelayCounter(&event_frame_delay); @@ -271,7 +276,7 @@ static void HandleEvents(void) ResetDelayCounter(&event_frame_delay); // do not handle events for longer than standard frame delay period - if (DelayReached(&event_frame_delay, event_frame_delay_value)) + if (DelayReached(&event_frame_delay)) break; // do not handle any further events if triggered by a special flag @@ -324,7 +329,7 @@ static void HandleMouseCursor(void) // when showing title screens, hide mouse pointer (if not moved) if (gfx.cursor_mode != CURSOR_NONE && - DelayReached(&special_cursor_delay, special_cursor_delay_value)) + DelayReached(&special_cursor_delay)) { SetMouseCursor(CURSOR_NONE); } @@ -336,15 +341,14 @@ static void HandleMouseCursor(void) // display normal pointer if mouse pressed if (button_status != MB_RELEASED) - DelayReached(&special_cursor_delay, 0); + ResetDelayCounter(&special_cursor_delay); if (gfx.cursor_mode != CURSOR_PLAYFIELD && cursor_inside_playfield && - DelayReached(&special_cursor_delay, special_cursor_delay_value)) + special_cursor_enabled && + DelayReached(&special_cursor_delay)) { - if (level.game_engine_type != GAME_ENGINE_TYPE_MM || - tile_cursor.enabled) - SetMouseCursor(CURSOR_PLAYFIELD); + SetMouseCursor(CURSOR_PLAYFIELD); } } else if (gfx.cursor_mode != CURSOR_DEFAULT) @@ -524,6 +528,10 @@ void HandleButtonEvent(ButtonEvent *event) // for any mouse button event, disable playfield tile cursor SetTileCursorEnabled(FALSE); + // for any mouse button event, disable playfield mouse cursor + if (cursor_inside_playfield) + SetPlayfieldMouseCursorEnabled(FALSE); + #if defined(HAS_SCREEN_KEYBOARD) if (video.shifted_up) event->y += video.shifted_up_pos; @@ -576,7 +584,7 @@ void HandleWheelEvent(WheelEvent *event) event->y < 0 ? MB_WHEEL_DOWN : event->y > 0 ? MB_WHEEL_UP : 0); -#if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX) +#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC) // accelerated mouse wheel available on Mac and Windows wheel_steps = (event->x ? ABS(event->x) : ABS(event->y)); #else @@ -613,6 +621,8 @@ void HandleWindowEvent(WindowEvent *event) subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" : subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" : subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" : + subtype == SDL_WINDOWEVENT_TAKE_FOCUS ? "SDL_WINDOWEVENT_TAKE_FOCUS" : + subtype == SDL_WINDOWEVENT_HIT_TEST ? "SDL_WINDOWEVENT_HIT_TEST" : "(UNKNOWN)"); Debug("event:window", "name: '%s', data1: %ld, data2: %ld", @@ -1449,16 +1459,13 @@ void HandlePauseResumeEvent(PauseResumeEvent *event) void HandleKeyEvent(KeyEvent *event) { int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED); - boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE); - Key key = GetEventKey(event, with_modifiers); - Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key); + Key key = GetEventKey(event); #if DEBUG_EVENTS_KEY - Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)", + Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)", event->type == EVENT_KEYPRESS ? "pressed" : "released", event->keysym.scancode, event->keysym.sym, - keymod, GetKeyModState(), key, getKeyNameFromKey(key)); @@ -1486,7 +1493,7 @@ void HandleKeyEvent(KeyEvent *event) } #endif - HandleKeyModState(keymod, key_status); + HandleKeyModState(key, key_status); // process all keys if not in text input mode or if non-printable keys if (!checkTextInputKey(key)) @@ -1545,6 +1552,15 @@ static int HandleDropFileEvent(char *filename) // add extracted level or artwork set to tree info structure AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type); + // force restart after adding level collection + if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL) + { + Request("Program must be restarted after adding a new level collection!", + REQ_CONFIRM); + + CloseAllAndExit(0); + } + // update menu screen (and possibly change current level set) DrawScreenAfterAddingSet(top_dir, tree_type); @@ -1691,6 +1707,7 @@ void HandleButton(int mx, int my, int button, int button_nr) static int old_mx = 0, old_my = 0; boolean button_hold = FALSE; boolean handle_gadgets = TRUE; + int game_status_last = game_status; if (button_nr < 0) { @@ -1709,9 +1726,11 @@ void HandleButton(int mx, int my, int button, int button_nr) // when playing, only handle gadgets when using "follow finger" controls // or when using touch controls in combination with the MM game engine // or when using gadgets that do not overlap with virtual buttons + // or when touch controls are disabled (e.g., with mouse-only levels) handle_gadgets = (game_status != GAME_MODE_PLAYING || level.game_engine_type == GAME_ENGINE_TYPE_MM || + strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) || strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) || (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) && !CheckVirtualButtonPressed(mx, my, button))); @@ -1733,8 +1752,12 @@ void HandleButton(int mx, int my, int button, int button_nr) if (handle_gadgets && HandleGadgets(mx, my, button)) { - // do not handle this button event anymore + // do not handle this button event anymore with position on screen mx = my = -32; // force mouse event to be outside screen tiles + + // do not handle this button event anymore if game status has changed + if (game_status != game_status_last) + return; } if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing) @@ -1772,7 +1795,11 @@ void HandleButton(int mx, int my, int button, int button_nr) break; case GAME_MODE_SCORES: - HandleHallOfFame(0, 0, 0, 0, button); + HandleHallOfFame(mx, my, 0, 0, button); + break; + + case GAME_MODE_SCOREINFO: + HandleScoreInfo(mx, my, 0, 0, button); break; case GAME_MODE_EDITOR: @@ -2091,6 +2118,8 @@ void HandleKey(Key key, int key_status) { key_action |= key_info[i].action | JOY_BUTTON_SNAP; key_snap_action |= key_info[i].action; + + tape.property_bits |= TAPE_PROPERTY_TAS_KEYS; } } } @@ -2154,6 +2183,10 @@ void HandleKey(Key key, int key_status) // for MM style levels, handle in-game keyboard input in HandleJoystick() if (level.game_engine_type == GAME_ENGINE_TYPE_MM) joy |= key_action; + + // for any keyboard event, enable playfield mouse cursor + if (key_action && key_status == KEY_PRESSED) + SetPlayfieldMouseCursorEnabled(TRUE); } } else @@ -2181,6 +2214,10 @@ void HandleKey(Key key, int key_status) // reset flag to ignore repeated "key pressed" events after key release ignore_repeated_key = FALSE; + // send key release event to global animation event handling + if (!is_global_anim_event) + HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE); + return; } @@ -2236,9 +2273,9 @@ void HandleKey(Key key, int key_status) } // some key events are handled like clicks for global animations - boolean click = (key == KSYM_space || - key == KSYM_Return || - key == KSYM_Escape); + boolean click = (!is_global_anim_event && (key == KSYM_space || + key == KSYM_Return || + key == KSYM_Escape)); if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE)) { @@ -2263,12 +2300,26 @@ void HandleKey(Key key, int key_status) return; } + if (game_status == GAME_MODE_MAIN && + (setup.internal.info_screens_from_main || + leveldir_current->info_screens_from_main) && + (key >= KSYM_KP_1 && key <= KSYM_KP_9)) + { + DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1); + + return; + } + if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING) { if (key == setup.shortcut.save_game) TapeQuickSave(); else if (key == setup.shortcut.load_game) TapeQuickLoad(); + else if (key == setup.shortcut.restart_game) + TapeRestartGame(); + else if (key == setup.shortcut.pause_before_end) + TapeReplayAndPauseBeforeEnd(); else if (key == setup.shortcut.toggle_pause) TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE); @@ -2276,6 +2327,11 @@ void HandleKey(Key key, int key_status) HandleSoundButtonKeys(key); } + if (game_status == GAME_MODE_SCOREINFO) + { + HandleScreenGadgetKeys(key); + } + if (game_status == GAME_MODE_PLAYING && !network_playing) { int centered_player_nr_next = -999; @@ -2305,6 +2361,14 @@ void HandleKey(Key key, int key_status) if (HandleGadgetsKeyInput(key)) return; // do not handle already processed keys again + // special case: on "space" key, either continue playing or go to main menu + if (game_status == GAME_MODE_SCORES && key == KSYM_space) + { + HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE); + + return; + } + switch (game_status) { case GAME_MODE_PSEUDO_TYPENAME: @@ -2320,6 +2384,7 @@ void HandleKey(Key key, int key_status) case GAME_MODE_SETUP: case GAME_MODE_INFO: case GAME_MODE_SCORES: + case GAME_MODE_SCOREINFO: if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape) break; @@ -2344,6 +2409,8 @@ void HandleKey(Key key, int key_status) HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE); else if (game_status == GAME_MODE_SCORES) HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE); + else if (game_status == GAME_MODE_SCOREINFO) + HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE); break; case KSYM_Escape: @@ -2364,6 +2431,8 @@ void HandleKey(Key key, int key_status) HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE); else if (game_status == GAME_MODE_SCORES) HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE); + else if (game_status == GAME_MODE_SCOREINFO) + HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE); break; case KSYM_Page_Up: @@ -2379,6 +2448,8 @@ void HandleKey(Key key, int key_status) HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK); else if (game_status == GAME_MODE_SCORES) HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK); + else if (game_status == GAME_MODE_SCOREINFO) + HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK); break; case KSYM_Page_Down: @@ -2394,6 +2465,8 @@ void HandleKey(Key key, int key_status) HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK); else if (game_status == GAME_MODE_SCORES) HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK); + else if (game_status == GAME_MODE_SCOREINFO) + HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK); break; default: @@ -2494,14 +2567,14 @@ static void HandleTileCursor(int dx, int dy, int button) { int old_xpos = tile_cursor.xpos; int old_ypos = tile_cursor.ypos; - int new_xpos = old_xpos; - int new_ypos = old_ypos; + int new_xpos = tile_cursor.xpos + dx; + int new_ypos = tile_cursor.ypos + dy; - if (IN_LEV_FIELD(old_xpos + dx, old_ypos)) - new_xpos = old_xpos + dx; + if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos)) + new_xpos = old_xpos; - if (IN_LEV_FIELD(old_xpos, old_ypos + dy)) - new_ypos = old_ypos + dy; + if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos)) + new_ypos = old_ypos; SetTileCursorTargetXY(new_xpos, new_ypos); } @@ -2542,8 +2615,7 @@ static int HandleJoystickForAllPlayers(void) void HandleJoystick(void) { - static unsigned int joytest_delay = 0; - static unsigned int joytest_delay_value = GADGET_FRAME_DELAY; + static DelayCounter joytest_delay = { GADGET_FRAME_DELAY }; static int joytest_last = 0; int delay_value_first = GADGET_FRAME_DELAY_FIRST; int delay_value = GADGET_FRAME_DELAY; @@ -2556,12 +2628,15 @@ void HandleJoystick(void) int up = joy & JOY_UP; int down = joy & JOY_DOWN; int button = joy & JOY_BUTTON; - int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED); + int anybutton = AnyJoystickButton(); + int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED); int dx = (left ? -1 : right ? 1 : 0); int dy = (up ? -1 : down ? 1 : 0); boolean use_delay_value_first = (joytest != joytest_last); + boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED || + anybutton == JOY_BUTTON_NEW_RELEASED); - if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE)) + if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE)) { // do not handle this button event anymore return; @@ -2597,7 +2672,11 @@ void HandleJoystick(void) SetTileCursorEnabled(TRUE); } - if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value)) + // for any joystick event, enable playfield mouse cursor + if (dx || dy || button) + SetPlayfieldMouseCursorEnabled(TRUE); + + if (joytest && !button && !DelayReached(&joytest_delay)) { // delay joystick/keyboard actions if axes/keys continually pressed newbutton = dx = dy = 0; @@ -2605,7 +2684,7 @@ void HandleJoystick(void) else { // first start with longer delay, then continue with shorter delay - joytest_delay_value = + joytest_delay.value = (use_delay_value_first ? delay_value_first : delay_value); } @@ -2621,6 +2700,7 @@ void HandleJoystick(void) case GAME_MODE_SETUP: case GAME_MODE_INFO: case GAME_MODE_SCORES: + case GAME_MODE_SCOREINFO: { if (anyTextGadgetActive()) break; @@ -2641,6 +2721,8 @@ void HandleJoystick(void) HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK); else if (game_status == GAME_MODE_SCORES) HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK); + else if (game_status == GAME_MODE_SCOREINFO) + HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK); break; } @@ -2753,9 +2835,13 @@ boolean DoKeysymAction(int keysym) { Key key = (Key)(-keysym); + is_global_anim_event = TRUE; + HandleKey(key, KEY_PRESSED); HandleKey(key, KEY_RELEASED); + is_global_anim_event = FALSE; + return TRUE; } diff --git a/src/events.h b/src/events.h index 30a64877..3887f3fd 100644 --- a/src/events.h +++ b/src/events.h @@ -21,6 +21,8 @@ #define USEREVENT_GADGET_PRESSED 3 +void SetPlayfieldMouseCursorEnabled(boolean); + int FilterMouseMotionEvents(void *, Event *); boolean NextValidEvent(Event *); void StopProcessingEvents(void); diff --git a/src/files.c b/src/files.c index 6c26bb01..097deead 100644 --- a/src/files.c +++ b/src/files.c @@ -22,6 +22,7 @@ #include "tools.h" #include "tape.h" #include "config.h" +#include "api.h" #define ENABLE_UNUSED_CODE 0 // currently unused functions #define ENABLE_HISTORIC_CHUNKS 0 // only for historic reference @@ -51,6 +52,7 @@ // (element number only) #define LEVEL_CHUNK_GRPX_UNCHANGED 2 +#define LEVEL_CHUNK_EMPX_UNCHANGED 2 #define LEVEL_CHUNK_NOTE_UNCHANGED 2 // (nothing at all if unchanged) @@ -58,9 +60,10 @@ #define TAPE_CHUNK_VERS_SIZE 8 // size of file version chunk #define TAPE_CHUNK_HEAD_SIZE 20 // size of tape file header -#define TAPE_CHUNK_HEAD_UNUSED 1 // unused tape header bytes #define TAPE_CHUNK_SCRN_SIZE 2 // size of screen size chunk +#define SCORE_CHUNK_VERS_SIZE 8 // size of file version chunk + #define LEVEL_CHUNK_CNT3_SIZE(x) (LEVEL_CHUNK_CNT3_HEADER + (x)) #define LEVEL_CHUNK_CUS3_SIZE(x) (2 + (x) * LEVEL_CPART_CUS3_SIZE) #define LEVEL_CHUNK_CUS4_SIZE(x) (96 + (x) * 48) @@ -68,7 +71,7 @@ // file identifier strings #define LEVEL_COOKIE_TMPL "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x" #define TAPE_COOKIE_TMPL "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x" -#define SCORE_COOKIE "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2" +#define SCORE_COOKIE_TMPL "ROCKSNDIAMONDS_SCORE_FILE_VERSION_x.x" // values for deciding when (not) to save configuration data #define SAVE_CONF_NEVER 0 @@ -111,7 +114,7 @@ CONF_CONTENT_NUM_BYTES : 1) #define CONF_ELEMENT_BYTE_POS(i) ((i) * CONF_ELEMENT_NUM_BYTES) -#define CONF_ELEMENTS_ELEMENT(b,i) ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \ +#define CONF_ELEMENTS_ELEMENT(b, i) ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \ (b[CONF_ELEMENT_BYTE_POS(i) + 1])) #define CONF_CONTENT_ELEMENT_POS(c,x,y) ((c) * CONF_CONTENT_NUM_ELEMENTS + \ @@ -264,6 +267,12 @@ static struct LevelFileConfigInfo chunk_config_INFO[] = &li.time_score_base, 1 }, + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(13), + &li.rate_time_over_score, FALSE + }, + { -1, -1, -1, -1, @@ -319,6 +328,11 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = TYPE_BOOLEAN, CONF_VALUE_8_BIT(16), &li.finish_dig_collect, TRUE }, + { + EL_PLAYER_1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(17), + &li.keep_walkable_ce, FALSE + }, // (these values are different for each player) { @@ -890,11 +904,34 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] = TYPE_INTEGER, CONF_VALUE_16_BIT(1), &li.mm_time_bomb, 75 }, + { EL_MM_GRAY_BALL, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), &li.mm_time_ball, 75 }, + { + EL_MM_GRAY_BALL, -1, + TYPE_INTEGER, CONF_VALUE_8_BIT(1), + &li.mm_ball_choice_mode, ANIM_RANDOM + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1), + &li.mm_ball_content, EL_EMPTY, NULL, + &li.num_mm_ball_contents, 8, MAX_MM_BALL_CONTENTS + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(3), + &li.rotate_mm_ball_content, TRUE + }, + { + EL_MM_GRAY_BALL, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(2), + &li.explode_mm_ball, FALSE + }, + { EL_MM_STEEL_BLOCK, -1, TYPE_INTEGER, CONF_VALUE_16_BIT(1), @@ -1363,6 +1400,26 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] = } }; +static struct LevelFileConfigInfo chunk_config_EMPX[] = +{ + { + -1, -1, + TYPE_BOOLEAN, CONF_VALUE_8_BIT(1), + &xx_ei.use_gfx_element, FALSE + }, + { + -1, -1, + TYPE_ELEMENT, CONF_VALUE_16_BIT(1), + &xx_ei.gfx_element_initial, EL_EMPTY_SPACE + }, + + { + -1, -1, + -1, -1, + NULL, -1 + } +}; + static struct LevelFileConfigInfo chunk_config_CONF[] = // (OBSOLETE) { { @@ -1807,6 +1864,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) int element = i; struct ElementInfo *ei = &element_info[element]; + if (element == EL_MM_GRAY_BALL) + { + struct LevelInfo_MM *level_mm = level->native_mm_level; + int j; + + for (j = 0; j < level->num_mm_ball_contents; j++) + level->mm_ball_content[j] = + map_element_MM_to_RND(level_mm->ball_content[j]); + } + // never initialize clipboard elements after the very first time // (to be able to use clipboard elements between several levels) if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized) @@ -1836,8 +1903,7 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) setElementChangeInfoToDefaults(ei->change); if (IS_CUSTOM_ELEMENT(element) || - IS_GROUP_ELEMENT(element) || - IS_INTERNAL_ELEMENT(element)) + IS_GROUP_ELEMENT(element)) { setElementDescriptionToDefault(ei); @@ -1880,6 +1946,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level) *group = xx_group; } + + if (IS_EMPTY_ELEMENT(element) || + IS_INTERNAL_ELEMENT(element)) + { + xx_ei = *ei; // copy element data into temporary buffer + + setConfigToDefaultsFromConfigList(chunk_config_EMPX); + + *ei = xx_ei; + } } clipboard_elements_initialized = TRUE; @@ -2801,9 +2877,10 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level) for (x = 0; x < 3; x++) ei->content.e[x][y] = getMappedElement(getFile16BitBE(file)); + // bits 0 - 31 of "has_event[]" event_bits = getFile32BitBE(file); - for (j = 0; j < NUM_CHANGE_EVENTS; j++) - if (event_bits & (1 << j)) + for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) + if (event_bits & (1u << j)) ei->change->has_event[j] = TRUE; ei->change->target_element = getMappedElement(getFile16BitBE(file)); @@ -2939,7 +3016,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) // bits 0 - 31 of "has_event[]" ... event_bits = getFile32BitBE(file); for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) - if (event_bits & (1 << j)) + if (event_bits & (1u << j)) change->has_event[j] = TRUE; change->target_element = getMappedElement(getFile16BitBE(file)); @@ -2980,7 +3057,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level) // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible) event_bits = getFile8Bit(file); for (j = 32; j < NUM_CHANGE_EVENTS; j++) - if (event_bits & (1 << (j - 32))) + if (event_bits & (1u << (j - 32))) change->has_event[j] = TRUE; } @@ -3147,7 +3224,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf, value = getMappedElement(value); if (data_type == TYPE_BOOLEAN) - *(boolean *)(conf[i].value) = value; + *(boolean *)(conf[i].value) = (value ? TRUE : FALSE); else *(int *) (conf[i].value) = value; @@ -3309,6 +3386,10 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level) while (!checkEndOfFile(file)) { + // level file might contain invalid change page number + if (xx_current_change_page >= ei->num_change_pages) + break; + struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page]; xx_change = *change; // copy change data into temporary buffer @@ -3338,6 +3419,9 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) struct ElementInfo *ei = &element_info[element]; struct ElementGroupInfo *group = ei->group; + if (group == NULL) + return -1; + xx_ei = *ei; // copy element data into temporary buffer xx_group = *group; // copy group data into temporary buffer @@ -3358,6 +3442,30 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level) return real_chunk_size; } +static int LoadLevel_EMPX(File *file, int chunk_size, struct LevelInfo *level) +{ + int element = getMappedElement(getFile16BitBE(file)); + int real_chunk_size = 2; + struct ElementInfo *ei = &element_info[element]; + + xx_ei = *ei; // copy element data into temporary buffer + + while (!checkEndOfFile(file)) + { + real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX, + -1, element); + + if (real_chunk_size >= chunk_size) + break; + } + + *ei = xx_ei; + + level->file_has_custom_elements = TRUE; + + return real_chunk_size; +} + static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -3480,6 +3588,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { "NOTE", -1, LoadLevel_NOTE }, { "CUSX", -1, LoadLevel_CUSX }, { "GRPX", -1, LoadLevel_GRPX }, + { "EMPX", -1, LoadLevel_EMPX }, { NULL, 0, NULL } }; @@ -3513,6 +3622,14 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, int chunk_size_expected = (chunk_info[i].loader)(file, chunk_size, level); + if (chunk_size_expected < 0) + { + Warn("error reading chunk '%s' in level file '%s'", + chunk_name, filename); + + break; + } + // the size of some chunks cannot be checked before reading other // chunks first (like "HEAD" and "BODY") that contain some header // information, so check them here @@ -3520,6 +3637,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level, { Warn("wrong size (%d) of chunk '%s' in level file '%s'", chunk_size, chunk_name, filename); + + break; } } } @@ -3593,6 +3712,7 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) cav->lenses_time = level->lenses_time; cav->magnify_time = level->magnify_time; + cav->wind_time = 9999; cav->wind_direction = map_direction_RND_to_EM(level->wind_direction_initial); @@ -3629,7 +3749,7 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level) // initialize player positions and delete players from the playfield for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++) { - if (ELEM_IS_PLAYER(level->field[x][y])) + if (IS_PLAYER_ELEMENT(level->field[x][y])) { int player_nr = GET_PLAYER_NR(level->field[x][y]); @@ -3930,12 +4050,11 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level) level->time_wheel = 0; level->amoeba_content = EL_EMPTY; -#if 1 - // original Supaplex does not use score values -- use default values -#else + // original Supaplex does not use score values -- rate by playing time for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) level->score[i] = 0; -#endif + + level->rate_time_over_score = TRUE; // there are no yamyams in supaplex levels for (i = 0; i < level->num_yamyam_contents; i++) @@ -4043,7 +4162,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level) static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) { struct LevelInfo_MM *level_mm = level->native_mm_level; - int x, y; + int i, x, y; level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH); level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT); @@ -4052,9 +4171,13 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) level_mm->kettles_needed = level->gems_needed; level_mm->auto_count_kettles = level->auto_count_gems; - level_mm->laser_red = level->mm_laser_red; - level_mm->laser_green = level->mm_laser_green; - level_mm->laser_blue = level->mm_laser_blue; + level_mm->mm_laser_red = level->mm_laser_red; + level_mm->mm_laser_green = level->mm_laser_green; + level_mm->mm_laser_blue = level->mm_laser_blue; + + level_mm->df_laser_red = level->df_laser_red; + level_mm->df_laser_green = level->df_laser_green; + level_mm->df_laser_blue = level->df_laser_blue; strcpy(level_mm->name, level->name); strcpy(level_mm->author, level->author); @@ -4071,6 +4194,15 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) level_mm->time_ball = level->mm_time_ball; level_mm->time_block = level->mm_time_block; + level_mm->num_ball_contents = level->num_mm_ball_contents; + level_mm->ball_choice_mode = level->mm_ball_choice_mode; + level_mm->rotate_ball_content = level->rotate_mm_ball_content; + level_mm->explode_ball = level->explode_mm_ball; + + for (i = 0; i < level->num_mm_ball_contents; i++) + level_mm->ball_content[i] = + map_element_RND_to_MM(level->mm_ball_content[i]); + for (x = 0; x < level->fieldx; x++) for (y = 0; y < level->fieldy; y++) Ur[x][y] = @@ -4080,7 +4212,7 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level) static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) { struct LevelInfo_MM *level_mm = level->native_mm_level; - int x, y; + int i, x, y; level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX); level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY); @@ -4089,9 +4221,13 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) level->gems_needed = level_mm->kettles_needed; level->auto_count_gems = level_mm->auto_count_kettles; - level->mm_laser_red = level_mm->laser_red; - level->mm_laser_green = level_mm->laser_green; - level->mm_laser_blue = level_mm->laser_blue; + level->mm_laser_red = level_mm->mm_laser_red; + level->mm_laser_green = level_mm->mm_laser_green; + level->mm_laser_blue = level_mm->mm_laser_blue; + + level->df_laser_red = level_mm->df_laser_red; + level->df_laser_green = level_mm->df_laser_green; + level->df_laser_blue = level_mm->df_laser_blue; strcpy(level->name, level_mm->name); @@ -4111,6 +4247,15 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level) level->mm_time_ball = level_mm->time_ball; level->mm_time_block = level_mm->time_block; + level->num_mm_ball_contents = level_mm->num_ball_contents; + level->mm_ball_choice_mode = level_mm->ball_choice_mode; + level->rotate_mm_ball_content = level_mm->rotate_ball_content; + level->explode_mm_ball = level_mm->explode_ball; + + for (i = 0; i < level->num_mm_ball_contents; i++) + level->mm_ball_content[i] = + map_element_MM_to_RND(level_mm->ball_content[i]); + for (x = 0; x < level->fieldx; x++) for (y = 0; y < level->fieldy; y++) level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]); @@ -5592,8 +5737,7 @@ static int getMappedElement_DC(int element) return getMappedElement(element); } -static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level, - int nr) +static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level) { byte header[DC_LEVEL_HEADER_SIZE]; int envelope_size; @@ -5842,7 +5986,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, } } - LoadLevelFromFileStream_DC(file, level, level_file_info->nr); + LoadLevelFromFileStream_DC(file, level); closeFile(file); } @@ -5883,6 +6027,21 @@ int getMappedElement_SB(int element_ascii, boolean use_ces) return EL_UNDEFINED; } +static void SetLevelSettings_SB(struct LevelInfo *level) +{ + // time settings + level->time = 0; + level->use_step_counter = TRUE; + + // score settings + level->score[SC_TIME_BONUS] = 0; + level->time_score_base = 1; + level->rate_time_over_score = TRUE; + + // game settings + level->auto_exit_sokoban = TRUE; +} + static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, struct LevelFileInfo *level_file_info, boolean level_info_only) @@ -6116,14 +6275,11 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level, } // set special level settings for Sokoban levels - - level->time = 0; - level->use_step_counter = TRUE; + SetLevelSettings_SB(level); if (load_xsb_to_ces) { // special global settings can now be set in level template - level->use_custom_template = TRUE; } } @@ -6456,6 +6612,41 @@ static void LoadLevel_InitVersion(struct LevelInfo *level) // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0 if (level->game_version <= VERSION_IDENT(4,2,2,0)) level->finish_dig_collect = FALSE; + + // CE changing to player was kept under the player if walkable up to 4.2.3.1 + if (level->game_version <= VERSION_IDENT(4,2,3,1)) + level->keep_walkable_ce = TRUE; +} + +static void LoadLevel_InitSettings_SB(struct LevelInfo *level) +{ + boolean is_sokoban_level = TRUE; // unless non-Sokoban elements found + int x, y; + + // check if this level is (not) a Sokoban level + for (y = 0; y < level->fieldy; y++) + for (x = 0; x < level->fieldx; x++) + if (!IS_SB_ELEMENT(Tile[x][y])) + is_sokoban_level = FALSE; + + if (is_sokoban_level) + { + // set special level settings for Sokoban levels + SetLevelSettings_SB(level); + } +} + +static void LoadLevel_InitSettings(struct LevelInfo *level) +{ + // adjust level settings for (non-native) Sokoban-style levels + LoadLevel_InitSettings_SB(level); + + // rename levels with title "nameless level" or if renaming is forced + if (leveldir_current->empty_level_name != NULL && + (strEqual(level->name, NAMELESS_LEVEL_NAME) || + leveldir_current->force_level_name)) + snprintf(level->name, MAX_LEVEL_NAME_LEN + 1, + leveldir_current->empty_level_name, level_nr); } static void LoadLevel_InitStandardElements(struct LevelInfo *level) @@ -6603,6 +6794,27 @@ static void LoadLevel_InitCustomElements(struct LevelInfo *level) element_info[element].ignition_delay = 8; } } + + // set mouse click change events to work for left/middle/right mouse button + if (level->game_version < VERSION_IDENT(4,2,3,0)) + { + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + struct ElementInfo *ei = &element_info[element]; + + for (j = 0; j < ei->num_change_pages; j++) + { + struct ElementChangeInfo *change = &ei->change_page[j]; + + if (change->has_event[CE_CLICKED_BY_MOUSE] || + change->has_event[CE_PRESSED_BY_MOUSE] || + change->has_event[CE_MOUSE_CLICKED_ON_X] || + change->has_event[CE_MOUSE_PRESSED_ON_X]) + change->trigger_side = CH_SIDE_ANY; + } + } + } } static void LoadLevel_InitElements(struct LevelInfo *level) @@ -6663,6 +6875,7 @@ static void LoadLevelTemplate_LoadAndInit(void) LoadLevel_InitVersion(&level_template); LoadLevel_InitElements(&level_template); + LoadLevel_InitSettings(&level_template); ActivateLevelTemplate(); } @@ -6703,6 +6916,7 @@ static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level) LoadLevel_InitVersion(&level); LoadLevel_InitElements(&level); LoadLevel_InitPlayfield(&level); + LoadLevel_InitSettings(&level); LoadLevel_InitNativeEngines(&level); } @@ -7171,7 +7385,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) event_bits = 0; for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++) if (change->has_event[j]) - event_bits |= (1 << j); + event_bits |= (1u << j); putFile32BitBE(file, event_bits); putFile16BitBE(file, change->target_element); @@ -7211,7 +7425,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) event_bits = 0; for (j = 32; j < NUM_CHANGE_EVENTS; j++) if (change->has_event[j]) - event_bits |= (1 << (j - 32)); + event_bits |= (1u << (j - 32)); putFile8Bit(file, event_bits); } } @@ -7462,6 +7676,22 @@ static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element) return chunk_size; } +static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element) +{ + struct ElementInfo *ei = &element_info[element]; + int chunk_size = 0; + int i; + + chunk_size += putFile16BitBE(file, element); + + xx_ei = *ei; // copy element data into temporary buffer + + for (i = 0; chunk_config_EMPX[i].data_type != -1; i++) + chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE); + + return chunk_size; +} + static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, boolean save_as_template) { @@ -7553,6 +7783,18 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename, SaveLevel_GRPX(file, level, element); } } + + for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++) + { + int element = GET_EMPTY_ELEMENT(i); + + chunk_size = SaveLevel_EMPX(NULL, level, element); + if (chunk_size > LEVEL_CHUNK_EMPX_UNCHANGED) // save if changed + { + putFileChunkBE(file, "EMPX", chunk_size); + SaveLevel_EMPX(file, level, element); + } + } } fclose(file); @@ -7628,10 +7870,55 @@ void DumpLevel(struct LevelInfo *level) Print("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no")); Print("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no")); Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no")); + Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no")); + + if (options.debug) + { + int i, j; + + for (i = 0; i < NUM_ENVELOPES; i++) + { + char *text = level->envelope[i].text; + int text_len = strlen(text); + boolean has_text = FALSE; + + for (j = 0; j < text_len; j++) + if (text[j] != ' ' && text[j] != '\n') + has_text = TRUE; + + if (has_text) + { + Print("\n"); + Print("Envelope %d:\n'%s'\n", i + 1, text); + } + } + } PrintLine("-", 79); } +void DumpLevels(void) +{ + static LevelDirTree *dumplevel_leveldir = NULL; + + dumplevel_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumplevel_leveldir); + + if (dumplevel_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumplevel_leveldir); + + if (global.dumplevel_level_nr < dumplevel_leveldir->first_level || + global.dumplevel_level_nr > dumplevel_leveldir->last_level) + Fail("no such level number: %d", global.dumplevel_level_nr); + + leveldir_current = dumplevel_leveldir; + + LoadLevel(global.dumplevel_level_nr); + DumpLevel(&level); + + CloseAllAndExit(0); +} + // ============================================================================ // tape file functions @@ -7657,6 +7944,7 @@ static void setTapeInfoToDefaults(void) tape.level_nr = level_nr; tape.counter = 0; tape.changed = FALSE; + tape.solved = FALSE; tape.recording = FALSE; tape.playing = FALSE; @@ -7665,6 +7953,7 @@ static void setTapeInfoToDefaults(void) tape.scr_fieldx = SCR_FIELDX_DEFAULT; tape.scr_fieldy = SCR_FIELDY_DEFAULT; + tape.no_info_chunk = TRUE; tape.no_valid_file = FALSE; } @@ -7742,8 +8031,7 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape) setTapeActionFlags(tape, getFile8Bit(file)); tape->property_bits = getFile8Bit(file); - - ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED); + tape->solved = getFile8Bit(file); engine_version = getFileVersion(file); if (engine_version > 0) @@ -7769,6 +8057,8 @@ static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape) int level_identifier_size; int i; + tape->no_info_chunk = FALSE; + level_identifier_size = getFile16BitBE(file); level_identifier = checked_malloc(level_identifier_size); @@ -8139,6 +8429,20 @@ void LoadSolutionTape(int nr) CopyNativeTape_SP_to_RND(&level); } +void LoadScoreTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + +void LoadScoreCacheTape(char *score_tape_basename, int nr) +{ + char *filename = getScoreCacheTapeFilename(score_tape_basename, nr); + + LoadTapeFromFilename(filename); +} + static boolean checkSaveTape_SCRN(struct TapeInfo *tape) { // chunk required for team mode tapes with non-default screen size @@ -8172,9 +8476,7 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) putFile8Bit(file, getTapeActionValue(tape)); putFile8Bit(file, tape->property_bits); - - // unused bytes not at the end here for 4-byte alignment of engine_version - WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED); + putFile8Bit(file, tape->solved); putFileVersion(file, tape->engine_version); } @@ -8267,13 +8569,10 @@ void SaveTapeToFilename(char *filename) SetFilePermissions(filename, PERMS_PRIVATE); } -void SaveTape(int nr) +static void SaveTapeExt(char *filename) { - char *filename = getTapeFilename(nr); int i; - InitTapeDirectory(leveldir_current->subdir); - tape.file_version = FILE_VERSION_ACTUAL; tape.game_version = GAME_VERSION_ACTUAL; @@ -8289,6 +8588,25 @@ void SaveTape(int nr) tape.changed = FALSE; } +void SaveTape(int nr) +{ + char *filename = getTapeFilename(nr); + + InitTapeDirectory(leveldir_current->subdir); + + SaveTapeExt(filename); +} + +void SaveScoreTape(int nr) +{ + char *filename = getScoreTapeFilename(tape.score_tape_basename, nr); + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreTapeDirectory(levelset.identifier, nr); + + SaveTapeExt(filename); +} + static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved, unsigned int req_state_added) { @@ -8333,11 +8651,47 @@ void DumpTape(struct TapeInfo *tape) } PrintLine("-", 79); + Print("Tape of Level %03d (file version %08d, game version %08d)\n", tape->level_nr, tape->file_version, tape->game_version); Print(" (effective engine version %08d)\n", tape->engine_version); Print("Level series identifier: '%s'\n", tape->level_identifier); + + Print("Solution tape: %s\n", + tape->solved ? "yes" : + tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no"); + + Print("Special tape properties: "); + if (tape->property_bits == TAPE_PROPERTY_NONE) + Print("[none]"); + if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG) + Print("[em_random_bug]"); + if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED) + Print("[game_speed]"); + if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE) + Print("[pause]"); + if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP) + Print("[single_step]"); + if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT) + Print("[snapshot]"); + if (tape->property_bits & TAPE_PROPERTY_REPLAYED) + Print("[replayed]"); + if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS) + Print("[tas_keys]"); + if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS) + Print("[small_graphics]"); + Print("\n"); + + int year2 = tape->date / 10000; + int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2); + int month_index_raw = (tape->date / 100) % 100; + int month_index = month_index_raw % 12; // prevent invalid index + int month = month_index + 1; + int day = tape->date % 100; + + Print("Tape date: %04d-%02d-%02d\n", year4, month, day); + PrintLine("-", 79); tape_frame_counter = 0; @@ -8375,95 +8729,726 @@ void DumpTape(struct TapeInfo *tape) PrintLine("-", 79); } +void DumpTapes(void) +{ + static LevelDirTree *dumptape_leveldir = NULL; + + dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.dumptape_leveldir); + + if (dumptape_leveldir == NULL) + Fail("no such level identifier: '%s'", global.dumptape_leveldir); + + if (global.dumptape_level_nr < dumptape_leveldir->first_level || + global.dumptape_level_nr > dumptape_leveldir->last_level) + Fail("no such level number: %d", global.dumptape_level_nr); + + leveldir_current = dumptape_leveldir; + + if (options.mytapes) + LoadTape(global.dumptape_level_nr); + else + LoadSolutionTape(global.dumptape_level_nr); + + DumpTape(&tape); + + CloseAllAndExit(0); +} + // ============================================================================ // score file functions // ============================================================================ -void LoadScore(int nr) +static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores) { int i; - char *filename = getScoreFilename(nr); - char cookie[MAX_LINE_LEN]; - char line[MAX_LINE_LEN]; - char *line_ptr; - FILE *file; - // always start with reliable default values for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - strcpy(highscore[i].Name, EMPTY_PLAYER_NAME); - highscore[i].Score = 0; + strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME); + strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME); + scores->entry[i].score = 0; + scores->entry[i].time = 0; + + scores->entry[i].id = -1; + strcpy(scores->entry[i].tape_date, UNKNOWN_NAME); + strcpy(scores->entry[i].platform, UNKNOWN_NAME); + strcpy(scores->entry[i].version, UNKNOWN_NAME); + strcpy(scores->entry[i].country_name, UNKNOWN_NAME); + strcpy(scores->entry[i].country_code, "??"); } - if (!(file = fopen(filename, MODE_READ))) - return; + scores->num_entries = 0; + scores->last_added = -1; + scores->last_added_local = -1; - // check file identifier - if (fgets(cookie, MAX_LINE_LEN, file) == NULL) - cookie[0] = '\0'; - if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') - cookie[strlen(cookie) - 1] = '\0'; + scores->updated = FALSE; + scores->uploaded = FALSE; + scores->tape_downloaded = FALSE; + scores->force_last_added = FALSE; - if (!checkCookieString(cookie, SCORE_COOKIE)) - { - Warn("unknown format of score file '%s'", filename); + // The following values are intentionally not reset here: + // - last_level_nr + // - last_entry_nr + // - next_level_nr + // - continue_playing + // - continue_on_return +} - fclose(file); +static void setScoreInfoToDefaults(void) +{ + setScoreInfoToDefaultsExt(&scores); +} - return; +static void setServerScoreInfoToDefaults(void) +{ + setScoreInfoToDefaultsExt(&server_scores); +} + +static void LoadScore_OLD(int nr) +{ + int i; + char *filename = getScoreFilename(nr); + char cookie[MAX_LINE_LEN]; + char line[MAX_LINE_LEN]; + char *line_ptr; + FILE *file; + + if (!(file = fopen(filename, MODE_READ))) + return; + + // check file identifier + if (fgets(cookie, MAX_LINE_LEN, file) == NULL) + cookie[0] = '\0'; + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; + + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); + + fclose(file); + + return; + } + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + if (fscanf(file, "%d", &scores.entry[i].score) == EOF) + Warn("fscanf() failed; %s", strerror(errno)); + + if (fgets(line, MAX_LINE_LEN, file) == NULL) + line[0] = '\0'; + + if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + for (line_ptr = line; *line_ptr; line_ptr++) + { + if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0') + { + strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN); + scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; + break; + } + } + } + + fclose(file); +} + +static void ConvertScore_OLD(void) +{ + // only convert score to time for levels that rate playing time over score + if (!level.rate_time_over_score) + return; + + // convert old score to playing time for score-less levels (like Supaplex) + int time_final_max = 999; + int i; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + int score = scores.entry[i].score; + + if (score > 0 && score < time_final_max) + scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND; + } +} + +static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores) +{ + scores->file_version = getFileVersion(file); + scores->game_version = getFileVersion(file); + + return chunk_size; +} + +static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores) +{ + char *level_identifier = NULL; + int level_identifier_size; + int i; + + level_identifier_size = getFile16BitBE(file); + + level_identifier = checked_malloc(level_identifier_size); + + for (i = 0; i < level_identifier_size; i++) + level_identifier[i] = getFile8Bit(file); + + strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN); + scores->level_identifier[MAX_FILENAME_LEN] = '\0'; + + checked_free(level_identifier); + + scores->level_nr = getFile16BitBE(file); + scores->num_entries = getFile16BitBE(file); + + chunk_size = 2 + level_identifier_size + 2 + 2; + + return chunk_size; +} + +static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + scores->entry[i].name[j] = getFile8Bit(file); + + scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0'; + } + + chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN; + + return chunk_size; +} + +static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile16BitBE(file); + + chunk_size = scores->num_entries * 2; + + return chunk_size; +} + +static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].score = getFile32BitBE(file); + + chunk_size = scores->num_entries * 4; + + return chunk_size; +} + +static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + scores->entry[i].time = getFile32BitBE(file); + + chunk_size = scores->num_entries * 4; + + return chunk_size; +} + +static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + scores->entry[i].tape_basename[j] = getFile8Bit(file); + + scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0'; + } + + chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + + return chunk_size; +} + +void LoadScore(int nr) +{ + char *filename = getScoreFilename(nr); + char cookie[MAX_LINE_LEN]; + char chunk_name[CHUNK_ID_LEN + 1]; + int chunk_size; + boolean old_score_file_format = FALSE; + File *file; + + // always start with reliable default values + setScoreInfoToDefaults(); + + if (!(file = openFile(filename, MODE_READ))) + return; + + getFileChunkBE(file, chunk_name, NULL); + if (strEqual(chunk_name, "RND1")) + { + getFile32BitBE(file); // not used + + getFileChunkBE(file, chunk_name, NULL); + if (!strEqual(chunk_name, "SCOR")) + { + Warn("unknown format of score file '%s'", filename); + + closeFile(file); + + return; + } + } + else // check for old file format with cookie string + { + strcpy(cookie, chunk_name); + if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL) + cookie[4] = '\0'; + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; + + if (!checkCookieString(cookie, SCORE_COOKIE_TMPL)) + { + Warn("unknown format of score file '%s'", filename); + + closeFile(file); + + return; + } + + old_score_file_format = TRUE; + } + + if (old_score_file_format) + { + // score files from versions before 4.2.4.0 without chunk structure + LoadScore_OLD(nr); + + // convert score to time, if possible (mainly for Supaplex levels) + ConvertScore_OLD(); + } + else + { + static struct + { + char *name; + int size; + int (*loader)(File *, int, struct ScoreInfo *); + } + chunk_info[] = + { + { "VERS", SCORE_CHUNK_VERS_SIZE, LoadScore_VERS }, + { "INFO", -1, LoadScore_INFO }, + { "NAME", -1, LoadScore_NAME }, + { "SCOR", -1, LoadScore_SCOR }, + { "SC4R", -1, LoadScore_SC4R }, + { "TIME", -1, LoadScore_TIME }, + { "TAPE", -1, LoadScore_TAPE }, + + { NULL, 0, NULL } + }; + + while (getFileChunkBE(file, chunk_name, &chunk_size)) + { + int i = 0; + + while (chunk_info[i].name != NULL && + !strEqual(chunk_name, chunk_info[i].name)) + i++; + + if (chunk_info[i].name == NULL) + { + Warn("unknown chunk '%s' in score file '%s'", + chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else if (chunk_info[i].size != -1 && + chunk_info[i].size != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in score file '%s'", + chunk_size, chunk_name, filename); + + ReadUnusedBytesFromFile(file, chunk_size); + } + else + { + // call function to load this score chunk + int chunk_size_expected = + (chunk_info[i].loader)(file, chunk_size, &scores); + + // the size of some chunks cannot be checked before reading other + // chunks first (like "HEAD" and "BODY") that contain some header + // information, so check them here + if (chunk_size_expected != chunk_size) + { + Warn("wrong size (%d) of chunk '%s' in score file '%s'", + chunk_size, chunk_name, filename); + } + } + } + } + + closeFile(file); +} + +#if ENABLE_HISTORIC_CHUNKS +void SaveScore_OLD(int nr) +{ + int i; + char *filename = getScoreFilename(nr); + FILE *file; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot save score for level %d", nr); + + return; + } + + fprintf(file, "%s\n\n", SCORE_COOKIE); + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + fprintf(file, "%d %s\n", scores.entry[i].score, scores.entry[i].name); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} +#endif + +static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores) +{ + putFileVersion(file, scores->file_version); + putFileVersion(file, scores->game_version); +} + +static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores) +{ + int level_identifier_size = strlen(scores->level_identifier) + 1; + int i; + + putFile16BitBE(file, level_identifier_size); + + for (i = 0; i < level_identifier_size; i++) + putFile8Bit(file, scores->level_identifier[i]); + + putFile16BitBE(file, scores->level_nr); + putFile16BitBE(file, scores->num_entries); +} + +static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + int name_size = strlen(scores->entry[i].name); + + for (j = 0; j < MAX_PLAYER_NAME_LEN; j++) + putFile8Bit(file, (j < name_size ? scores->entry[i].name[j] : 0)); + } +} + +static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile16BitBE(file, scores->entry[i].score); +} + +static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].score); +} + +static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores) +{ + int i; + + for (i = 0; i < scores->num_entries; i++) + putFile32BitBE(file, scores->entry[i].time); +} + +static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores) +{ + int i, j; + + for (i = 0; i < scores->num_entries; i++) + { + int size = strlen(scores->entry[i].tape_basename); + + for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++) + putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0)); + } +} + +static void SaveScoreToFilename(char *filename) +{ + FILE *file; + int info_chunk_size; + int name_chunk_size; + int scor_chunk_size; + int sc4r_chunk_size; + int time_chunk_size; + int tape_chunk_size; + boolean has_large_score_values; + int i; + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot save score file '%s'", filename); + + return; + } + + info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2; + name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN; + scor_chunk_size = scores.num_entries * 2; + sc4r_chunk_size = scores.num_entries * 4; + time_chunk_size = scores.num_entries * 4; + tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN; + + has_large_score_values = FALSE; + for (i = 0; i < scores.num_entries; i++) + if (scores.entry[i].score > 0xffff) + has_large_score_values = TRUE; + + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); + putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE); + + putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE); + SaveScore_VERS(file, &scores); + + putFileChunkBE(file, "INFO", info_chunk_size); + SaveScore_INFO(file, &scores); + + putFileChunkBE(file, "NAME", name_chunk_size); + SaveScore_NAME(file, &scores); + + if (has_large_score_values) + { + putFileChunkBE(file, "SC4R", sc4r_chunk_size); + SaveScore_SC4R(file, &scores); + } + else + { + putFileChunkBE(file, "SCOR", scor_chunk_size); + SaveScore_SCOR(file, &scores); + } + + putFileChunkBE(file, "TIME", time_chunk_size); + SaveScore_TIME(file, &scores); + + putFileChunkBE(file, "TAPE", tape_chunk_size); + SaveScore_TAPE(file, &scores); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} + +void SaveScore(int nr) +{ + char *filename = getScoreFilename(nr); + int i; + + // used instead of "leveldir_current->subdir" (for network games) + InitScoreDirectory(levelset.identifier); + + scores.file_version = FILE_VERSION_ACTUAL; + scores.game_version = GAME_VERSION_ACTUAL; + + strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN); + scores.level_identifier[MAX_FILENAME_LEN] = '\0'; + scores.level_nr = level_nr; + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + if (scores.entry[i].score == 0 && + scores.entry[i].time == 0 && + strEqual(scores.entry[i].name, EMPTY_PLAYER_NAME)) + break; + + scores.num_entries = i; + + if (scores.num_entries == 0) + return; + + SaveScoreToFilename(filename); +} + +static void LoadServerScoreFromCache(int nr) +{ + struct ScoreEntry score_entry; + struct + { + void *value; + boolean is_string; + int string_size; } + score_mapping[] = + { + { &score_entry.score, FALSE, 0 }, + { &score_entry.time, FALSE, 0 }, + { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN }, + { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN }, + { score_entry.tape_date, TRUE, MAX_ISO_DATE_LEN }, + { &score_entry.id, FALSE, 0 }, + { score_entry.platform, TRUE, MAX_PLATFORM_TEXT_LEN }, + { score_entry.version, TRUE, MAX_VERSION_TEXT_LEN }, + { score_entry.country_code, TRUE, MAX_COUNTRY_CODE_LEN }, + { score_entry.country_name, TRUE, MAX_COUNTRY_NAME_LEN }, + + { NULL, FALSE, 0 } + }; + char *filename = getScoreCacheFilename(nr); + SetupFileHash *score_hash = loadSetupFileHash(filename); + int i, j; + + server_scores.num_entries = 0; + + if (score_hash == NULL) + return; for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - if (fscanf(file, "%d", &highscore[i].Score) == EOF) - Warn("fscanf() failed; %s", strerror(errno)); + score_entry = server_scores.entry[i]; - if (fgets(line, MAX_LINE_LEN, file) == NULL) - line[0] = '\0'; + for (j = 0; score_mapping[j].value != NULL; j++) + { + char token[10]; - if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') - line[strlen(line) - 1] = '\0'; + sprintf(token, "%02d.%d", i, j); - for (line_ptr = line; *line_ptr; line_ptr++) - { - if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0') + char *value = getHashEntry(score_hash, token); + + if (value == NULL) + continue; + + if (score_mapping[j].is_string) { - strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN); - highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0'; - break; + char *score_value = (char *)score_mapping[j].value; + int value_size = score_mapping[j].string_size; + + strncpy(score_value, value, value_size); + score_value[value_size] = '\0'; + } + else + { + int *score_value = (int *)score_mapping[j].value; + + *score_value = atoi(value); } + + server_scores.num_entries = i + 1; } + + server_scores.entry[i] = score_entry; } - fclose(file); + freeSetupFileHash(score_hash); } -void SaveScore(int nr) +void LoadServerScore(int nr, boolean download_score) { - int i; - int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); - char *filename = getScoreFilename(nr); - FILE *file; + if (!setup.use_api_server) + return; - // used instead of "leveldir_current->subdir" (for network games) - InitScoreDirectory(levelset.identifier); + // always start with reliable default values + setServerScoreInfoToDefaults(); - if (!(file = fopen(filename, MODE_WRITE))) + // 1st step: load server scores from cache file (which may not exist) + // (this should prevent reading it while the thread is writing to it) + LoadServerScoreFromCache(nr); + + if (download_score && runtime.use_api_server) { - Warn("cannot save score for level %d", nr); + // 2nd step: download server scores from score server to cache file + // (as thread, as it might time out if the server is not reachable) + ApiGetScoreAsThread(nr); + } +} + +void PrepareScoreTapesForUpload(char *leveldir_subdir) +{ + MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir); + + // if score tape not uploaded, ask for uploading missing tapes later + if (!setup.has_remaining_tapes) + setup.ask_for_remaining_tapes = TRUE; + + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; + + SaveSetup_ServerSetup(); +} + +void SaveServerScore(int nr, boolean tape_saved) +{ + if (!runtime.use_api_server) + { + PrepareScoreTapesForUpload(leveldir_current->subdir); return; } - fprintf(file, "%s\n\n", SCORE_COOKIE); + ApiAddScoreAsThread(nr, tape_saved, NULL); +} - for (i = 0; i < MAX_SCORE_ENTRIES; i++) - fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name); +void SaveServerScoreFromFile(int nr, boolean tape_saved, + char *score_tape_filename) +{ + if (!runtime.use_api_server) + return; - fclose(file); + ApiAddScoreAsThread(nr, tape_saved, score_tape_filename); +} + +void LoadLocalAndServerScore(int nr, boolean download_score) +{ + int last_added_local = scores.last_added_local; + boolean force_last_added = scores.force_last_added; + + // needed if only showing server scores + setScoreInfoToDefaults(); + + if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY)) + LoadScore(nr); + + // restore last added local score entry (before merging server scores) + scores.last_added = scores.last_added_local = last_added_local; + + if (setup.use_api_server && + !strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_LOCAL_ONLY)) + { + // load server scores from cache file and trigger update from server + LoadServerScore(nr, download_score); - SetFilePermissions(filename, permissions); + // merge local scores with scores from server + MergeServerScore(); + } + + if (force_last_added) + scores.force_last_added = force_last_added; } @@ -8504,6 +9489,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.toons, "toons" }, + { + TYPE_SWITCH, + &setup.global_animations, "global_animations" + }, { TYPE_SWITCH, &setup.scroll_delay, "scroll_delay" @@ -8532,6 +9521,14 @@ static struct TokenInfo global_setup_tokens[] = TYPE_SWITCH, &setup.autorecord, "automatic_tape_recording" }, + { + TYPE_SWITCH, + &setup.autorecord_after_replay, "autorecord_after_replay" + }, + { + TYPE_SWITCH, + &setup.auto_pause_on_start, "auto_pause_on_start" + }, { TYPE_SWITCH, &setup.show_titlescreen, "show_titlescreen" @@ -8650,7 +9647,15 @@ static struct TokenInfo global_setup_tokens[] = }, { TYPE_SWITCH, - &setup.show_snapshot_buttons, "show_snapshot_buttons" + &setup.show_load_save_buttons, "show_load_save_buttons" + }, + { + TYPE_SWITCH, + &setup.show_undo_redo_buttons, "show_undo_redo_buttons" + }, + { + TYPE_STRING, + &setup.scores_in_highscore_list, "scores_in_highscore_list" }, { TYPE_STRING, @@ -8740,6 +9745,10 @@ static struct TokenInfo global_setup_tokens[] = TYPE_INTEGER, &setup.touch.grid_ysize[1], "touch.virtual_buttons.1.ysize" }, + { + TYPE_SWITCH, + &setup.touch.overlay_buttons, "touch.overlay_buttons" + }, }; static struct TokenInfo auto_setup_tokens[] = @@ -8750,6 +9759,50 @@ static struct TokenInfo auto_setup_tokens[] = }, }; +static struct TokenInfo server_setup_tokens[] = +{ + { + TYPE_STRING, + &setup.player_uuid, "player_uuid" + }, + { + TYPE_INTEGER, + &setup.player_version, "player_version" + }, + { + TYPE_SWITCH, + &setup.use_api_server, TEST_PREFIX "use_api_server" + }, + { + TYPE_STRING, + &setup.api_server_hostname, TEST_PREFIX "api_server_hostname" + }, + { + TYPE_STRING, + &setup.api_server_password, TEST_PREFIX "api_server_password" + }, + { + TYPE_SWITCH, + &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_remaining_tapes, TEST_PREFIX "ask_for_remaining_tapes" + }, + { + TYPE_SWITCH, + &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes" + }, + { + TYPE_SWITCH, + &setup.ask_for_using_api_server,TEST_PREFIX "ask_for_using_api_server" + }, + { + TYPE_SWITCH, + &setup.has_remaining_tapes, TEST_PREFIX "has_remaining_tapes" + }, +}; + static struct TokenInfo editor_setup_tokens[] = { { @@ -8840,6 +9893,10 @@ static struct TokenInfo editor_cascade_setup_tokens[] = TYPE_SWITCH, &setup.editor_cascade.el_ge, "editor.cascade.el_ge" }, + { + TYPE_SWITCH, + &setup.editor_cascade.el_es, "editor.cascade.el_es" + }, { TYPE_SWITCH, &setup.editor_cascade.el_ref, "editor.cascade.el_ref" @@ -8864,6 +9921,14 @@ static struct TokenInfo shortcut_setup_tokens[] = TYPE_KEY_X11, &setup.shortcut.load_game, "shortcut.load_game" }, + { + TYPE_KEY_X11, + &setup.shortcut.restart_game, "shortcut.restart_game" + }, + { + TYPE_KEY_X11, + &setup.shortcut.pause_before_end, "shortcut.pause_before_end" + }, { TYPE_KEY_X11, &setup.shortcut.toggle_pause, "shortcut.toggle_pause" @@ -9113,10 +10178,18 @@ static struct TokenInfo internal_setup_tokens[] = TYPE_BOOLEAN, &setup.internal.create_user_levelset, "create_user_levelset" }, + { + TYPE_BOOLEAN, + &setup.internal.info_screens_from_main, "info_screens_from_main" + }, { TYPE_BOOLEAN, &setup.internal.menu_game, "menu_game" }, + { + TYPE_BOOLEAN, + &setup.internal.menu_engines, "menu_engines" + }, { TYPE_BOOLEAN, &setup.internal.menu_editor, "menu_editor" @@ -9153,6 +10226,58 @@ static struct TokenInfo internal_setup_tokens[] = TYPE_BOOLEAN, &setup.internal.menu_save_and_exit, "menu_save_and_exit" }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_various, "menu_shortcuts_various" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_focus, "menu_shortcuts_focus" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_tape, "menu_shortcuts_tape" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_sound, "menu_shortcuts_sound" + }, + { + TYPE_BOOLEAN, + &setup.internal.menu_shortcuts_snap, "menu_shortcuts_snap" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_title, "info_title" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_elements, "info_elements" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_music, "info_music" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_credits, "info_credits" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_program, "info_program" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_version, "info_version" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_levelset, "info_levelset" + }, + { + TYPE_BOOLEAN, + &setup.internal.info_exit, "info_exit" + }, }; static struct TokenInfo debug_setup_tokens[] = @@ -9264,6 +10389,14 @@ static struct TokenInfo options_setup_tokens[] = TYPE_BOOLEAN, &setup.options.verbose, "options.verbose" }, + { + TYPE_BOOLEAN, + &setup.options.debug, "options.debug" + }, + { + TYPE_STRING, + &setup.options.debug_mode, "options.debug_mode" + }, }; static void setSetupInfoToDefaults(struct SetupInfo *si) @@ -9279,6 +10412,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->sound_music = TRUE; si->sound_simple = TRUE; si->toons = TRUE; + si->global_animations = TRUE; si->scroll_delay = TRUE; si->forced_scroll_delay = FALSE; si->scroll_delay_value = STD_SCROLL_DELAY; @@ -9286,6 +10420,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT; si->fade_screens = TRUE; si->autorecord = TRUE; + si->autorecord_after_replay = TRUE; + si->auto_pause_on_start = FALSE; si->show_titlescreen = TRUE; si->quick_doors = FALSE; si->team_mode = FALSE; @@ -9315,7 +10451,9 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->game_frame_delay = GAME_FRAME_DELAY; si->sp_show_border_elements = FALSE; si->small_game_graphics = FALSE; - si->show_snapshot_buttons = FALSE; + si->show_load_save_buttons = FALSE; + si->show_undo_redo_buttons = FALSE; + si->scores_in_highscore_list = getStringCopy(STR_SCORES_TYPE_DEFAULT); si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); @@ -9389,6 +10527,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->touch.grid_initialized = video.initialized; + si->touch.overlay_buttons = FALSE; + si->editor.el_boulderdash = TRUE; si->editor.el_emerald_mine = TRUE; si->editor.el_emerald_mine_club = TRUE; @@ -9420,6 +10560,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME; + si->shortcut.restart_game = DEFAULT_KEY_RESTART_GAME; + si->shortcut.pause_before_end = DEFAULT_KEY_PAUSE_BEFORE_END; si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE; si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1; @@ -9491,6 +10633,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->internal.choose_from_top_leveldir = FALSE; si->internal.show_scaling_in_title = TRUE; si->internal.create_user_levelset = TRUE; + si->internal.info_screens_from_main = FALSE; si->internal.default_window_width = WIN_XSIZE_DEFAULT; si->internal.default_window_height = WIN_YSIZE_DEFAULT; @@ -9526,9 +10669,12 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->debug.xsn_percent = 0; si->options.verbose = FALSE; + si->options.debug = FALSE; + si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING); #if defined(PLATFORM_ANDROID) si->fullscreen = TRUE; + si->touch.overlay_buttons = TRUE; #endif setHideSetupEntry(&setup.debug.xsn_mode); @@ -9539,6 +10685,21 @@ static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si) si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE; } +static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si) +{ + si->player_uuid = NULL; // (will be set later) + si->player_version = 1; // (will be set later) + + si->use_api_server = TRUE; + si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME); + si->api_server_password = getStringCopy(UNDEFINED_PASSWORD); + si->ask_for_uploading_tapes = TRUE; + si->ask_for_remaining_tapes = FALSE; + si->provide_uploading_tapes = TRUE; + si->ask_for_using_api_server = TRUE; + si->has_remaining_tapes = FALSE; +} + static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) { si->editor_cascade.el_bd = TRUE; @@ -9557,6 +10718,7 @@ static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si) si->editor_cascade.el_steel_chars = FALSE; si->editor_cascade.el_ce = FALSE; si->editor_cascade.el_ge = FALSE; + si->editor_cascade.el_es = FALSE; si->editor_cascade.el_ref = FALSE; si->editor_cascade.el_user = FALSE; si->editor_cascade.el_dynamic = FALSE; @@ -9626,7 +10788,7 @@ static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash, token_info[token_nr].text); } -static void decodeSetupFileHash(SetupFileHash *setup_file_hash) +static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash) { int i, pnr; @@ -9726,6 +10888,19 @@ static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash) auto_setup_tokens[i].text)); } +static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash) +{ + int i; + + if (!setup_file_hash) + return; + + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + setSetupInfo(server_setup_tokens, i, + getHashEntry(setup_file_hash, + server_setup_tokens[i].text)); +} + static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash) { int i; @@ -9782,7 +10957,7 @@ void LoadSetupFromFilename(char *filename) if (setup_file_hash) { - decodeSetupFileHash(setup_file_hash); + decodeSetupFileHash_Default(setup_file_hash); freeSetupFileHash(setup_file_hash); } @@ -9813,7 +10988,7 @@ static void LoadSetup_SpecialPostProcessing(void) MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY); } -void LoadSetup(void) +void LoadSetup_Default(void) { char *filename; @@ -9823,6 +10998,12 @@ void LoadSetup(void) // try to load setup values from default setup file filename = getDefaultSetupFilename(); + if (fileExists(filename)) + LoadSetupFromFilename(filename); + + // try to load setup values from platform setup file + filename = getPlatformSetupFilename(); + if (fileExists(filename)) LoadSetupFromFilename(filename); @@ -9854,6 +11035,35 @@ void LoadSetup_AutoSetup(void) free(filename); } +void LoadSetup_ServerSetup(void) +{ + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); + SetupFileHash *setup_file_hash = NULL; + + // always start with reliable default values + setSetupInfoToDefaults_ServerSetup(&setup); + + setup_file_hash = loadSetupFileHash(filename); + + if (setup_file_hash) + { + decodeSetupFileHash_ServerSetup(setup_file_hash); + + freeSetupFileHash(setup_file_hash); + } + + free(filename); + + if (setup.player_uuid == NULL) + { + // player UUID does not yet exist in setup file + setup.player_uuid = getStringCopy(getUUID()); + setup.player_version = 2; + + SaveSetup_ServerSetup(); + } +} + void LoadSetup_EditorCascade(void) { char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME); @@ -9874,6 +11084,14 @@ void LoadSetup_EditorCascade(void) free(filename); } +void LoadSetup(void) +{ + LoadSetup_Default(); + LoadSetup_AutoSetup(); + LoadSetup_ServerSetup(); + LoadSetup_EditorCascade(); +} + static void addGameControllerMappingToHash(SetupFileHash *mappings_hash, char *mapping_line) { @@ -9923,7 +11141,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash, fclose(file); } -void SaveSetup(void) +void SaveSetup_Default(void) { char *filename = getSetupFilename(); FILE *file; @@ -10016,18 +11234,47 @@ void SaveSetup(void) setup.debug.xsn_mode != AUTO) fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i)); - fprintf(file, "\n"); - for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) - fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i)); + fprintf(file, "\n"); + for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i)); + + fclose(file); + + SetFilePermissions(filename, PERMS_PRIVATE); +} + +void SaveSetup_AutoSetup(void) +{ + char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); + FILE *file; + int i; + + InitUserDataDirectory(); + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot write auto setup file '%s'", filename); + + free(filename); + + return; + } + + fprintFileHeader(file, AUTOSETUP_FILENAME); + + for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++) + fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i)); fclose(file); SetFilePermissions(filename, PERMS_PRIVATE); + + free(filename); } -void SaveSetup_AutoSetup(void) +void SaveSetup_ServerSetup(void) { - char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME); + char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME); FILE *file; int i; @@ -10035,17 +11282,23 @@ void SaveSetup_AutoSetup(void) if (!(file = fopen(filename, MODE_WRITE))) { - Warn("cannot write auto setup file '%s'", filename); + Warn("cannot write server setup file '%s'", filename); free(filename); return; } - fprintFileHeader(file, AUTOSETUP_FILENAME); + fprintFileHeader(file, SERVERSETUP_FILENAME); - for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++) - fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i)); + for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++) + { + // just to make things nicer :) + if (server_setup_tokens[i].value == &setup.use_api_server) + fprintf(file, "\n"); + + fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i)); + } fclose(file); @@ -10083,6 +11336,14 @@ void SaveSetup_EditorCascade(void) free(filename); } +void SaveSetup(void) +{ + SaveSetup_Default(); + SaveSetup_AutoSetup(); + SaveSetup_ServerSetup(); + SaveSetup_EditorCascade(); +} + static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash, char *filename) { @@ -10270,8 +11531,9 @@ static boolean string_has_parameter(char *s, char *s_contained) char next_char = s[strlen(s_contained)]; // check if next character is delimiter or whitespace - return (next_char == ',' || next_char == '\0' || - next_char == ' ' || next_char == '\t' ? TRUE : FALSE); + if (next_char == ',' || next_char == '\0' || + next_char == ' ' || next_char == '\t') + return TRUE; } // check if string contains another parameter string after a comma @@ -10289,6 +11551,85 @@ static boolean string_has_parameter(char *s, char *s_contained) return string_has_parameter(substring, s_contained); } +static int get_anim_parameter_value_ce(char *s) +{ + char *s_ptr = s; + char *pattern_1 = "ce_change:custom_"; + char *pattern_2 = ".page_"; + int pattern_1_len = strlen(pattern_1); + char *matching_char = strstr(s_ptr, pattern_1); + int result = ANIM_EVENT_NONE; + + if (matching_char == NULL) + return ANIM_EVENT_NONE; + + result = ANIM_EVENT_CE_CHANGE; + + s_ptr = matching_char + pattern_1_len; + + // check for custom element number ("custom_X", "custom_XX" or "custom_XXX") + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_ce_nr = (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + { + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0'); + } + + if (gic_ce_nr < 1 || gic_ce_nr > NUM_CUSTOM_ELEMENTS) + return ANIM_EVENT_NONE; + + // custom element stored as 0 to 255 + gic_ce_nr--; + + result |= gic_ce_nr << ANIM_EVENT_CE_BIT; + } + else + { + // invalid custom element number specified + + return ANIM_EVENT_NONE; + } + + // check for change page number ("page_X" or "page_XX") (optional) + if (strPrefix(s_ptr, pattern_2)) + { + s_ptr += strlen(pattern_2); + + if (*s_ptr >= '0' && *s_ptr <= '9') + { + int gic_page_nr = (*s_ptr++ - '0'); + + if (*s_ptr >= '0' && *s_ptr <= '9') + gic_page_nr = 10 * gic_page_nr + (*s_ptr++ - '0'); + + if (gic_page_nr < 1 || gic_page_nr > MAX_CHANGE_PAGES) + return ANIM_EVENT_NONE; + + // change page stored as 1 to 32 (0 means "all change pages") + + result |= gic_page_nr << ANIM_EVENT_PAGE_BIT; + } + else + { + // invalid animation part number specified + + return ANIM_EVENT_NONE; + } + } + + // discard result if next character is neither delimiter nor whitespace + if (!(*s_ptr == ',' || *s_ptr == '\0' || + *s_ptr == ' ' || *s_ptr == '\t')) + return ANIM_EVENT_NONE; + + return result; +} + static int get_anim_parameter_value(char *s) { int event_value[] = @@ -10314,6 +11655,11 @@ static int get_anim_parameter_value(char *s) int result = ANIM_EVENT_NONE; int i; + result = get_anim_parameter_value_ce(s); + + if (result != ANIM_EVENT_NONE) + return result; + for (i = 0; i < ARRAY_SIZE(event_value); i++) { matching_char = strstr(s_ptr, pattern_1[i]); @@ -10443,6 +11789,18 @@ static int get_anim_action_parameter_value(char *token) result = -(int)key; } + if (result == -1) + { + if (isURL(token)) + { + result = get_hash_from_key(token); // unsigned int => int + result = ABS(result); // may be negative now + result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0); + + setHashEntry(anim_url_hash, int2str(result, 0), token); + } + } + if (result == -1) result = ANIM_EVENT_ACTION_NONE; @@ -10471,6 +11829,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type) strEqual(value, "lower") ? POS_LOWER : strEqual(value, "bottom") ? POS_BOTTOM : strEqual(value, "any") ? POS_ANY : + strEqual(value, "ce") ? POS_CE : + strEqual(value, "ce_trigger") ? POS_CE_TRIGGER : strEqual(value, "last") ? POS_LAST : POS_UNDEFINED); } else if (strEqual(suffix, ".align")) @@ -10495,6 +11855,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "pingpong") ? ANIM_PINGPONG : string_has_parameter(value, "pingpong2") ? ANIM_PINGPONG2 : string_has_parameter(value, "random") ? ANIM_RANDOM : + string_has_parameter(value, "random_static") ? ANIM_RANDOM_STATIC : string_has_parameter(value, "ce_value") ? ANIM_CE_VALUE : string_has_parameter(value, "ce_score") ? ANIM_CE_SCORE : string_has_parameter(value, "ce_delay") ? ANIM_CE_DELAY : @@ -10502,6 +11863,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type) string_has_parameter(value, "vertical") ? ANIM_VERTICAL : string_has_parameter(value, "centered") ? ANIM_CENTERED : string_has_parameter(value, "all") ? ANIM_ALL : + string_has_parameter(value, "tiled") ? ANIM_TILED : + string_has_parameter(value, "level_nr") ? ANIM_LEVEL_NR : ANIM_DEFAULT); if (string_has_parameter(value, "once")) @@ -10558,11 +11921,16 @@ int get_parameter_value(char *value_raw, char *suffix, int type) if (string_has_parameter(value, "multiple_actions")) result |= STYLE_MULTIPLE_ACTIONS; + + if (string_has_parameter(value, "consume_ce_event")) + result |= STYLE_CONSUME_CE_EVENT; } else if (strEqual(suffix, ".fade_mode")) { result = (string_has_parameter(value, "none") ? FADE_MODE_NONE : string_has_parameter(value, "fade") ? FADE_MODE_FADE : + string_has_parameter(value, "fade_in") ? FADE_MODE_FADE_IN : + string_has_parameter(value, "fade_out") ? FADE_MODE_FADE_OUT : string_has_parameter(value, "crossfade") ? FADE_MODE_CROSSFADE : string_has_parameter(value, "melt") ? FADE_MODE_MELT : string_has_parameter(value, "curtain") ? FADE_MODE_CURTAIN : @@ -10609,14 +11977,18 @@ static int get_token_parameter_value(char *token, char *value_raw) return get_parameter_value(value_raw, suffix, TYPE_INTEGER); } -void InitMenuDesignSettings_Static(void) +void InitMenuDesignSettings_FromHash(SetupFileHash *setup_file_hash, + boolean ignore_defaults) { int i; - // always start with reliable default values from static default config for (i = 0; image_config_vars[i].token != NULL; i++) { - char *value = getHashEntry(image_config_hash, image_config_vars[i].token); + char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); + + // (ignore definitions set to "[DEFAULT]" which are already initialized) + if (ignore_defaults && strEqual(value, ARG_DEFAULT)) + continue; if (value != NULL) *image_config_vars[i].value = @@ -10624,6 +11996,12 @@ void InitMenuDesignSettings_Static(void) } } +void InitMenuDesignSettings_Static(void) +{ + // always start with reliable default values from static default config + InitMenuDesignSettings_FromHash(image_config_hash, FALSE); +} + static void InitMenuDesignSettings_SpecialPreProcessing(void) { int i; @@ -10829,7 +12207,7 @@ static void InitMenuDesignSettings_SpecialPostProcessing(void) vp_playfield->width = MIN(vp_playfield->width, vp_playfield->max_width); if (vp_playfield->max_height != -1) - vp_playfield->height = MIN(vp_playfield->height,vp_playfield->max_height); + vp_playfield->height = MIN(vp_playfield->height, vp_playfield->max_height); // adjust playfield position according to specified alignment @@ -11064,6 +12442,45 @@ static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void) } } +static void InitMenuDesignSettings_PreviewPlayers_Ext(SetupFileHash *hash, + boolean initialize) +{ + // special case: check if network and preview player positions are redefined, + // to compare this later against the main menu level preview being redefined + struct TokenIntPtrInfo menu_config_players[] = + { + { "main.network_players.x", &menu.main.network_players.redefined }, + { "main.network_players.y", &menu.main.network_players.redefined }, + { "main.preview_players.x", &menu.main.preview_players.redefined }, + { "main.preview_players.y", &menu.main.preview_players.redefined }, + { "preview.x", &preview.redefined }, + { "preview.y", &preview.redefined } + }; + int i; + + if (initialize) + { + for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) + *menu_config_players[i].value = FALSE; + } + else + { + for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) + if (getHashEntry(hash, menu_config_players[i].token) != NULL) + *menu_config_players[i].value = TRUE; + } +} + +static void InitMenuDesignSettings_PreviewPlayers(void) +{ + InitMenuDesignSettings_PreviewPlayers_Ext(NULL, TRUE); +} + +static void InitMenuDesignSettings_PreviewPlayers_FromHash(SetupFileHash *hash) +{ + InitMenuDesignSettings_PreviewPlayers_Ext(hash, FALSE); +} + static void LoadMenuDesignSettingsFromFilename(char *filename) { static struct TitleFadingInfo tfi; @@ -11198,7 +12615,9 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) { { "menu.draw_xoffset.INFO", &menu.draw_xoffset_info[i] }, { "menu.draw_yoffset.INFO", &menu.draw_yoffset_info[i] }, - { "menu.list_size.INFO", &menu.list_size_info[i] } + { "menu.list_size.INFO", &menu.list_size_info[i] }, + { "menu.list_entry_size.INFO", &menu.list_entry_size_info[i] }, + { "menu.tile_size.INFO", &menu.tile_size_info[i] } }; for (j = 0; j < ARRAY_SIZE(menu_config); j++) @@ -11238,6 +12657,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) struct TokenIntPtrInfo menu_config[] = { { "menu.left_spacing.INFO", &menu.left_spacing_info[i] }, + { "menu.middle_spacing.INFO", &menu.middle_spacing_info[i] }, { "menu.right_spacing.INFO", &menu.right_spacing_info[i] }, { "menu.top_spacing.INFO", &menu.top_spacing_info[i] }, { "menu.bottom_spacing.INFO", &menu.bottom_spacing_info[i] }, @@ -11402,35 +12822,11 @@ static void LoadMenuDesignSettingsFromFilename(char *filename) } } - // special case: check if network and preview player positions are redefined, - // to compare this later against the main menu level preview being redefined - struct TokenIntPtrInfo menu_config_players[] = - { - { "main.network_players.x", &menu.main.network_players.redefined }, - { "main.network_players.y", &menu.main.network_players.redefined }, - { "main.preview_players.x", &menu.main.preview_players.redefined }, - { "main.preview_players.y", &menu.main.preview_players.redefined }, - { "preview.x", &preview.redefined }, - { "preview.y", &preview.redefined } - }; - - for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) - *menu_config_players[i].value = FALSE; - - for (i = 0; i < ARRAY_SIZE(menu_config_players); i++) - if (getHashEntry(setup_file_hash, menu_config_players[i].token) != NULL) - *menu_config_players[i].value = TRUE; - // read (and overwrite with) values that may be specified in config file - for (i = 0; image_config_vars[i].token != NULL; i++) - { - char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); + InitMenuDesignSettings_FromHash(setup_file_hash, TRUE); - // (ignore definitions set to "[DEFAULT]" which are already initialized) - if (value != NULL && !strEqual(value, ARG_DEFAULT)) - *image_config_vars[i].value = - get_token_parameter_value(image_config_vars[i].token, value); - } + // special case: check if network and preview player positions are redefined + InitMenuDesignSettings_PreviewPlayers_FromHash(setup_file_hash); freeSetupFileHash(setup_file_hash); } @@ -11441,6 +12837,7 @@ void LoadMenuDesignSettings(void) InitMenuDesignSettings_Static(); InitMenuDesignSettings_SpecialPreProcessing(); + InitMenuDesignSettings_PreviewPlayers(); if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS)) { @@ -11563,11 +12960,13 @@ static struct MusicFileInfo *get_music_file_info_ext(char *basename, int music, { "artist_header", &tmp_music_file_info.artist_header }, { "album_header", &tmp_music_file_info.album_header }, { "year_header", &tmp_music_file_info.year_header }, + { "played_header", &tmp_music_file_info.played_header }, { "title", &tmp_music_file_info.title }, { "artist", &tmp_music_file_info.artist }, { "album", &tmp_music_file_info.album }, { "year", &tmp_music_file_info.year }, + { "played", &tmp_music_file_info.played }, { NULL, NULL }, }; @@ -11663,14 +13062,12 @@ static boolean sound_info_listed(struct MusicFileInfo *list, char *basename) void LoadMusicInfo(void) { - char *music_directory = getCustomMusicDirectory(); + int num_music_noconf = getMusicListSize_NoConf(); int num_music = getMusicListSize(); - int num_music_noconf = 0; int num_sounds = getSoundListSize(); - Directory *dir; - DirectoryEntry *dir_entry; struct FileInfo *music, *sound; struct MusicFileInfo *next, **new; + int i; while (music_file_info != NULL) @@ -11683,11 +13080,13 @@ void LoadMusicInfo(void) checked_free(music_file_info->artist_header); checked_free(music_file_info->album_header); checked_free(music_file_info->year_header); + checked_free(music_file_info->played_header); checked_free(music_file_info->title); checked_free(music_file_info->artist); checked_free(music_file_info->album); checked_free(music_file_info->year); + checked_free(music_file_info->played); free(music_file_info); @@ -11696,76 +13095,68 @@ void LoadMusicInfo(void) new = &music_file_info; - for (i = 0; i < num_music; i++) + // get (configured or unconfigured) music file info for all levels + for (i = leveldir_current->first_level; + i <= leveldir_current->last_level; i++) { - music = getMusicListEntry(i); + int music_nr; - if (music->filename == NULL) - continue; + if (levelset.music[i] != MUS_UNDEFINED) + { + // get music file info for configured level music + music_nr = levelset.music[i]; + } + else if (num_music_noconf > 0) + { + // get music file info for unconfigured level music + int level_pos = i - leveldir_current->first_level; - if (strEqual(music->filename, UNDEFINED_FILENAME)) + music_nr = MAP_NOCONF_MUSIC(level_pos % num_music_noconf); + } + else + { continue; + } - // a configured file may be not recognized as music - if (!FileIsMusic(music->filename)) + char *basename = getMusicInfoEntryFilename(music_nr); + + if (basename == NULL) continue; - if (!music_info_listed(music_file_info, music->filename)) + if (!music_info_listed(music_file_info, basename)) { - *new = get_music_file_info(music->filename, i); + *new = get_music_file_info(basename, music_nr); if (*new != NULL) new = &(*new)->next; } } - if ((dir = openDirectory(music_directory)) == NULL) - { - Warn("cannot read music directory '%s'", music_directory); - - return; - } - - while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries + // get music file info for all remaining configured music files + for (i = 0; i < num_music; i++) { - char *basename = dir_entry->basename; - boolean music_already_used = FALSE; - int i; - - // skip all music files that are configured in music config file - for (i = 0; i < num_music; i++) - { - music = getMusicListEntry(i); - - if (music->filename == NULL) - continue; + music = getMusicListEntry(i); - if (strEqual(basename, music->filename)) - { - music_already_used = TRUE; - break; - } - } + if (music->filename == NULL) + continue; - if (music_already_used) + if (strEqual(music->filename, UNDEFINED_FILENAME)) continue; - if (!FileIsMusic(dir_entry->filename)) + // a configured file may be not recognized as music + if (!FileIsMusic(music->filename)) continue; - if (!music_info_listed(music_file_info, basename)) + if (!music_info_listed(music_file_info, music->filename)) { - *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf)); + *new = get_music_file_info(music->filename, i); if (*new != NULL) new = &(*new)->next; } - - num_music_noconf++; } - closeDirectory(dir); - + // get sound file info for all configured sound files for (i = 0; i < num_sounds; i++) { sound = getSoundListEntry(i); @@ -11787,6 +13178,18 @@ void LoadMusicInfo(void) new = &(*new)->next; } } + + // add pointers to previous list nodes + + struct MusicFileInfo *node = music_file_info; + + while (node != NULL) + { + if (node->next) + node->next->prev = node; + + node = node->next; + } } static void add_helpanim_entry(int element, int action, int direction, @@ -12132,6 +13535,11 @@ void ConvertLevels(void) Print("converting level ... "); +#if 0 + // special case: conversion of some EMC levels as requested by ACME + level.game_engine_type = GAME_ENGINE_TYPE_RND; +#endif + level_filename = getDefaultLevelFilename(level_nr); new_level = !fileExists(level_filename); @@ -12209,8 +13617,8 @@ void CreateLevelSketchImages(void) sprintf(basename1, "%04d.bmp", i); sprintf(basename2, "%04ds.bmp", i); - filename1 = getPath2(global.create_images_dir, basename1); - filename2 = getPath2(global.create_images_dir, basename2); + filename1 = getPath2(global.create_sketch_images_dir, basename1); + filename2 = getPath2(global.create_sketch_images_dir, basename2); DrawSizedElement(0, 0, element, TILESIZE); BlitBitmap(drawto, bitmap1, SX, SY, TILEX, TILEY, 0, 0); @@ -12254,6 +13662,135 @@ void CreateLevelSketchImages(void) } +// ---------------------------------------------------------------------------- +// create and save images for element collecting animations (raw BMP format) +// ---------------------------------------------------------------------------- + +static boolean createCollectImage(int element) +{ + return (IS_COLLECTIBLE(element) && !IS_SP_ELEMENT(element)); +} + +void CreateCollectElementImages(void) +{ + int i, j; + int num_steps = 8; + int anim_frames = num_steps - 1; + int tile_size = TILESIZE; + int anim_width = tile_size * anim_frames; + int anim_height = tile_size; + int num_collect_images = 0; + int pos_collect_images = 0; + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + if (createCollectImage(i)) + num_collect_images++; + + Info("Creating %d element collecting animation images ...", + num_collect_images); + + int dst_width = anim_width * 2; + int dst_height = anim_height * num_collect_images / 2; + Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH); + char *basename_bmp = "RocksCollect.bmp"; + char *basename_png = "RocksCollect.png"; + char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp); + char *filename_png = getPath2(global.create_collect_images_dir, basename_png); + int len_filename_bmp = strlen(filename_bmp); + int len_filename_png = strlen(filename_png); + int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png; + char cmd_convert[max_command_len]; + + snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"", + filename_bmp, + filename_png); + + // force using RGBA surface for destination bitmap + SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00)); + + dst_bitmap->surface = + SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + if (!createCollectImage(i)) + continue; + + int dst_x = (pos_collect_images / (num_collect_images / 2)) * anim_width; + int dst_y = (pos_collect_images % (num_collect_images / 2)) * anim_height; + int graphic = el2img(i); + char *token_name = element_info[i].token_name; + Bitmap *tmp_bitmap = CreateBitmap(tile_size, tile_size, DEFAULT_DEPTH); + Bitmap *src_bitmap; + int src_x, src_y; + + Info("- creating collecting image for '%s' ...", token_name); + + getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y); + + BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y, + tile_size, tile_size, 0, 0); + + // force using RGBA surface for temporary bitmap (using transparent black) + SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00)); + + tmp_bitmap->surface = + SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0); + + tmp_bitmap->surface_masked = tmp_bitmap->surface; + + for (j = 0; j < anim_frames; j++) + { + int frame_size_final = tile_size * (anim_frames - j) / num_steps; + int frame_size = frame_size_final * num_steps; + int offset = (tile_size - frame_size_final) / 2; + Bitmap *frame_bitmap = ZoomBitmap(tmp_bitmap, frame_size, frame_size); + + while (frame_size > frame_size_final) + { + frame_size /= 2; + + Bitmap *half_bitmap = ZoomBitmap(frame_bitmap, frame_size, frame_size); + + FreeBitmap(frame_bitmap); + + frame_bitmap = half_bitmap; + } + + BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0, + frame_size_final, frame_size_final, + dst_x + j * tile_size + offset, dst_y + offset); + + FreeBitmap(frame_bitmap); + } + + tmp_bitmap->surface_masked = NULL; + + FreeBitmap(tmp_bitmap); + + pos_collect_images++; + } + + if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0) + Fail("cannot save element collecting image file '%s'", filename_bmp); + + FreeBitmap(dst_bitmap); + + Info("Converting image file from BMP to PNG ..."); + + if (system(cmd_convert) != 0) + Fail("converting image file failed"); + + unlink(filename_bmp); + + Info("Done."); + + CloseAllAndExit(0); +} + + // ---------------------------------------------------------------------------- // create and save images for custom and group elements (raw BMP format) // ---------------------------------------------------------------------------- diff --git a/src/files.h b/src/files.h index b82c89e5..887a9fdf 100644 --- a/src/files.h +++ b/src/files.h @@ -44,6 +44,7 @@ void SaveLevel(int); void SaveLevelTemplate(void); void SaveNativeLevel(struct LevelInfo *); void DumpLevel(struct LevelInfo *); +void DumpLevels(void); boolean SaveLevelChecked(int); void CopyNativeLevel_RND_to_Native(struct LevelInfo *); @@ -52,27 +53,45 @@ void CopyNativeLevel_Native_to_RND(struct LevelInfo *); void LoadTapeFromFilename(char *); void LoadTape(int); void LoadSolutionTape(int); +void LoadScoreTape(char *, int); +void LoadScoreCacheTape(char *, int); void SaveTapeToFilename(char *); void SaveTape(int); +void SaveScoreTape(int); void DumpTape(struct TapeInfo *); +void DumpTapes(void); boolean SaveTapeChecked(int); boolean SaveTapeChecked_LevelSolved(int); void LoadScore(int); void SaveScore(int); +void LoadServerScore(int, boolean); +void SaveServerScore(int, boolean); +void SaveServerScoreFromFile(int, boolean, char *); + +void LoadLocalAndServerScore(int, boolean); + +void PrepareScoreTapesForUpload(char *); + void LoadUserNames(void); void LoadSetupFromFilename(char *); -void LoadSetup(void); -void SaveSetup(void); +void LoadSetup_Default(void); +void SaveSetup_Default(void); void LoadSetup_AutoSetup(void); void SaveSetup_AutoSetup(void); +void LoadSetup_ServerSetup(void); +void SaveSetup_ServerSetup(void); + void LoadSetup_EditorCascade(void); void SaveSetup_EditorCascade(void); +void LoadSetup(void); +void SaveSetup(void); + void SaveSetup_AddGameControllerMapping(char *); void setHideSetupEntry(void *); @@ -80,6 +99,7 @@ void removeHideSetupEntry(void *); boolean hideSetupEntry(void *); void LoadCustomElementDescriptions(void); +void InitMenuDesignSettings_FromHash(SetupFileHash *, boolean); void InitMenuDesignSettings_Static(void); void LoadMenuDesignSettings(void); void LoadMenuDesignSettings_AfterGraphics(void); @@ -90,6 +110,7 @@ void LoadHelpTextInfo(void); void ConvertLevels(void); void CreateLevelSketchImages(void); +void CreateCollectElementImages(void); void CreateCustomElementImages(char *); void FreeGlobalAnimEventInfo(void); diff --git a/src/game.c b/src/game.c index 07df1fe5..fd943b99 100644 --- a/src/game.c +++ b/src/game.c @@ -962,7 +962,7 @@ static struct GamePanelControlInfo game_panel_controls[] = ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND) #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y])) + ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y])) #define PACMAN_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y])) @@ -1017,19 +1017,22 @@ static struct GamePanelControlInfo game_panel_controls[] = #define GAME_CTRL_ID_SAVE 5 #define GAME_CTRL_ID_PAUSE2 6 #define GAME_CTRL_ID_LOAD 7 -#define GAME_CTRL_ID_PANEL_STOP 8 -#define GAME_CTRL_ID_PANEL_PAUSE 9 -#define GAME_CTRL_ID_PANEL_PLAY 10 -#define GAME_CTRL_ID_TOUCH_STOP 11 -#define GAME_CTRL_ID_TOUCH_PAUSE 12 -#define SOUND_CTRL_ID_MUSIC 13 -#define SOUND_CTRL_ID_LOOPS 14 -#define SOUND_CTRL_ID_SIMPLE 15 -#define SOUND_CTRL_ID_PANEL_MUSIC 16 -#define SOUND_CTRL_ID_PANEL_LOOPS 17 -#define SOUND_CTRL_ID_PANEL_SIMPLE 18 - -#define NUM_GAME_BUTTONS 19 +#define GAME_CTRL_ID_RESTART 8 +#define GAME_CTRL_ID_PANEL_STOP 9 +#define GAME_CTRL_ID_PANEL_PAUSE 10 +#define GAME_CTRL_ID_PANEL_PLAY 11 +#define GAME_CTRL_ID_PANEL_RESTART 12 +#define GAME_CTRL_ID_TOUCH_STOP 13 +#define GAME_CTRL_ID_TOUCH_PAUSE 14 +#define GAME_CTRL_ID_TOUCH_RESTART 15 +#define SOUND_CTRL_ID_MUSIC 16 +#define SOUND_CTRL_ID_LOOPS 17 +#define SOUND_CTRL_ID_SIMPLE 18 +#define SOUND_CTRL_ID_PANEL_MUSIC 19 +#define SOUND_CTRL_ID_PANEL_LOOPS 20 +#define SOUND_CTRL_ID_PANEL_SIMPLE 21 + +#define NUM_GAME_BUTTONS 22 // forward declaration for internal use @@ -1058,7 +1061,10 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *); static void KillPlayerUnlessEnemyProtected(int, int); static void KillPlayerUnlessExplosionProtected(int, int); +static void CheckNextToConditions(int, int); +static void TestIfPlayerNextToCustomElement(int, int); static void TestIfPlayerTouchesCustomElement(int, int); +static void TestIfElementNextToCustomElement(int, int); static void TestIfElementTouchesCustomElement(int, int); static void TestIfElementHitsCustomElement(int, int, int); @@ -1066,9 +1072,9 @@ static void HandleElementChange(int, int, int); static void ExecuteCustomElementAction(int, int, int, int); static boolean ChangeElement(int, int, int, int); -static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int); +static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int); #define CheckTriggeredElementChange(x, y, e, ev) \ - CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1) + CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1) #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \ CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1) #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \ @@ -1106,7 +1112,7 @@ void ContinueMoving(int, int); void Bang(int, int); void InitMovDir(int, int); void InitAmoebaNr(int, int); -int NewHiScore(int); +void NewHighScore(int, boolean); void TestIfGoodThingHitsBadThing(int, int, int); void TestIfBadThingHitsGoodThing(int, int, int); @@ -1551,6 +1557,14 @@ access_direction_list[] = { EL_UNDEFINED, MV_NONE } }; +static struct XY xy_topdown[] = +{ + { 0, -1 }, + { -1, 0 }, + { +1, 0 }, + { 0, +1 } +}; + static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS]; #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY]) @@ -1773,7 +1787,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) player->active = TRUE; // remove potentially duplicate players - if (StorePlayer[jx][jy] == Tile[x][y]) + if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y]) StorePlayer[jx][jy] = 0; StorePlayer[x][y] = Tile[x][y]; @@ -1834,15 +1848,15 @@ static void InitField(int x, int y, boolean init_game) break; case EL_STONEBLOCK: - if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID) + if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID) Tile[x][y] = EL_ACID_POOL_TOPLEFT; - else if (x > 0 && Tile[x-1][y] == EL_ACID) + else if (x > 0 && Tile[x - 1][y] == EL_ACID) Tile[x][y] = EL_ACID_POOL_TOPRIGHT; - else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT) + else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT) Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT; - else if (y > 0 && Tile[x][y-1] == EL_ACID) + else if (y > 0 && Tile[x][y - 1] == EL_ACID) Tile[x][y] = EL_ACID_POOL_BOTTOM; - else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT) + else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT) Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT; break; @@ -2025,6 +2039,14 @@ static void InitField(int x, int y, boolean init_game) InitField(x, y, init_game); } + else if (IS_EMPTY_ELEMENT(element)) + { + GfxElementEmpty[x][y] = element; + Tile[x][y] = EL_EMPTY; + + if (element_info[element].use_gfx_element) + game.use_masked_elements = TRUE; + } break; } @@ -2228,7 +2250,7 @@ static void UpdateGameControlValues(void) game_sp.time_played : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.energy_left : - game.no_time_limit ? TimePlayed : TimeLeft); + game.no_level_time_limit ? TimePlayed : TimeLeft); int score = (game.LevelSolved ? game.LevelSolved_CountingScore : level.game_engine_type == GAME_ENGINE_TYPE_EM ? @@ -2402,7 +2424,7 @@ static void UpdateGameControlValues(void) } game_panel_controls[GAME_PANEL_SCORE].value = score; - game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score; + game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score; game_panel_controls[GAME_PANEL_TIME].value = time; @@ -2547,7 +2569,9 @@ static void UpdateGameControlValues(void) int element = gpc->value; int graphic = el2panelimg(element); int init_gfx_random = (graphic_info[graphic].anim_global_sync ? - sync_random_frame : INIT_GFX_RANDOM()); + sync_random_frame : + graphic_info[graphic].anim_global_anim_sync ? + getGlobalAnimSyncFrame() : INIT_GFX_RANDOM()); if (gpc->value != gpc->last_value) { @@ -2582,7 +2606,9 @@ static void UpdateGameControlValues(void) int last_anim_random_frame = gfx.anim_random_frame; int graphic = gpc->graphic; int init_gfx_random = (graphic_info[graphic].anim_global_sync ? - sync_random_frame : INIT_GFX_RANDOM()); + sync_random_frame : + graphic_info[graphic].anim_global_anim_sync ? + getGlobalAnimSyncFrame() : INIT_GFX_RANDOM()); if (gpc->value != gpc->last_value) { @@ -2670,15 +2696,25 @@ static void DisplayGameControlValues(void) if (type == TYPE_INTEGER) { if (nr == GAME_PANEL_LEVEL_NUMBER || + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_SCORE || + nr == GAME_PANEL_HIGHSCORE || nr == GAME_PANEL_TIME) { boolean use_dynamic_size = (size == -1 ? TRUE : FALSE); if (use_dynamic_size) // use dynamic number of digits { - int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000); - int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3); - int size2 = size1 + 1; + int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 1000 : 100000); + int size_add = (nr == GAME_PANEL_LEVEL_NUMBER || + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 1 : 2); + int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 3 : 5); + int size2 = size1 + size_add; int font1 = pos->font; int font2 = pos->font_alt; @@ -3062,6 +3098,9 @@ static void InitGameEngine(void) game_em.use_single_button = (game.engine_version > VERSION_IDENT(4,0,0,2)); + game_em.use_push_delay = + (game.engine_version > VERSION_IDENT(4,3,7,1)); + game_em.use_snap_key_bug = (game.engine_version < VERSION_IDENT(4,0,1,0)); @@ -3162,6 +3201,17 @@ static void InitGameEngine(void) SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE); } + // ---------- initialize if element can trigger global animations ----------- + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + struct ElementInfo *ei = &element_info[i]; + + ei->has_anim_event = FALSE; + } + + InitGlobalAnimEventsForCustomElements(); + // ---------- initialize internal run-time variables ------------------------ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) @@ -3227,12 +3277,16 @@ static void InitGameEngine(void) for (j = 0; j < ei->num_change_pages; j++) { - ei->change_page[j].actual_trigger_element = EL_EMPTY; - ei->change_page[j].actual_trigger_player = EL_EMPTY; - ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE; - ei->change_page[j].actual_trigger_side = CH_SIDE_NONE; - ei->change_page[j].actual_trigger_ce_value = 0; - ei->change_page[j].actual_trigger_ce_score = 0; + struct ElementChangeInfo *change = &ei->change_page[j]; + + change->actual_trigger_element = EL_EMPTY; + change->actual_trigger_player = EL_EMPTY; + change->actual_trigger_player_bits = CH_PLAYER_NONE; + change->actual_trigger_side = CH_SIDE_NONE; + change->actual_trigger_ce_value = 0; + change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } } @@ -3250,16 +3304,18 @@ static void InitGameEngine(void) for (j = 0; j < ei->num_change_pages; j++) { - if (!ei->change_page[j].can_change_or_has_action) + struct ElementChangeInfo *change = &ei->change_page[j]; + + if (!change->can_change_or_has_action) continue; - if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION]) + if (change->has_event[CE_BY_OTHER_ACTION]) { - int trigger_element = ei->change_page[j].trigger_element; + int trigger_element = change->trigger_element; for (k = 0; k < NUM_CHANGE_EVENTS; k++) { - if (ei->change_page[j].has_event[k]) + if (change->has_event[k]) { if (IS_GROUP_ELEMENT(trigger_element)) { @@ -3426,8 +3482,9 @@ static void InitGameEngine(void) level.game_engine_type == GAME_ENGINE_TYPE_EM && !setup.forced_scroll_delay ? 0 : setup.scroll_delay ? setup.scroll_delay_value : 0); - game.scroll_delay_value = - MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY); + if (game.forced_scroll_delay_value == -1) + game.scroll_delay_value = + MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY); // ---------- initialize game engine snapshots ------------------------------ for (i = 0; i < MAX_PLAYERS; i++) @@ -3468,10 +3525,10 @@ static void InitGameEngine(void) { int element = EL_CUSTOM_START + i; - if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || - HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) || - HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) || - HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X)) + if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) || + HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X)) game.use_mouse_actions = TRUE; } } @@ -3540,9 +3597,8 @@ void InitGame(void) int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0); int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0); int fade_mask = REDRAW_FIELD; - + boolean restarting = (game_status == GAME_MODE_PLAYING); boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found - boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found int initial_move_dir = MV_DOWN; int i, j, x, y; @@ -3553,7 +3609,15 @@ void InitGame(void) if (!game.restart_level) CloseDoor(DOOR_CLOSE_1); - SetGameStatus(GAME_MODE_PLAYING); + if (restarting) + { + // force fading out global animations displayed during game play + SetGameStatus(GAME_MODE_PSEUDO_RESTARTING); + } + else + { + SetGameStatus(GAME_MODE_PLAYING); + } if (level_editor_test_game) FadeSkipNextFadeOut(); @@ -3569,6 +3633,18 @@ void InitGame(void) FadeOut(fade_mask); + if (restarting) + { + // force restarting global animations displayed during game play + RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING); + + // this is required for "transforming" fade modes like cross-fading + // (else global animations will be stopped, but not restarted here) + SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING); + + SetGameStatus(GAME_MODE_PLAYING); + } + if (level_editor_test_game) FadeSkipNextFadeIn(); @@ -3660,7 +3736,8 @@ void InitGame(void) player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr); - player->actual_frame_counter = 0; + player->actual_frame_counter.count = 0; + player->actual_frame_counter.value = 1; player->step_counter = 0; @@ -3796,9 +3873,12 @@ void InitGame(void) game.LevelSolved_CountingScore = 0; game.LevelSolved_CountingHealth = 0; + game.RestartGameRequested = FALSE; + game.panel.active = TRUE; - game.no_time_limit = (level.time == 0); + game.no_level_time_limit = (level.time == 0); + game.time_limit = (leveldir_current->time_limit && setup.time_limit); game.yamyam_content_nr = 0; game.robot_wheel_active = FALSE; @@ -3809,6 +3889,9 @@ void InitGame(void) game.switchgate_pos = 0; game.wind_direction = level.wind_direction_initial; + game.time_final = 0; + game.score_time_final = 0; + game.score = 0; game.score_final = 0; @@ -3832,6 +3915,9 @@ void InitGame(void) game.envelope_active = FALSE; + // special case: set custom artwork setting to initial value + game.use_masked_elements = game.use_masked_elements_initial; + for (i = 0; i < NUM_BELTS; i++) { game.belt_dir[i] = MV_NONE; @@ -3873,7 +3959,9 @@ void InitGame(void) GfxFrame[x][y] = 0; GfxRandom[x][y] = INIT_GFX_RANDOM(); + GfxRandomStatic[x][y] = INIT_GFX_RANDOM(); GfxElement[x][y] = EL_UNDEFINED; + GfxElementEmpty[x][y] = EL_EMPTY; GfxAction[x][y] = ACTION_DEFAULT; GfxDir[x][y] = MV_NONE; GfxRedraw[x][y] = GFX_REDRAW_NONE; @@ -3883,8 +3971,6 @@ void InitGame(void) { if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y])) emulate_bd = FALSE; - if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y])) - emulate_sb = FALSE; if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y])) emulate_sp = FALSE; @@ -3895,6 +3981,10 @@ void InitGame(void) InitBeltMovement(); + // required if level does not contain any "empty space" element + if (element_info[EL_EMPTY].use_gfx_element) + game.use_masked_elements = TRUE; + for (i = 0; i < MAX_PLAYERS; i++) { struct PlayerInfo *player = &stored_player[i]; @@ -3909,7 +3999,6 @@ void InitGame(void) } game.emulation = (emulate_bd ? EMU_BOULDERDASH : - emulate_sb ? EMU_SOKOBAN : emulate_sp ? EMU_SUPAPLEX : EMU_NONE); // initialize type of slippery elements @@ -4307,7 +4396,7 @@ void InitGame(void) { // check for player created from custom element as single target content = element_info[element].change_page[i].target_element; - is_player = ELEM_IS_PLAYER(content); + is_player = IS_PLAYER_ELEMENT(content); if (is_player && (found_rating < 3 || (found_rating == 3 && element < found_element))) @@ -4325,7 +4414,7 @@ void InitGame(void) { // check for player created from custom element as explosion content content = element_info[element].content.e[xx][yy]; - is_player = ELEM_IS_PLAYER(content); + is_player = IS_PLAYER_ELEMENT(content); if (is_player && (found_rating < 2 || (found_rating == 2 && element < found_element))) @@ -4346,7 +4435,7 @@ void InitGame(void) content = element_info[element].change_page[i].target_content.e[xx][yy]; - is_player = ELEM_IS_PLAYER(content); + is_player = IS_PLAYER_ELEMENT(content); if (is_player && (found_rating < 1 || (found_rating == 1 && element < found_element))) @@ -4370,6 +4459,11 @@ void InitGame(void) scroll_y = SCROLL_POSITION_Y(local_player->jy); } + if (game.forced_scroll_x != ARG_UNDEFINED_VALUE) + scroll_x = game.forced_scroll_x; + if (game.forced_scroll_y != ARG_UNDEFINED_VALUE) + scroll_y = game.forced_scroll_y; + // !!! FIX THIS (START) !!! if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { @@ -4460,10 +4554,7 @@ void InitGame(void) } game.restart_level = FALSE; - game.restart_game_message = NULL; - game.request_active = FALSE; - game.request_active_or_moving = FALSE; if (level.game_engine_type == GAME_ENGINE_TYPE_MM) InitGameActions_MM(); @@ -4477,6 +4568,8 @@ void InitGame(void) if (setup.sound_music) PlayLevelMusic(); } + + SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions); } void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y, @@ -4697,29 +4790,55 @@ void InitAmoebaNr(int x, int y) AmoebaCnt2[group_nr]++; } -static void LevelSolved(void) +static void LevelSolved_SetFinalGameValues(void) { - if (level.game_engine_type == GAME_ENGINE_TYPE_RND && - game.players_still_needed > 0) - return; - - game.LevelSolved = TRUE; - game.GameOver = TRUE; + game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft); + game.score_time_final = (level.use_step_counter ? TimePlayed : + TimePlayed * FRAMES_PER_SECOND + TimeFrames); game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score : game.score); + game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ? MM_HEALTH(game_mm.laser_overload_value) : game.health); - game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft); + game.LevelSolved_CountingTime = game.time_final; game.LevelSolved_CountingScore = game.score_final; game.LevelSolved_CountingHealth = game.health_final; } +static void LevelSolved_DisplayFinalGameValues(int time, int score, int health) +{ + game.LevelSolved_CountingTime = time; + game.LevelSolved_CountingScore = score; + game.LevelSolved_CountingHealth = health; + + game_panel_controls[GAME_PANEL_TIME].value = time; + game_panel_controls[GAME_PANEL_SCORE].value = score; + game_panel_controls[GAME_PANEL_HEALTH].value = health; + + DisplayGameControlValues(); +} + +static void LevelSolved(void) +{ + if (level.game_engine_type == GAME_ENGINE_TYPE_RND && + game.players_still_needed > 0) + return; + + game.LevelSolved = TRUE; + game.GameOver = TRUE; + + tape.solved = TRUE; + + // needed here to display correct panel values while player walks into exit + LevelSolved_SetFinalGameValues(); +} + void GameWon(void) { static int time_count_steps; @@ -4740,6 +4859,9 @@ void GameWon(void) if (local_player->active && local_player->MovPos) return; + // calculate final game values after player finished walking into exit + LevelSolved_SetFinalGameValues(); + game.LevelSolved_GameWon = TRUE; game.LevelSolved_SaveTape = tape.recording; game.LevelSolved_SaveScore = !tape.playing; @@ -4760,23 +4882,31 @@ void GameWon(void) game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game - time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft); + time = time_final = game.time_final; score = score_final = game.score_final; health = health_final = game.health_final; + // update game panel values before (delayed) counting of score (if any) + LevelSolved_DisplayFinalGameValues(time, score, health); + + // if level has time score defined, calculate new final game values if (time_score > 0) { + int time_final_max = 999; + int time_frames_final_max = time_final_max * FRAMES_PER_SECOND; int time_frames = 0; + int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames; + int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames; if (TimeLeft > 0) { time_final = 0; - time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames; + time_frames = time_frames_left; } - else if (game.no_time_limit && TimePlayed < 999) + else if (game.no_level_time_limit && TimePlayed < time_final_max) { - time_final = 999; - time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames; + time_final = time_final_max; + time_frames = time_frames_final_max - time_frames_played; } score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5; @@ -4793,18 +4923,13 @@ void GameWon(void) game.health_final = health_final; } + // if not counting score after game, immediately update game panel values if (level_editor_test_game || !setup.count_score_after_game) { time = time_final; score = score_final; - game.LevelSolved_CountingTime = time; - game.LevelSolved_CountingScore = score; - - game_panel_controls[GAME_PANEL_TIME].value = time; - game_panel_controls[GAME_PANEL_SCORE].value = score; - - DisplayGameControlValues(); + LevelSolved_DisplayFinalGameValues(time, score, health); } if (level.game_engine_type == GAME_ENGINE_TYPE_RND) @@ -4881,13 +5006,7 @@ void GameWon(void) if (time == time_final) score = score_final; - game.LevelSolved_CountingTime = time; - game.LevelSolved_CountingScore = score; - - game_panel_controls[GAME_PANEL_TIME].value = time; - game_panel_controls[GAME_PANEL_SCORE].value = score; - - DisplayGameControlValues(); + LevelSolved_DisplayFinalGameValues(time, score, health); if (time == time_final) StopSound(SND_GAME_LEVELTIME_BONUS); @@ -4913,13 +5032,7 @@ void GameWon(void) health += health_count_dir; score += time_score; - game.LevelSolved_CountingHealth = health; - game.LevelSolved_CountingScore = score; - - game_panel_controls[GAME_PANEL_HEALTH].value = health; - game_panel_controls[GAME_PANEL_SCORE].value = score; - - DisplayGameControlValues(); + LevelSolved_DisplayFinalGameValues(time, score, health); if (health == health_final) StopSound(SND_GAME_LEVELTIME_BONUS); @@ -4948,23 +5061,27 @@ void GameEnd(void) { // used instead of "level_nr" (needed for network games) int last_level_nr = levelset.level_nr; - int hi_pos; + boolean tape_saved = FALSE; game.LevelSolved_GameEnd = TRUE; - if (game.LevelSolved_SaveTape) + if (game.LevelSolved_SaveTape && !score_info_tape_play) { // make sure that request dialog to save tape does not open door again if (!global.use_envelope_request) CloseDoor(DOOR_CLOSE_1); - SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape + // ask to save tape + tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr); + + // set unique basename for score tape (also saved in high score table) + strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name)); } // if no tape is to be saved, close both doors simultaneously CloseDoor(DOOR_CLOSE_ALL); - if (level_editor_test_game) + if (level_editor_test_game || score_info_tape_play) { SetGameStatus(GAME_MODE_MAIN); @@ -4989,6 +5106,9 @@ void GameEnd(void) SaveLevelSetup_SeriesInfo(); } + // save score and score tape before potentially erasing tape below + NewHighScore(last_level_nr, tape_saved); + if (setup.increment_levels && level_nr < leveldir_current->last_level && !network_playing) @@ -4998,23 +5118,22 @@ void GameEnd(void) if (setup.auto_play_next_level) { + scores.continue_playing = TRUE; + scores.next_level_nr = level_nr; + LoadLevel(level_nr); SaveLevelSetup_SeriesInfo(); } } - hi_pos = NewHiScore(last_level_nr); - - if (hi_pos >= 0 && setup.show_scores_after_game) + if (scores.last_added >= 0 && setup.show_scores_after_game) { SetGameStatus(GAME_MODE_SCORES); - DrawHallOfFame(last_level_nr, hi_pos); + DrawHallOfFame(last_level_nr); } - else if (setup.auto_play_next_level && setup.increment_levels && - last_level_nr < leveldir_current->last_level && - !network_playing) + else if (scores.continue_playing) { StartGameActions(network.enabled, setup.autorecord, level.random_seed); } @@ -5026,64 +5145,171 @@ void GameEnd(void) } } -int NewHiScore(int level_nr) +static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry, + boolean one_score_entry_per_name) { - int k, l; - int position = -1; - boolean one_score_entry_per_name = !program.many_scores_per_name; - - LoadScore(level_nr); + int i; - if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) || - game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score) + if (strEqual(new_entry->name, EMPTY_PLAYER_NAME)) return -1; - for (k = 0; k < MAX_SCORE_ENTRIES; k++) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - if (game.score_final > highscore[k].Score) + struct ScoreEntry *entry = &list->entry[i]; + boolean score_is_better = (new_entry->score > entry->score); + boolean score_is_equal = (new_entry->score == entry->score); + boolean time_is_better = (new_entry->time < entry->time); + boolean time_is_equal = (new_entry->time == entry->time); + boolean better_by_score = (score_is_better || + (score_is_equal && time_is_better)); + boolean better_by_time = (time_is_better || + (time_is_equal && score_is_better)); + boolean is_better = (level.rate_time_over_score ? better_by_time : + better_by_score); + boolean entry_is_empty = (entry->score == 0 && + entry->time == 0); + + // prevent adding server score entries if also existing in local score file + // (special case: historic score entries have an empty tape basename entry) + if (strEqual(new_entry->tape_basename, entry->tape_basename) && + !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME)) + { + // add fields from server score entry not stored in local score entry + // (currently, this means setting platform, version and country fields; + // in rare cases, this may also correct an invalid score value, as + // historic scores might have been truncated to 16-bit values locally) + *entry = *new_entry; + + return -1; + } + + if (is_better || entry_is_empty) { // player has made it to the hall of fame - if (k < MAX_SCORE_ENTRIES - 1) + if (i < MAX_SCORE_ENTRIES - 1) { int m = MAX_SCORE_ENTRIES - 1; + int l; if (one_score_entry_per_name) { - for (l = k; l < MAX_SCORE_ENTRIES; l++) - if (strEqual(setup.player_name, highscore[l].Name)) + for (l = i; l < MAX_SCORE_ENTRIES; l++) + if (strEqual(list->entry[l].name, new_entry->name)) m = l; - if (m == k) // player's new highscore overwrites his old one + if (m == i) // player's new highscore overwrites his old one goto put_into_list; } - for (l = m; l > k; l--) - { - strcpy(highscore[l].Name, highscore[l - 1].Name); - highscore[l].Score = highscore[l - 1].Score; - } + for (l = m; l > i; l--) + list->entry[l] = list->entry[l - 1]; } put_into_list: - strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN); - highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0'; - highscore[k].Score = game.score_final; - position = k; + *entry = *new_entry; - break; + return i; } else if (one_score_entry_per_name && - !strncmp(setup.player_name, highscore[k].Name, - MAX_PLAYER_NAME_LEN)) - break; // player already there with a higher score + strEqual(entry->name, new_entry->name)) + { + // player already in high score list with better score or time + + return -1; + } + } + + // special case: new score is beyond the last high score list position + return MAX_SCORE_ENTRIES; +} + +void NewHighScore(int level_nr, boolean tape_saved) +{ + struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119) + boolean one_per_name = FALSE; + + strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN); + strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN); + + new_entry.score = game.score_final; + new_entry.time = game.score_time_final; + + LoadScore(level_nr); + + scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name); + + if (scores.last_added >= MAX_SCORE_ENTRIES) + { + scores.last_added = MAX_SCORE_ENTRIES - 1; + scores.force_last_added = TRUE; + + scores.entry[scores.last_added] = new_entry; + + // store last added local score entry (before merging server scores) + scores.last_added_local = scores.last_added; + + return; + } + + if (scores.last_added < 0) + return; + + SaveScore(level_nr); + + // store last added local score entry (before merging server scores) + scores.last_added_local = scores.last_added; + + if (!game.LevelSolved_SaveTape) + return; + + SaveScoreTape(level_nr); + + if (setup.ask_for_using_api_server) + { + setup.use_api_server = + Request("Upload your score and tape to the high score server?", REQ_ASK); + + if (!setup.use_api_server) + Request("Not using high score server! Use setup menu to enable again!", + REQ_CONFIRM); + + runtime.use_api_server = setup.use_api_server; + + // after asking for using API server once, do not ask again + setup.ask_for_using_api_server = FALSE; + + SaveSetup_ServerSetup(); } - if (position >= 0) - SaveScore(level_nr); + SaveServerScore(level_nr, tape_saved); +} + +void MergeServerScore(void) +{ + struct ScoreEntry last_added_entry; + boolean one_per_name = FALSE; + int i; + + if (scores.last_added >= 0) + last_added_entry = scores.entry[scores.last_added]; + + for (i = 0; i < server_scores.num_entries; i++) + { + int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name); + + if (pos >= 0 && pos <= scores.last_added) + scores.last_added++; + } + + if (scores.last_added >= MAX_SCORE_ENTRIES) + { + scores.last_added = MAX_SCORE_ENTRIES - 1; + scores.force_last_added = TRUE; - return position; + scores.entry[scores.last_added] = last_added_entry; + } } static int getElementMoveStepsizeExt(int x, int y, int direction) @@ -5135,6 +5361,8 @@ static void ResetGfxFrame(int x, int y) if (graphic_info[graphic].anim_global_sync) GfxFrame[x][y] = FrameCounter; + else if (graphic_info[graphic].anim_global_anim_sync) + GfxFrame[x][y] = getGlobalAnimSyncFrame(); else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) GfxFrame[x][y] = CustomValue[x][y]; else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) @@ -5214,17 +5442,9 @@ void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y) void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y) { - int oldx = x, oldy = y; int direction = MovDir[x][y]; - - if (direction == MV_LEFT) - oldx++; - else if (direction == MV_RIGHT) - oldx--; - else if (direction == MV_UP) - oldy++; - else if (direction == MV_DOWN) - oldy--; + int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0); + int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0); *comes_from_x = oldx; *comes_from_y = oldy; @@ -5239,16 +5459,17 @@ static int MovingOrBlocked2Element(int x, int y) int oldx, oldy; Blocked2Moving(x, y, &oldx, &oldy); + return Tile[oldx][oldy]; } - else - return element; + + return element; } static int MovingOrBlocked2ElementIfNotLeaving(int x, int y) { // like MovingOrBlocked2Element(), but if element is moving - // and (x,y) is the field the moving element is just leaving, + // and (x, y) is the field the moving element is just leaving, // return EL_BLOCKED instead of the element value int element = Tile[x][y]; @@ -5363,7 +5584,7 @@ void DrawDynamite(int x, int y) else if (game.use_masked_elements) DrawLevelElement(x, y, EL_EMPTY); - frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + frame = getGraphicAnimationFrameXY(graphic, x, y); if (Back[x][y] || Store[x][y] || game.use_masked_elements) DrawGraphicThruMask(sx, sy, graphic, frame); @@ -5441,7 +5662,7 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy) *sy = (sy1 + sy2) / 2; } -static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, +static void DrawRelocateScreen(int old_x, int old_y, int x, int y, boolean center_screen, boolean quick_relocation) { unsigned int frame_delay_value_old = GetVideoFrameDelay(); @@ -5471,14 +5692,47 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, { // relocation _without_ centering of screen - int center_scroll_x = SCROLL_POSITION_X(old_x); - int center_scroll_y = SCROLL_POSITION_Y(old_y); - int offset_x = x + (scroll_x - center_scroll_x); - int offset_y = y + (scroll_y - center_scroll_y); + // apply distance between old and new player position to scroll position + int shifted_scroll_x = scroll_x + (x - old_x); + int shifted_scroll_y = scroll_y + (y - old_y); + + // make sure that shifted scroll position does not scroll beyond screen + new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX); + new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY); + + // special case for teleporting from one end of the playfield to the other + // (this kludge prevents the destination area to be shifted by half a tile + // against the source destination for even screen width or screen height; + // probably most useful when used with high "game.forced_scroll_delay_value" + // in combination with "game.forced_scroll_x" and "game.forced_scroll_y") + if (quick_relocation) + { + if (EVEN(SCR_FIELDX)) + { + // relocate (teleport) between left and right border (half or full) + if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1) + new_scroll_x = SBX_Right; + else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right) + new_scroll_x = SBX_Right - 1; + else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1) + new_scroll_x = SBX_Left; + else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left) + new_scroll_x = SBX_Left + 1; + } - // for new screen position, apply previous offset to center position - new_scroll_x = SCROLL_POSITION_X(offset_x); - new_scroll_y = SCROLL_POSITION_Y(offset_y); + if (EVEN(SCR_FIELDY)) + { + // relocate (teleport) between top and bottom border (half or full) + if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1) + new_scroll_y = SBY_Lower; + else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower) + new_scroll_y = SBY_Lower - 1; + else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1) + new_scroll_y = SBY_Upper; + else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper) + new_scroll_y = SBY_Upper + 1; + } + } } if (quick_relocation) @@ -5606,13 +5860,13 @@ static void RelocatePlayer(int jx, int jy, int el_player_raw) possible that the relocation target field did not contain a player element, but a walkable element, to which the new player was relocated -- in this case, restore that (already initialized!) element on the player field */ - if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element + if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element { Tile[jx][jy] = element; // restore previously existing element } // only visually relocate centered player - DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir, + DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, FALSE, level.instant_relocation); TestIfPlayerTouchesBadThing(jx, jy); @@ -5645,9 +5899,6 @@ static void Explode(int ex, int ey, int phase, int mode) int last_phase; int border_element; - // !!! eliminate this variable !!! - int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2); - if (game.explosions_delayed) { ExplodeField[ex][ey] = mode; @@ -5657,6 +5908,8 @@ static void Explode(int ex, int ey, int phase, int mode) if (phase == EX_PHASE_START) // initialize 'Store[][]' field { int center_element = Tile[ex][ey]; + int ce_value = CustomValue[ex][ey]; + int ce_score = element_info[center_element].collect_score; int artwork_element, explosion_element; // set these values later // remove things displayed in background while burning dynamite @@ -5776,7 +6029,7 @@ static void Explode(int ex, int ey, int phase, int mode) // !!! check this case -- currently needed for rnd_rado_negundo_v, // !!! levels 015 018 019 020 021 022 023 026 027 028 !!! - else if (ELEM_IS_PLAYER(center_element)) + else if (IS_PLAYER_ELEMENT(center_element)) Store[x][y] = EL_EMPTY; else if (center_element == EL_YAMYAM) Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy]; @@ -5795,6 +6048,14 @@ static void Explode(int ex, int ey, int phase, int mode) else Store[x][y] = EL_EMPTY; + if (IS_CUSTOM_ELEMENT(center_element)) + Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value : + Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score : + Store[x][y] >= EL_PREV_CE_8 && + Store[x][y] <= EL_NEXT_CE_8 ? + RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) : + Store[x][y]); + if (x != ex || y != ey || mode == EX_TYPE_BORDER || center_element == EL_AMOEBA_TO_DIAMOND) Store2[x][y] = element; @@ -5868,6 +6129,10 @@ static void Explode(int ex, int ey, int phase, int mode) return; } + // this can happen if the player was just killed by an explosion + if (GfxElement[x][y] == EL_UNDEFINED) + GfxElement[x][y] = EL_EMPTY; + if (phase == last_phase) { int element; @@ -5916,15 +6181,15 @@ static void Explode(int ex, int ey, int phase, int mode) if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) StorePlayer[x][y] = 0; - if (ELEM_IS_PLAYER(element)) + if (IS_PLAYER_ELEMENT(element)) RelocatePlayer(x, y, element); } else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y))) { int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + int frame = getGraphicAnimationFrameXY(graphic, x, y); - if (phase == delay) + if (phase == 1) TEST_DrawLevelFieldCrumbled(x, y); if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY) @@ -5934,11 +6199,11 @@ static void Explode(int ex, int ey, int phase, int mode) } else if (IS_WALKABLE_UNDER(Back[x][y])) { - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); DrawLevelElementThruMask(x, y, Back[x][y]); } else if (!IS_WALKABLE_INSIDE(Back[x][y])) - DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); } } @@ -5949,13 +6214,7 @@ static void DynaExplode(int ex, int ey) int dynabomb_size = 1; boolean dynabomb_xl = FALSE; struct PlayerInfo *player; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (IS_ACTIVE_BOMB(dynabomb_element)) { @@ -5971,8 +6230,8 @@ static void DynaExplode(int ex, int ey) { for (j = 1; j <= dynabomb_size; j++) { - int x = ex + j * xy[i][0]; - int y = ey + j * xy[i][1]; + int x = ex + j * xy[i].x; + int y = ey + j * xy[i].y; int element; if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y])) @@ -6253,7 +6512,7 @@ static void ToggleBeltSwitch(int x, int y) } } -static void ToggleSwitchgateSwitch(int x, int y) +static void ToggleSwitchgateSwitch(void) { int xx, yy; @@ -6733,7 +6992,7 @@ static void Impact(int x, int y) smashed == EL_DC_SWITCHGATE_SWITCH_UP || smashed == EL_DC_SWITCHGATE_SWITCH_DOWN) { - ToggleSwitchgateSwitch(x, y + 1); + ToggleSwitchgateSwitch(); } else if (smashed == EL_LIGHT_SWITCH || smashed == EL_LIGHT_SWITCH_ACTIVE) @@ -7107,18 +7366,12 @@ static void TurnRoundExt(int x, int y) if (element == EL_PENGUIN) { int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int ex = x + xy[i][0]; - int ey = y + xy[i][1]; + int ex = x + xy[i].x; + int ey = y + xy[i].y; if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN || Tile[ex][ey] == EL_EM_EXIT_OPEN || @@ -7362,7 +7615,7 @@ static void TurnRoundExt(int x, int y) boolean can_turn_left = CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y); boolean can_turn_right = - CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y); + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y); if (element_info[element].move_stepsize == 0) // "not moving" return; @@ -7517,25 +7770,13 @@ static void TurnRoundExt(int x, int y) } else if (move_pattern & MV_MAZE_RUNNER_STYLE) { - static int test_xy[7][2] = + struct XY *test_xy = xy_topdown; + static int test_dir[4] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 }, - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - }; - static int test_dir[7] = - { - MV_UP, - MV_LEFT, - MV_RIGHT, - MV_DOWN, MV_UP, MV_LEFT, MV_RIGHT, + MV_DOWN }; boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER); int move_preference = -1000000; // start with very low preference @@ -7545,11 +7786,12 @@ static void TurnRoundExt(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int move_dir = test_dir[start_test + i]; + int j = (start_test + i) % 4; + int move_dir = test_dir[j]; int move_dir_preference; - xx = x + test_xy[start_test + i][0]; - yy = y + test_xy[start_test + i][1]; + xx = x + test_xy[j].x; + yy = y + test_xy[j].y; if (hunter_mode && IN_LEV_FIELD(xx, yy) && (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING)) @@ -8111,7 +8353,7 @@ static void StartMoving(int x, int y) dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT : dir == MV_UP ? IMG_FLAMES_1_UP : dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + int frame = getGraphicAnimationFrameXY(graphic, x, y); GfxAction[x][y] = ACTION_ATTACKING; @@ -8149,7 +8391,7 @@ static void StartMoving(int x, int y) if (IN_SCR_FIELD(sx, sy)) { TEST_DrawLevelFieldCrumbled(xx, yy); - DrawGraphic(sx, sy, flame_graphic, frame); + DrawScreenGraphic(sx, sy, flame_graphic, frame); } } else @@ -8203,7 +8445,7 @@ static void StartMoving(int x, int y) PlayLevelSound(newx, newy, SND_PENGUIN_PASSING); if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy))) - DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0); + DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0); game.friends_still_needed--; if (!game.friends_still_needed && @@ -8215,7 +8457,7 @@ static void StartMoving(int x, int y) } else if (IS_FOOD_PENGUIN(Tile[newx][newy])) { - if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING) + if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING) TEST_DrawLevelField(newx, newy); else GfxDir[x][y] = MovDir[x][y] = MV_NONE; @@ -8298,6 +8540,9 @@ static void StartMoving(int x, int y) GfxDir[x][y] = diagonal_move_dir; ChangeDelay[x][y] = change_delay; + if (Store[x][y] == EL_EMPTY) + Store[x][y] = GfxElementEmpty[x][y]; + graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]); @@ -8517,7 +8762,7 @@ void ContinueMoving(int x, int y) if (pushed_by_player) // special case: moving object pushed by player { - MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos)); + MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos)); } else if (use_step_delay) // special case: moving object has step delay { @@ -8702,7 +8947,7 @@ void ContinueMoving(int x, int y) if (GFX_CRUMBLED(Tile[x][y])) TEST_DrawLevelFieldCrumbledNeighbours(x, y); - if (ELEM_IS_PLAYER(move_leave_element)) + if (IS_PLAYER_ELEMENT(move_leave_element)) RelocatePlayer(x, y, move_leave_element); } @@ -8779,7 +9024,7 @@ void ContinueMoving(int x, int y) CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER, player->index_bit, push_side); - CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X, + CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X, player->index_bit, push_side); } @@ -8804,18 +9049,12 @@ int AmoebaNeighbourNr(int ax, int ay) int i; int element = Tile[ax][ay]; int group_nr = 0; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int x = ax + xy[i][0]; - int y = ay + xy[i][1]; + int x = ax + xy[i].x; + int y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -8831,21 +9070,15 @@ static void AmoebaMerge(int ax, int ay) { int i, x, y, xx, yy; int new_group_nr = AmoebaNr[ax][ay]; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (new_group_nr == 0) return; for (i = 0; i < NUM_DIRECTIONS; i++) { - x = ax + xy[i][0]; - y = ay + xy[i][1]; + x = ax + xy[i].x; + y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -8908,18 +9141,12 @@ void AmoebaToDiamond(int ax, int ay) } else { - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - x = ax + xy[i][0]; - y = ay + xy[i][1]; + x = ax + xy[i].x; + y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -8974,17 +9201,16 @@ static void AmoebaToDiamondBD(int ax, int ay, int new_element) static void AmoebaGrowing(int x, int y) { - static unsigned int sound_delay = 0; - static unsigned int sound_delay_value = 0; + static DelayCounter sound_delay = { 0 }; if (!MovDelay[x][y]) // start new growing cycle { MovDelay[x][y] = 7; - if (DelayReached(&sound_delay, sound_delay_value)) + if (DelayReached(&sound_delay)) { PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING); - sound_delay_value = 30; + sound_delay.value = 30; } } @@ -8996,7 +9222,7 @@ static void AmoebaGrowing(int x, int y) int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING, 6 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame); + DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame); } if (!MovDelay[x][y]) @@ -9010,15 +9236,14 @@ static void AmoebaGrowing(int x, int y) static void AmoebaShrinking(int x, int y) { - static unsigned int sound_delay = 0; - static unsigned int sound_delay_value = 0; + static DelayCounter sound_delay = { 0 }; if (!MovDelay[x][y]) // start new shrinking cycle { MovDelay[x][y] = 7; - if (DelayReached(&sound_delay, sound_delay_value)) - sound_delay_value = 30; + if (DelayReached(&sound_delay)) + sound_delay.value = 30; } if (MovDelay[x][y]) // wait some time before shrinking @@ -9029,7 +9254,7 @@ static void AmoebaShrinking(int x, int y) int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING, 6 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame); + DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame); } if (!MovDelay[x][y]) @@ -9051,13 +9276,7 @@ static void AmoebaReproduce(int ax, int ay) int graphic = el2img(element); int newax = ax, neway = ay; boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER); - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (!level.amoeba_speed && element != EL_EMC_DRIPPER) { @@ -9082,8 +9301,8 @@ static void AmoebaReproduce(int ax, int ay) if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER { int start = RND(4); - int x = ax + xy[start][0]; - int y = ay + xy[start][1]; + int x = ax + xy[start].x; + int y = ay + xy[start].y; if (!IN_LEV_FIELD(x, y)) return; @@ -9108,8 +9327,8 @@ static void AmoebaReproduce(int ax, int ay) for (i = 0; i < NUM_DIRECTIONS; i++) { int j = (start + i) % 4; - int x = ax + xy[j][0]; - int y = ay + xy[j][1]; + int x = ax + xy[j].x; + int y = ay + xy[j].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -9283,7 +9502,7 @@ static void Life(int ax, int ay) num_neighbours <= life_parameter[3]) { Tile[xx][yy] = element; - MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1); + MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1); if (Tile[xx][yy] != old_element) TEST_DrawLevelField(xx, yy); Stop[xx][yy] = TRUE; @@ -9531,7 +9750,7 @@ static void DrawTwinkleOnField(int x, int y) } } -static void MauerWaechst(int x, int y) +static void WallGrowing(int x, int y) { int delay = 6; @@ -9547,7 +9766,7 @@ static void MauerWaechst(int x, int y) int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]); int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); } if (!MovDelay[x][y]) @@ -9581,15 +9800,59 @@ static void MauerWaechst(int x, int y) } } -static void MauerAbleger(int ax, int ay) +static void CheckWallGrowing(int ax, int ay) { int element = Tile[ax][ay]; int graphic = el2img(element); - boolean oben_frei = FALSE, unten_frei = FALSE; - boolean links_frei = FALSE, rechts_frei = FALSE; - boolean oben_massiv = FALSE, unten_massiv = FALSE; - boolean links_massiv = FALSE, rechts_massiv = FALSE; - boolean new_wall = FALSE; + boolean free_top = FALSE; + boolean free_bottom = FALSE; + boolean free_left = FALSE; + boolean free_right = FALSE; + boolean stop_top = FALSE; + boolean stop_bottom = FALSE; + boolean stop_left = FALSE; + boolean stop_right = FALSE; + boolean new_wall = FALSE; + + boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL || + element == EL_EXPANDABLE_WALL_ANY || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL || + element == EL_EXPANDABLE_WALL_ANY || + element == EL_EXPANDABLE_WALL || + element == EL_BD_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL); + + boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL || + element == EL_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL); + + int wall_growing = (is_steelwall ? + EL_EXPANDABLE_STEELWALL_GROWING : + EL_EXPANDABLE_WALL_GROWING); + + int gfx_wall_growing_up = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_UP : + IMG_EXPANDABLE_WALL_GROWING_UP); + int gfx_wall_growing_down = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_DOWN : + IMG_EXPANDABLE_WALL_GROWING_DOWN); + int gfx_wall_growing_left = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_LEFT : + IMG_EXPANDABLE_WALL_GROWING_LEFT); + int gfx_wall_growing_right = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT : + IMG_EXPANDABLE_WALL_GROWING_RIGHT); if (IS_ANIMATED(graphic)) DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic); @@ -9604,188 +9867,84 @@ static void MauerAbleger(int ax, int ay) return; } - if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) - oben_frei = TRUE; - if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) - unten_frei = TRUE; - if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) - links_frei = TRUE; - if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) - rechts_frei = TRUE; + if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1)) + free_top = TRUE; + if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1)) + free_bottom = TRUE; + if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay)) + free_left = TRUE; + if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay)) + free_right = TRUE; - if (element == EL_EXPANDABLE_WALL_VERTICAL || - element == EL_EXPANDABLE_WALL_ANY) + if (grow_vertical) { - if (oben_frei) - { - Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING; - Store[ax][ay-1] = element; - GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1))) - DrawGraphic(SCREENX(ax), SCREENY(ay - 1), - IMG_EXPANDABLE_WALL_GROWING_UP, 0); - new_wall = TRUE; - } - if (unten_frei) + if (free_top) { - Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING; - Store[ax][ay+1] = element; - GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1))) - DrawGraphic(SCREENX(ax), SCREENY(ay + 1), - IMG_EXPANDABLE_WALL_GROWING_DOWN, 0); - new_wall = TRUE; - } - } + Tile[ax][ay - 1] = wall_growing; + Store[ax][ay - 1] = element; + GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP; - if (element == EL_EXPANDABLE_WALL_HORIZONTAL || - element == EL_EXPANDABLE_WALL_ANY || - element == EL_EXPANDABLE_WALL || - element == EL_BD_EXPANDABLE_WALL) - { - if (links_frei) - { - Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING; - Store[ax-1][ay] = element; - GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT; - if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay))) - DrawGraphic(SCREENX(ax - 1), SCREENY(ay), - IMG_EXPANDABLE_WALL_GROWING_LEFT, 0); - new_wall = TRUE; - } + if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1))) + DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0); - if (rechts_frei) - { - Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING; - Store[ax+1][ay] = element; - GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT; - if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay))) - DrawGraphic(SCREENX(ax + 1), SCREENY(ay), - IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0); new_wall = TRUE; } - } - - if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei)) - TEST_DrawLevelField(ax, ay); - - if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1])) - oben_massiv = TRUE; - if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1])) - unten_massiv = TRUE; - if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay])) - links_massiv = TRUE; - if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay])) - rechts_massiv = TRUE; - - if (((oben_massiv && unten_massiv) || - element == EL_EXPANDABLE_WALL_HORIZONTAL || - element == EL_EXPANDABLE_WALL) && - ((links_massiv && rechts_massiv) || - element == EL_EXPANDABLE_WALL_VERTICAL)) - Tile[ax][ay] = EL_WALL; - - if (new_wall) - PlayLevelSoundAction(ax, ay, ACTION_GROWING); -} - -static void MauerAblegerStahl(int ax, int ay) -{ - int element = Tile[ax][ay]; - int graphic = el2img(element); - boolean oben_frei = FALSE, unten_frei = FALSE; - boolean links_frei = FALSE, rechts_frei = FALSE; - boolean oben_massiv = FALSE, unten_massiv = FALSE; - boolean links_massiv = FALSE, rechts_massiv = FALSE; - boolean new_wall = FALSE; - - if (IS_ANIMATED(graphic)) - DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic); - - if (!MovDelay[ax][ay]) // start building new wall - MovDelay[ax][ay] = 6; - if (MovDelay[ax][ay]) // wait some time before building new wall - { - MovDelay[ax][ay]--; - if (MovDelay[ax][ay]) - return; - } + if (free_bottom) + { + Tile[ax][ay + 1] = wall_growing; + Store[ax][ay + 1] = element; + GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN; - if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) - oben_frei = TRUE; - if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) - unten_frei = TRUE; - if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) - links_frei = TRUE; - if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) - rechts_frei = TRUE; + if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1))) + DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0); - if (element == EL_EXPANDABLE_STEELWALL_VERTICAL || - element == EL_EXPANDABLE_STEELWALL_ANY) - { - if (oben_frei) - { - Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax][ay-1] = element; - GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1))) - DrawGraphic(SCREENX(ax), SCREENY(ay - 1), - IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0); - new_wall = TRUE; - } - if (unten_frei) - { - Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax][ay+1] = element; - GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1))) - DrawGraphic(SCREENX(ax), SCREENY(ay + 1), - IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0); new_wall = TRUE; } } - if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || - element == EL_EXPANDABLE_STEELWALL_ANY) + if (grow_horizontal) { - if (links_frei) + if (free_left) { - Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax-1][ay] = element; - GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT; - if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay))) - DrawGraphic(SCREENX(ax - 1), SCREENY(ay), - IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0); + Tile[ax - 1][ay] = wall_growing; + Store[ax - 1][ay] = element; + GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT; + + if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay))) + DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0); + new_wall = TRUE; } - if (rechts_frei) + if (free_right) { - Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax+1][ay] = element; - GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT; - if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay))) - DrawGraphic(SCREENX(ax + 1), SCREENY(ay), - IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0); + Tile[ax + 1][ay] = wall_growing; + Store[ax + 1][ay] = element; + GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT; + + if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay))) + DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0); + new_wall = TRUE; } } - if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1])) - oben_massiv = TRUE; - if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1])) - unten_massiv = TRUE; - if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay])) - links_massiv = TRUE; - if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay])) - rechts_massiv = TRUE; + if (element == EL_EXPANDABLE_WALL && (free_left || free_right)) + TEST_DrawLevelField(ax, ay); + + if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1])) + stop_top = TRUE; + if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1])) + stop_bottom = TRUE; + if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay])) + stop_left = TRUE; + if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay])) + stop_right = TRUE; - if (((oben_massiv && unten_massiv) || - element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) && - ((links_massiv && rechts_massiv) || - element == EL_EXPANDABLE_STEELWALL_VERTICAL)) - Tile[ax][ay] = EL_STEELWALL; + if (((stop_top && stop_bottom) || stop_horizontal) && + ((stop_left && stop_right) || stop_vertical)) + Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL); if (new_wall) PlayLevelSoundAction(ax, ay, ACTION_GROWING); @@ -9795,19 +9954,14 @@ static void CheckForDragon(int x, int y) { int i, j; boolean dragon_found = FALSE; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { for (j = 0; j < 4; j++) { - int xx = x + j * xy[i][0], yy = y + j * xy[i][1]; + int xx = x + j * xy[i].x; + int yy = y + j * xy[i].y; if (IN_LEV_FIELD(xx, yy) && (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON)) @@ -9826,8 +9980,9 @@ static void CheckForDragon(int x, int y) { for (j = 0; j < 3; j++) { - int xx = x + j * xy[i][0], yy = y + j * xy[i][1]; - + int xx = x + j * xy[i].x; + int yy = y + j * xy[i].y; + if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES) { Tile[xx][yy] = EL_EMPTY; @@ -9857,18 +10012,12 @@ static void InitBuggyBase(int x, int y) static void WarnBuggyBase(int x, int y) { int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy)) { @@ -10071,7 +10220,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) DisplayGameControlValues(); - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && game.time_limit) for (i = 0; i < MAX_PLAYERS; i++) KillPlayer(&stored_player[i]); } @@ -10512,7 +10661,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) int previous_move_direction = MovDir[x][y]; int last_ce_value = CustomValue[x][y]; boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y); - boolean new_element_is_player = ELEM_IS_PLAYER(new_element); + boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element); boolean add_player_onto_element = (new_element_is_player && new_element != EL_SOKOBAN_FIELD_PLAYER && IS_WALKABLE(old_element)); @@ -10543,17 +10692,26 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) if (GFX_CRUMBLED(new_element)) TEST_DrawLevelFieldCrumbledNeighbours(x, y); - } - // check if element under the player changes from accessible to unaccessible - // (needed for special case of dropping element which then changes) - // (must be checked after creating new element for walkable group elements) - if (IS_PLAYER(x, y) && !player_explosion_protected && - IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) - { - Bang(x, y); + if (old_element == EL_EXPLOSION) + { + Store[x][y] = Store2[x][y] = 0; - return; + // check if new element replaces an exploding player, requiring cleanup + if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) + StorePlayer[x][y] = 0; + } + + // check if element under the player changes from accessible to unaccessible + // (needed for special case of dropping element which then changes) + // (must be checked after creating new element for walkable group elements) + if (IS_PLAYER(x, y) && !player_explosion_protected && + IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) + { + KillPlayer(PLAYERINFO(x, y)); + + return; + } } // "ChangeCount" not set yet to allow "entered by player" change one time @@ -10613,6 +10771,8 @@ static boolean ChangeElement(int x, int y, int element, int page) change->actual_trigger_side = CH_SIDE_NONE; change->actual_trigger_ce_value = 0; change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } // do not change elements more than a specified maximum number of changes @@ -10621,6 +10781,11 @@ static boolean ChangeElement(int x, int y, int element, int page) ChangeCount[x][y]++; // count number of changes in the same frame + if (ei->has_anim_event) + HandleGlobalAnimEventByElementChange(element, page, x, y, + change->actual_trigger_x, + change->actual_trigger_y); + if (change->explode) { Bang(x, y); @@ -10688,7 +10853,7 @@ static boolean ChangeElement(int x, int y, int element, int page) (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) || (change->replace_when == CP_WHEN_REMOVABLE && is_removable) || (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) && - !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element))); + !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element))); if (!can_replace[xx][yy]) complete_replace = FALSE; @@ -10750,6 +10915,10 @@ static boolean ChangeElement(int x, int y, int element, int page) Store[x][y] = EL_EMPTY; } + // special case: element changes to player (and may be kept if walkable) + if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce) + CreateElementFromChange(x, y, EL_EMPTY); + CreateElementFromChange(x, y, target_element); PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING); @@ -10851,13 +11020,14 @@ static void HandleElementChange(int x, int y, int page) if (ChangeDelay[x][y] != 0) // continue element change { - if (change->can_change) - { - int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); - if (IS_ANIMATED(graphic)) - DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + // also needed if CE can not change, but has CE delay with CE action + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + if (change->can_change) + { if (change->change_function) change->change_function(x, y); } @@ -10944,6 +11114,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = trigger_x; + change->actual_trigger_y = trigger_y; if ((change->can_change && !change_done) || change->has_action) { @@ -11039,7 +11211,8 @@ static boolean CheckElementChangeExt(int x, int y, different to element changes that affect other elements to change on the whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */ boolean check_trigger_element = - (trigger_event == CE_TOUCHING_X || + (trigger_event == CE_NEXT_TO_X || + trigger_event == CE_TOUCHING_X || trigger_event == CE_HITTING_X || trigger_event == CE_HIT_BY_X || trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3 @@ -11057,6 +11230,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[x][y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = x; + change->actual_trigger_y = y; // special case: trigger element not at (x,y) position for some events if (check_trigger_element) @@ -11080,6 +11255,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_ce_value = CustomValue[xx][yy]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = xx; + change->actual_trigger_y = yy; } if (change->can_change && !change_done) @@ -11466,6 +11643,35 @@ static void CheckLevelSolved(void) } } +static void CheckLevelTime_StepCounter(void) +{ + int i; + + TimePlayed++; + + if (TimeLeft > 0) + { + TimeLeft--; + + if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved) + PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); + + game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; + + DisplayGameControlValues(); + + if (!TimeLeft && game.time_limit && !game.LevelSolved) + for (i = 0; i < MAX_PLAYERS; i++) + KillPlayer(&stored_player[i]); + } + else if (game.no_level_time_limit && !game.all_players_gone) + { + game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; + + DisplayGameControlValues(); + } +} + static void CheckLevelTime(void) { int i; @@ -11496,7 +11702,7 @@ static void CheckLevelTime(void) { TimeLeft--; - if (TimeLeft <= 10 && setup.time_limit) + if (TimeLeft <= 10 && game.time_limit) PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value @@ -11504,7 +11710,7 @@ static void CheckLevelTime(void) game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && game.time_limit) { if (level.game_engine_type == GAME_ENGINE_TYPE_EM) game_em.lev->killed_out_of_time = TRUE; @@ -11513,12 +11719,12 @@ static void CheckLevelTime(void) KillPlayer(&stored_player[i]); } } - else if (game.no_time_limit && !game.all_players_gone) + else if (game.no_level_time_limit && !game.all_players_gone) { game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; } - game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft); + game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft); } if (tape.recording || tape.playing) @@ -11580,6 +11786,64 @@ void AdvanceFrameAndPlayerCounters(int player_nr) } } +void AdvanceFrameCounter(void) +{ + FrameCounter++; +} + +void AdvanceGfxFrame(void) +{ + int x, y; + + SCAN_PLAYFIELD(x, y) + { + GfxFrame[x][y]++; + } +} + +static void HandleMouseAction(struct MouseActionInfo *mouse_action, + struct MouseActionInfo *mouse_action_last) +{ + if (mouse_action->button) + { + int new_button = (mouse_action->button && mouse_action_last->button == 0); + int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button); + int x = mouse_action->lx; + int y = mouse_action->ly; + int element = Tile[x][y]; + + if (new_button) + { + CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X, + ch_button); + } + + CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X, + ch_button); + + if (level.use_step_counter) + { + boolean counted_click = FALSE; + + // element clicked that can change when clicked/pressed + if (CAN_CHANGE_OR_HAS_ACTION(element) && + (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE))) + counted_click = TRUE; + + // element clicked that can trigger change when clicked/pressed + if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] || + trigger_events[element][CE_MOUSE_PRESSED_ON_X]) + counted_click = TRUE; + + if (new_button && counted_click) + CheckLevelTime_StepCounter(); + } + } +} + void StartGameActions(boolean init_network_game, boolean record_tape, int random_seed) { @@ -11588,6 +11852,9 @@ void StartGameActions(boolean init_network_game, boolean record_tape, if (record_tape) TapeStartRecording(new_random_seed); + if (setup.auto_pause_on_start && !tape.pausing) + TapeTogglePause(TAPE_TOGGLE_MANUAL); + if (init_network_game) { SendToServer_LevelFile(); @@ -11620,7 +11887,7 @@ static void GameActionsExt(void) Warn("element '%s' caused endless loop in game engine", EL_NAME(recursion_loop_element)); - RequestQuitGameExt(FALSE, level_editor_test_game, message); + RequestQuitGameExt(program.headless, level_editor_test_game, message); recursion_loop_detected = FALSE; // if game should be continued @@ -11772,7 +12039,7 @@ static void GameActionsExt(void) TapeRecordAction(tape_action); // remember if game was played (especially after tape stopped playing) - if (!tape.playing && summarized_player_action) + if (!tape.playing && summarized_player_action && !checkGameFailed()) game.GamePlayed = TRUE; #if USE_NEW_PLAYER_ASSIGNMENTS @@ -11895,25 +12162,23 @@ void GameActions(void) void GameActions_EM_Main(void) { byte effective_action[MAX_PLAYERS]; - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); int i; for (i = 0; i < MAX_PLAYERS; i++) effective_action[i] = stored_player[i].effective_action; - GameActions_EM(effective_action, warp_mode); + GameActions_EM(effective_action); } void GameActions_SP_Main(void) { byte effective_action[MAX_PLAYERS]; - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); int i; for (i = 0; i < MAX_PLAYERS; i++) effective_action[i] = stored_player[i].effective_action; - GameActions_SP(effective_action, warp_mode); + GameActions_SP(effective_action); for (i = 0; i < MAX_PLAYERS; i++) { @@ -11926,9 +12191,9 @@ void GameActions_SP_Main(void) void GameActions_MM_Main(void) { - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); + AdvanceGfxFrame(); - GameActions_MM(local_player->effective_mouse_action, warp_mode); + GameActions_MM(local_player->effective_mouse_action); } void GameActions_RND_Main(void) @@ -11992,7 +12257,7 @@ void GameActions_RND(void) game.centered_player_nr = game.centered_player_nr_next; game.set_centered_player = FALSE; - DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch); + DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch); DrawGameDoorValues(); } @@ -12091,6 +12356,9 @@ void GameActions_RND(void) TEST_DrawLevelField(x, y); TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit); + + if (IS_ENVELOPE(element)) + local_player->show_envelope = element; } } @@ -12142,26 +12410,7 @@ void GameActions_RND(void) #endif } - if (mouse_action.button) - { - int new_button = (mouse_action.button && mouse_action_last.button == 0); - int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button); - - x = mouse_action.lx; - y = mouse_action.ly; - element = Tile[x][y]; - - if (new_button) - { - CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button); - CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X, - ch_button); - } - - CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button); - CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X, - ch_button); - } + HandleMouseAction(&mouse_action, &mouse_action_last); SCAN_PLAYFIELD(x, y) { @@ -12169,6 +12418,9 @@ void GameActions_RND(void) graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); last_gfx_frame = GfxFrame[x][y]; + if (element == EL_EMPTY) + graphic = el2img(GfxElementEmpty[x][y]); + ResetGfxFrame(x, y); if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y]) @@ -12202,6 +12454,8 @@ void GameActions_RND(void) graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); } + CheckNextToConditions(x, y); + if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element))) { StartMoving(x, y); @@ -12262,17 +12516,16 @@ void GameActions_RND(void) CheckExitSP(x, y); else if (element == EL_EXPANDABLE_WALL_GROWING || element == EL_EXPANDABLE_STEELWALL_GROWING) - MauerWaechst(x, y); + WallGrowing(x, y); else if (element == EL_EXPANDABLE_WALL || element == EL_EXPANDABLE_WALL_HORIZONTAL || element == EL_EXPANDABLE_WALL_VERTICAL || element == EL_EXPANDABLE_WALL_ANY || - element == EL_BD_EXPANDABLE_WALL) - MauerAbleger(x, y); - else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_BD_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || element == EL_EXPANDABLE_STEELWALL_VERTICAL || element == EL_EXPANDABLE_STEELWALL_ANY) - MauerAblegerStahl(x, y); + CheckWallGrowing(x, y); else if (element == EL_FLAMES) CheckForDragon(x, y); else if (element == EL_EXPLOSION) @@ -12281,7 +12534,7 @@ void GameActions_RND(void) element == EL_DIAGONAL_SHRINKING || element == EL_DIAGONAL_GROWING) { - graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]); + graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]); DrawLevelGraphicAnimationIfNeeded(x, y, graphic); } @@ -12326,7 +12579,7 @@ void GameActions_RND(void) y = RND(lev_fieldy); element = Tile[x][y]; - if (!IS_PLAYER(x,y) && + if (!IS_PLAYER(x, y) && (element == EL_EMPTY || CAN_GROW_INTO(element) || element == EL_QUICKSAND_EMPTY || @@ -12334,10 +12587,10 @@ void GameActions_RND(void) element == EL_ACID_SPLASH_LEFT || element == EL_ACID_SPLASH_RIGHT)) { - if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET)) + if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET)) Tile[x][y] = EL_AMOEBA_DROP; } @@ -12702,7 +12955,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, !AllPlayersInSight(player, new_jx, new_jy)) return MP_NO_ACTION; - can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG); + can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG); if (can_move != MP_MOVING) return can_move; @@ -12975,7 +13228,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (mode == SCROLL_INIT) { - player->actual_frame_counter = FrameCounter; + player->actual_frame_counter.count = FrameCounter; player->GfxPos = move_stepsize * (player->MovPos / move_stepsize); if ((player->block_last_field || player->block_delay_adjustment > 0) && @@ -13004,7 +13257,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (player->MovPos != 0) // player has not yet reached destination return; } - else if (!FrameReached(&player->actual_frame_counter, 1)) + else if (!FrameReached(&player->actual_frame_counter)) return; if (player->MovPos != 0) @@ -13033,9 +13286,6 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) } } - player->last_jx = jx; - player->last_jy = jy; - if (Tile[jx][jy] == EL_EXIT_OPEN || Tile[jx][jy] == EL_EM_EXIT_OPEN || Tile[jx][jy] == EL_EM_EXIT_OPENING || @@ -13053,6 +13303,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) LevelSolved(); } + player->last_jx = jx; + player->last_jy = jy; + // this breaks one level: "machine", level 000 { int move_direction = player->MovDir; @@ -13072,13 +13325,18 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) CE_PLAYER_LEAVES_X, player->index_bit, leave_side); - if (IS_CUSTOM_ELEMENT(new_element)) - CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER, - player->index_bit, enter_side); + // needed because pushed element has not yet reached its destination, + // so it would trigger a change event at its previous field location + if (!player->is_pushing) + { + if (IS_CUSTOM_ELEMENT(new_element)) + CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER, + player->index_bit, enter_side); - CheckTriggeredElementChangeByPlayer(jx, jy, new_element, - CE_PLAYER_ENTERS_X, - player->index_bit, enter_side); + CheckTriggeredElementChangeByPlayer(jx, jy, new_element, + CE_PLAYER_ENTERS_X, + player->index_bit, enter_side); + } CheckTriggeredElementChangeBySide(jx, jy, player->initial_element, CE_MOVE_OF_X, move_direction); @@ -13089,8 +13347,8 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) TestIfPlayerTouchesBadThing(jx, jy); TestIfPlayerTouchesCustomElement(jx, jy); - /* needed because pushed element has not yet reached its destination, - so it would trigger a change event at its previous field location */ + // needed because pushed element has not yet reached its destination, + // so it would trigger a change event at its previous field location if (!player->is_pushing) TestIfElementTouchesCustomElement(jx, jy); // for empty space @@ -13113,34 +13371,8 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) RemovePlayer(player); } - if (!game.LevelSolved && level.use_step_counter) - { - int i; - - TimePlayed++; - - if (TimeLeft > 0) - { - TimeLeft--; - - if (TimeLeft <= 10 && setup.time_limit) - PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); - - game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; - - DisplayGameControlValues(); - - if (!TimeLeft && setup.time_limit) - for (i = 0; i < MAX_PLAYERS; i++) - KillPlayer(&stored_player[i]); - } - else if (game.no_time_limit && !game.all_players_gone) - { - game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; - - DisplayGameControlValues(); - } - } + if (level.use_step_counter) + CheckLevelTime_StepCounter(); if (tape.single_step && tape.recording && !tape.pausing && !player->programmed_action) @@ -13153,20 +13385,22 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) void ScrollScreen(struct PlayerInfo *player, int mode) { - static unsigned int screen_frame_counter = 0; + static DelayCounter screen_frame_counter = { 0 }; if (mode == SCROLL_INIT) { // set scrolling step size according to actual player's moving speed ScrollStepSize = TILEX / player->move_delay_value; - screen_frame_counter = FrameCounter; + screen_frame_counter.count = FrameCounter; + screen_frame_counter.value = 1; + ScreenMovDir = player->MovDir; ScreenMovPos = player->MovPos; ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize); return; } - else if (!FrameReached(&screen_frame_counter, 1)) + else if (!FrameReached(&screen_frame_counter)) return; if (ScreenMovPos) @@ -13179,15 +13413,73 @@ void ScrollScreen(struct PlayerInfo *player, int mode) ScreenMovDir = MV_NONE; } -void TestIfPlayerTouchesCustomElement(int x, int y) +void CheckNextToConditions(int x, int y) { - static int xy[4][2] = + int element = Tile[x][y]; + + if (IS_PLAYER(x, y)) + TestIfPlayerNextToCustomElement(x, y); + + if (CAN_CHANGE_OR_HAS_ACTION(element) && + HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X)) + TestIfElementNextToCustomElement(x, y); +} + +void TestIfPlayerNextToCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; + static int trigger_sides[4][2] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } + // center side border side + { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top + { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left + { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right + { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom }; + int i; + + if (!IS_PLAYER(x, y)) + return; + + struct PlayerInfo *player = PLAYERINFO(x, y); + + if (player->is_moving) + return; + + for (i = 0; i < NUM_DIRECTIONS; i++) + { + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int border_side = trigger_sides[i][1]; + int border_element; + + if (!IN_LEV_FIELD(xx, yy)) + continue; + + if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy)) + continue; // center and border element not connected + + border_element = Tile[xx][yy]; + + CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER, + player->index_bit, border_side); + CheckTriggeredElementChangeByPlayer(xx, yy, border_element, + CE_PLAYER_NEXT_TO_X, + player->index_bit, border_side); + + /* use player element that is initially defined in the level playfield, + not the player element that corresponds to the runtime player number + (example: a level that contains EL_PLAYER_3 as the only player would + incorrectly give EL_PLAYER_1 for "player->element_nr") */ + + CheckElementChangeBySide(xx, yy, border_element, player->initial_element, + CE_NEXT_TO_X, border_side); + } +} + +void TestIfPlayerTouchesCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; static int trigger_sides[4][2] = { // center side border side @@ -13208,8 +13500,8 @@ void TestIfPlayerTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int center_side = trigger_sides[i][0]; int border_side = trigger_sides[i][1]; int border_element; @@ -13243,8 +13535,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(x, y)->initial_element; + // as element "X" is the player here, check opposite (center) side CheckElementChangeBySide(xx, yy, border_element, player_element, - CE_TOUCHING_X, border_side); + CE_TOUCHING_X, center_side); } } else if (IS_PLAYER(xx, yy)) // player found at border element @@ -13270,8 +13563,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(xx, yy)->initial_element; + // as element "X" is the player here, check opposite (border) side CheckElementChangeBySide(x, y, center_element, player_element, - CE_TOUCHING_X, center_side); + CE_TOUCHING_X, border_side); } break; @@ -13279,15 +13573,48 @@ void TestIfPlayerTouchesCustomElement(int x, int y) } } -void TestIfElementTouchesCustomElement(int x, int y) +void TestIfElementNextToCustomElement(int x, int y) { - static int xy[4][2] = + struct XY *xy = xy_topdown; + static int trigger_sides[4][2] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } + // center side border side + { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top + { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left + { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right + { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom }; + int center_element = Tile[x][y]; // should always be non-moving! + int i; + + if (IS_MOVING(x, y) || IS_BLOCKED(x, y)) + return; + + for (i = 0; i < NUM_DIRECTIONS; i++) + { + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int border_side = trigger_sides[i][1]; + int border_element; + + if (!IN_LEV_FIELD(xx, yy)) + continue; + + if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy)) + continue; // center and border element not connected + + border_element = Tile[xx][yy]; + + // check for change of center element (but change it only once) + if (CheckElementChangeBySide(x, y, center_element, border_element, + CE_NEXT_TO_X, border_side)) + break; + } +} + +void TestIfElementTouchesCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; static int trigger_sides[4][2] = { // center side border side @@ -13310,8 +13637,8 @@ void TestIfElementTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int border_element; border_element_old[i] = -1; @@ -13333,8 +13660,8 @@ void TestIfElementTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int center_side = trigger_sides[i][0]; int border_element = border_element_old[i]; @@ -13345,13 +13672,13 @@ void TestIfElementTouchesCustomElement(int x, int y) CheckElementChangeBySide(xx, yy, border_element, center_element, CE_TOUCHING_X, center_side); - // (center element cannot be player, so we dont have to check this here) + // (center element cannot be player, so we don't have to check this here) } for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int border_side = trigger_sides[i][1]; int border_element = border_element_old[i]; @@ -13372,6 +13699,7 @@ void TestIfElementTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(xx, yy)->initial_element; + // as element "X" is the player here, check opposite (border) side CheckElementChangeBySide(x, y, center_element, player_element, CE_TOUCHING_X, border_side); } @@ -13438,13 +13766,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) int i, kill_x = -1, kill_y = -1; int bad_element = -1; - static int test_xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *test_xy = xy_topdown; static int test_dir[4] = { MV_UP, @@ -13457,8 +13779,8 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) { int test_x, test_y, test_move_dir, test_element; - test_x = good_x + test_xy[i][0]; - test_y = good_y + test_xy[i][1]; + test_x = good_x + test_xy[i].x; + test_y = good_y + test_xy[i].y; if (!IN_LEV_FIELD(test_x, test_y)) continue; @@ -13503,13 +13825,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) { int i, kill_x = -1, kill_y = -1; int bad_element = Tile[bad_x][bad_y]; - static int test_xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *test_xy = xy_topdown; static int touch_dir[4] = { MV_LEFT | MV_RIGHT, @@ -13532,8 +13848,8 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) { int test_x, test_y, test_move_dir, test_element; - test_x = bad_x + test_xy[i][0]; - test_y = bad_y + test_xy[i][1]; + test_x = bad_x + test_xy[i].x; + test_y = bad_y + test_xy[i].y; if (!IN_LEV_FIELD(test_x, test_y)) continue; @@ -13685,20 +14001,14 @@ void TestIfBadThingTouchesFriend(int x, int y) void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y) { int i, kill_x = bad_x, kill_y = bad_y; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { int x, y, element; - x = bad_x + xy[i][0]; - y = bad_y + xy[i][1]; + x = bad_x + xy[i].x; + y = bad_y + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -13747,7 +14057,7 @@ void KillPlayer(struct PlayerInfo *player) player->killed = TRUE; // remove accessible field at the player's position - Tile[jx][jy] = EL_EMPTY; + RemoveField(jx, jy); // deactivate shield (else Bang()/Explode() would not work right) player->shield_normal_time_left = 0; @@ -13793,7 +14103,6 @@ void BuryPlayer(struct PlayerInfo *player) return; PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING); - PlayLevelSound(jx, jy, SND_GAME_LOSING); RemovePlayer(player); @@ -13874,7 +14183,11 @@ static void TestFieldAfterSnapping(int x, int y, int element, int direction, if (level.finish_dig_collect) { int dig_side = MV_DIR_OPPOSITE(direction); + int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X : + CE_PLAYER_COLLECTS_X); + CheckTriggeredElementChangeByPlayer(x, y, element, change_event, + player_index_bit, dig_side); CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X, player_index_bit, dig_side); } @@ -13957,7 +14270,6 @@ static int DigField(struct PlayerInfo *player, return MP_NO_ACTION; } } - if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0)) old_element = Back[jx][jy]; @@ -13969,7 +14281,7 @@ static int DigField(struct PlayerInfo *player, if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction)) return MP_NO_ACTION; // field has no opening in this direction - if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction)) + if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction)) return MP_NO_ACTION; // field has no opening in this direction if (player_can_move && element == EL_ACID && move_direction == MV_DOWN) @@ -14207,9 +14519,13 @@ static int DigField(struct PlayerInfo *player, } else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY) { - player->shield_normal_time_left += level.shield_normal_time; + int shield_time = (element == EL_SHIELD_DEADLY ? + level.shield_deadly_time : + level.shield_normal_time); + + player->shield_normal_time_left += shield_time; if (element == EL_SHIELD_DEADLY) - player->shield_deadly_time_left += level.shield_deadly_time; + player->shield_deadly_time_left += shield_time; } else if (element == EL_DYNAMITE || element == EL_EM_DYNAMITE || @@ -14248,7 +14564,10 @@ static int DigField(struct PlayerInfo *player, } else if (IS_ENVELOPE(element)) { - player->show_envelope = element; + boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field); + + if (!wait_for_snapping) + player->show_envelope = element; } else if (element == EL_EMC_LENSES) { @@ -14435,13 +14754,13 @@ static int DigField(struct PlayerInfo *player, if (sokoban_task_solved && game.sokoban_fields_still_needed == 0 && game.sokoban_objects_still_needed == 0 && - (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban)) + level.auto_exit_sokoban) { game.players_still_needed = 0; LevelSolved(); - PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING); + PlaySound(SND_GAME_SOKOBAN_SOLVING); } } else @@ -14526,7 +14845,7 @@ static int DigField(struct PlayerInfo *player, element == EL_DC_SWITCHGATE_SWITCH_UP || element == EL_DC_SWITCHGATE_SWITCH_DOWN) { - ToggleSwitchgateSwitch(x, y); + ToggleSwitchgateSwitch(); } else if (element == EL_LIGHT_SWITCH || element == EL_LIGHT_SWITCH_ACTIVE) @@ -14567,7 +14886,7 @@ static int DigField(struct PlayerInfo *player, if (level.time > 0 || level.use_time_orb_bug) { TimeLeft += level.time_orb_time; - game.no_time_limit = FALSE; + game.no_level_time_limit = FALSE; game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; @@ -15028,10 +15347,12 @@ static void StopLevelSoundActionIfLoop(int x, int y, int action) static int getLevelMusicNr(void) { + int level_pos = level_nr - leveldir_current->first_level; + if (levelset.music[level_nr] != MUS_UNDEFINED) return levelset.music[level_nr]; // from config file else - return MAP_NOCONF_MUSIC(level_nr); // from music dir + return MAP_NOCONF_MUSIC(level_pos); // from music dir } static void FadeLevelSounds(void) @@ -15377,9 +15698,10 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) { // prevent short reactivation of overlay buttons while closing door SetOverlayActive(FALSE); + UnmapGameButtons(); // door may still be open due to skipped or envelope style request - CloseDoor(DOOR_CLOSE_1); + CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1); } if (network.enabled) @@ -15412,27 +15734,49 @@ void RequestQuitGame(boolean escape_key_pressed) boolean quick_quit = ((escape_key_pressed && !ask_on_escape) || level_editor_test_game); boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game || - quick_quit); + quick_quit || score_info_tape_play); RequestQuitGameExt(skip_request, quick_quit, "Do you really want to quit the game?"); } -void RequestRestartGame(char *message) +static char *getRestartGameMessage(void) { - game.restart_game_message = NULL; + boolean play_again = hasStartedNetworkGame(); + static char message[MAX_OUTPUT_LINESIZE]; + char *game_over_text = "Game over!"; + char *play_again_text = " Play it again?"; + if (level.game_engine_type == GAME_ENGINE_TYPE_MM && + game_mm.game_over_message != NULL) + game_over_text = game_mm.game_over_message; + + snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text, + (play_again ? play_again_text : "")); + + return message; +} + +static void RequestRestartGame(void) +{ + char *message = getRestartGameMessage(); boolean has_started_game = hasStartedNetworkGame(); int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM); + int door_state = DOOR_CLOSE_1; - if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game) + if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game) { + CloseDoor(door_state); + StartGameActions(network.enabled, setup.autorecord, level.random_seed); } else { - // needed in case of envelope request to close game panel - CloseDoor(DOOR_CLOSE_1); + // if game was invoked from level editor, also close tape recorder door + if (level_editor_test_game) + door_state = DOOR_CLOSE_ALL; + + CloseDoor(door_state); SetGameStatus(GAME_MODE_MAIN); @@ -15440,42 +15784,50 @@ void RequestRestartGame(char *message) } } -void CheckGameOver(void) +boolean CheckRestartGame(void) { - static boolean last_game_over = FALSE; static int game_over_delay = 0; int game_over_delay_value = 50; boolean game_over = checkGameFailed(); - // do not handle game over if request dialog is already active - if (game.request_active) - return; - - // do not ask to play again if game was never actually played - if (!game.GamePlayed) - return; - if (!game_over) { - last_game_over = FALSE; game_over_delay = game_over_delay_value; - return; + return FALSE; } if (game_over_delay > 0) { + if (game_over_delay == game_over_delay_value / 2) + PlaySound(SND_GAME_LOSING); + game_over_delay--; - return; + return FALSE; } - if (last_game_over != game_over) - game.restart_game_message = (hasStartedNetworkGame() ? - "Game over! Play it again?" : - "Game over!"); + // do not ask to play again if request dialog is already active + if (game.request_active) + return FALSE; + + // do not ask to play again if request dialog already handled + if (game.RestartGameRequested) + return FALSE; + + // do not ask to play again if game was never actually played + if (!game.GamePlayed) + return FALSE; + + // do not ask to play again if this was disabled in setup menu + if (!setup.ask_on_game_over) + return FALSE; - last_game_over = game_over; + game.RestartGameRequested = TRUE; + + RequestRestartGame(); + + return TRUE; } boolean checkGameSolved(void) @@ -15659,7 +16011,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) if (level.game_engine_type == GAME_ENGINE_TYPE_SP) SaveEngineSnapshotValues_SP(&buffers); if (level.game_engine_type == GAME_ENGINE_TYPE_MM) - SaveEngineSnapshotValues_MM(&buffers); + SaveEngineSnapshotValues_MM(); // save values stored in special snapshot structure @@ -15724,6 +16076,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir)); @@ -15896,6 +16249,11 @@ static struct GAME_CTRL_ID_LOAD, NULL, TRUE, FALSE, "load game" }, + { + IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart, + GAME_CTRL_ID_RESTART, NULL, + TRUE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop, GAME_CTRL_ID_PANEL_STOP, NULL, @@ -15911,6 +16269,11 @@ static struct GAME_CTRL_ID_PANEL_PLAY, NULL, FALSE, FALSE, "play game" }, + { + IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart, + GAME_CTRL_ID_PANEL_RESTART, NULL, + FALSE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop, GAME_CTRL_ID_TOUCH_STOP, NULL, @@ -15921,6 +16284,11 @@ static struct GAME_CTRL_ID_TOUCH_PAUSE, NULL, FALSE, TRUE, "pause game" }, + { + IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart, + GAME_CTRL_ID_TOUCH_RESTART, NULL, + FALSE, TRUE, "restart game" + }, { IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music, SOUND_CTRL_ID_MUSIC, &setup.sound_music, @@ -15983,6 +16351,10 @@ void CreateGameButtons(void) int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y)); int id = i; + // do not use touch buttons if overlay touch buttons are disabled + if (is_touch_button && !setup.touch.overlay_buttons) + continue; + if (gfx->bitmap == NULL) { game_gadget[id] = NULL; @@ -15996,7 +16368,10 @@ void CreateGameButtons(void) id == GAME_CTRL_ID_PLAY || id == GAME_CTRL_ID_PANEL_PLAY || id == GAME_CTRL_ID_SAVE || - id == GAME_CTRL_ID_LOAD) + id == GAME_CTRL_ID_LOAD || + id == GAME_CTRL_ID_RESTART || + id == GAME_CTRL_ID_PANEL_RESTART || + id == GAME_CTRL_ID_TOUCH_RESTART) { button_type = GD_TYPE_NORMAL_BUTTON; checked = FALSE; @@ -16065,12 +16440,18 @@ static void UnmapGameButtonsAtSamePosition(int id) static void UnmapGameButtonsAtSamePosition_All(void) { - if (setup.show_snapshot_buttons) + if (setup.show_load_save_buttons) { UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE); UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2); UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD); } + else if (setup.show_undo_redo_buttons) + { + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO); + } else { UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP); @@ -16083,17 +16464,13 @@ static void UnmapGameButtonsAtSamePosition_All(void) } } -static void MapGameButtonsAtSamePosition(int id) +void MapLoadSaveButtons(void) { - int i; - - for (i = 0; i < NUM_GAME_BUTTONS; i++) - if (i != id && - gamebutton_info[i].pos->x == gamebutton_info[id].pos->x && - gamebutton_info[i].pos->y == gamebutton_info[id].pos->y) - MapGadget(game_gadget[i]); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE); - UnmapGameButtonsAtSamePosition_All(); + MapGadget(game_gadget[GAME_CTRL_ID_LOAD]); + MapGadget(game_gadget[GAME_CTRL_ID_SAVE]); } void MapUndoRedoButtons(void) @@ -16105,15 +16482,6 @@ void MapUndoRedoButtons(void) MapGadget(game_gadget[GAME_CTRL_ID_REDO]); } -void UnmapUndoRedoButtons(void) -{ - UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]); - UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]); - - MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO); - MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO); -} - void ModifyPauseButtons(void) { static int ids[] = @@ -16126,6 +16494,10 @@ void ModifyPauseButtons(void) }; int i; + // do not redraw pause button on closed door (may happen when restarting game) + if (!(GetDoorState() & DOOR_OPEN_1)) + return; + for (i = 0; ids[i] > -1; i++) ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END); } @@ -16135,10 +16507,15 @@ static void MapGameButtonsExt(boolean on_tape) int i; for (i = 0; i < NUM_GAME_BUTTONS; i++) - if ((!on_tape || gamebutton_info[i].allowed_on_tape) && - i != GAME_CTRL_ID_UNDO && - i != GAME_CTRL_ID_REDO) + { + if ((i == GAME_CTRL_ID_UNDO || + i == GAME_CTRL_ID_REDO) && + game_status != GAME_MODE_PLAYING) + continue; + + if (!on_tape || gamebutton_info[i].allowed_on_tape) MapGadget(game_gadget[i]); + } UnmapGameButtonsAtSamePosition_All(); @@ -16229,6 +16606,8 @@ static void GameUndoRedoExt(void) DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter); DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0); + ModifyPauseButtons(); + BackToFront(); } @@ -16237,8 +16616,12 @@ static void GameUndo(int steps) if (!CheckEngineSnapshotList()) return; + int tape_property_bits = tape.property_bits; + LoadEngineSnapshot_Undo(steps); + tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT; + GameUndoRedoExt(); } @@ -16247,8 +16630,12 @@ static void GameRedo(int steps) if (!CheckEngineSnapshotList()) return; + int tape_property_bits = tape.property_bits; + LoadEngineSnapshot_Redo(steps); + tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT; + GameUndoRedoExt(); } @@ -16268,13 +16655,7 @@ static void HandleGameButtonsExt(int id, int button) case GAME_CTRL_ID_STOP: case GAME_CTRL_ID_PANEL_STOP: case GAME_CTRL_ID_TOUCH_STOP: - if (game_status == GAME_MODE_MAIN) - break; - - if (tape.playing) - TapeStop(); - else - RequestQuitGame(FALSE); + TapeStopGame(); break; @@ -16338,6 +16719,13 @@ static void HandleGameButtonsExt(int id, int button) TapeQuickLoad(); break; + case GAME_CTRL_ID_RESTART: + case GAME_CTRL_ID_PANEL_RESTART: + case GAME_CTRL_ID_TOUCH_RESTART: + TapeRestartGame(); + + break; + case SOUND_CTRL_ID_MUSIC: case SOUND_CTRL_ID_PANEL_MUSIC: if (setup.sound_music) diff --git a/src/game.h b/src/game.h index 5739ecf0..3752ba00 100644 --- a/src/game.h +++ b/src/game.h @@ -36,6 +36,11 @@ #define STR_SNAPSHOT_MODE_EVERY_COLLECT "every_collect" #define STR_SNAPSHOT_MODE_DEFAULT STR_SNAPSHOT_MODE_OFF +#define STR_SCORES_TYPE_LOCAL_ONLY "local_scores_only" +#define STR_SCORES_TYPE_SERVER_ONLY "server_scores_only" +#define STR_SCORES_TYPE_LOCAL_AND_SERVER "local_and_server_scores" +#define STR_SCORES_TYPE_DEFAULT STR_SCORES_TYPE_LOCAL_AND_SERVER + #define SNAPSHOT_MODE_OFF 0 #define SNAPSHOT_MODE_EVERY_STEP 1 #define SNAPSHOT_MODE_EVERY_MOVE 2 @@ -118,6 +123,8 @@ struct GameButtonInfo struct XY pause2; struct XY load; + struct XY restart; + struct XY sound_music; struct XY sound_loops; struct XY sound_simple; @@ -126,12 +133,15 @@ struct GameButtonInfo struct XY panel_pause; struct XY panel_play; + struct XY panel_restart; + struct XY panel_sound_music; struct XY panel_sound_loops; struct XY panel_sound_simple; struct XY touch_stop; struct XY touch_pause; + struct XY touch_restart; }; struct GameSnapshotInfo @@ -157,6 +167,9 @@ struct GameInfo boolean use_native_sp_graphics_engine; boolean use_masked_pushing; boolean use_masked_elements; + boolean use_masked_elements_initial; + int forced_scroll_x; + int forced_scroll_y; int forced_scroll_delay_value; int scroll_delay_value; int tile_size; @@ -197,7 +210,11 @@ struct GameInfo boolean explosions_delayed; boolean envelope_active; - boolean no_time_limit; // (variable only in very special case) + boolean no_level_time_limit; // (variable only in very special case) + boolean time_limit; // forced by levelset config or setup option + + int time_final; // time (in seconds) or steps left or played + int score_time_final; // time (in frames) or steps played int score; int score_final; @@ -232,12 +249,8 @@ struct GameInfo // values for special game initialization control boolean restart_level; - // trigger message to ask for restarting the game - char *restart_game_message; - // values for special request dialog control boolean request_active; - boolean request_active_or_moving; // values for special game control int centered_player_nr; @@ -267,6 +280,8 @@ struct GameInfo int LevelSolved_CountingTime; int LevelSolved_CountingScore; int LevelSolved_CountingHealth; + + boolean RestartGameRequested; }; struct PlayerInfo @@ -369,7 +384,7 @@ struct PlayerInfo int push_delay; int push_delay_value; - unsigned int actual_frame_counter; + DelayCounter actual_frame_counter; int drop_delay; int drop_pressed_delay; @@ -414,9 +429,13 @@ void UpdateEngineValues(int, int, int, int); void GameWon(void); void GameEnd(void); +void MergeServerScore(void); + void InitPlayerGfxAnimation(struct PlayerInfo *, int, int); + void Moving2Blocked(int, int, int *, int *); void Blocked2Moving(int, int, int *, int *); + void DrawDynamite(int, int); void StartGameActions(boolean, boolean, int); @@ -443,9 +462,8 @@ void RaiseScoreElement(int); void RequestQuitGameExt(boolean, boolean, char *); void RequestQuitGame(boolean); -void RequestRestartGame(char *); -void CheckGameOver(void); +boolean CheckRestartGame(void); boolean checkGameSolved(void); boolean checkGameFailed(void); boolean checkGameEnded(void); @@ -465,8 +483,8 @@ boolean CheckEngineSnapshotList(void); void CreateGameButtons(void); void FreeGameButtons(void); +void MapLoadSaveButtons(void); void MapUndoRedoButtons(void); -void UnmapUndoRedoButtons(void); void ModifyPauseButtons(void); void MapGameButtons(void); void UnmapGameButtons(void); diff --git a/src/game_em/cave.c b/src/game_em/cave.c index 40b2dc6f..25d7785a 100644 --- a/src/game_em/cave.c +++ b/src/game_em/cave.c @@ -126,7 +126,7 @@ boolean LoadNativeLevel_EM(char *filename, boolean level_info_only) return FALSE; } - file_version = cleanup_em_level(raw_leveldata, raw_leveldata_length,filename); + file_version = cleanup_em_level(raw_leveldata, raw_leveldata_length, filename); if (file_version == FILE_VERSION_EM_UNKNOWN) { diff --git a/src/game_em/convert.c b/src/game_em/convert.c index 4c34fd16..f8059b53 100644 --- a/src/game_em/convert.c +++ b/src/game_em/convert.c @@ -472,6 +472,10 @@ void prepare_em_level(void) // - game_em.use_old_push_elements (default: FALSE) // - game_em.use_old_push_into_acid (default: FALSE) // - game_em.use_wrap_around (default: TRUE) + // - game_em.use_push_delay (default: TRUE) + + if (native_em_level.file_version > FILE_VERSION_EM_V5) + game_em.use_push_delay = FALSE; game_em.level_solved = FALSE; game_em.game_over = FALSE; diff --git a/src/game_em/export.h b/src/game_em/export.h index c5373fdb..a2bbdef9 100644 --- a/src/game_em/export.h +++ b/src/game_em/export.h @@ -51,6 +51,7 @@ struct GameInfo_EM boolean use_old_push_elements; boolean use_old_push_into_acid; boolean use_wrap_around; + boolean use_push_delay; }; struct LevelInfo_EM @@ -108,7 +109,7 @@ void em_close_all(void); void InitGfxBuffers_EM(void); void InitGameEngine_EM(void); -void GameActions_EM(byte *, boolean); +void GameActions_EM(byte[MAX_PLAYERS]); unsigned int InitEngineRandom_EM(int); diff --git a/src/game_em/game.c b/src/game_em/game.c index 8443ff05..a6a06800 100644 --- a/src/game_em/game.c +++ b/src/game_em/game.c @@ -82,7 +82,7 @@ void InitGameEngine_EM(void) RedrawPlayfield_EM(FALSE); } -void GameActions_EM(byte action[MAX_PLAYERS], boolean warp_mode) +void GameActions_EM(byte action[MAX_PLAYERS]) { int i; boolean any_player_dropping = FALSE; @@ -109,7 +109,7 @@ void GameActions_EM(byte action[MAX_PLAYERS], boolean warp_mode) any_player_dropping = TRUE; boolean single_step_mode_paused = - CheckSingleStepMode_EM(action, frame, game_em.any_player_moving, + CheckSingleStepMode_EM(frame, game_em.any_player_moving, game_em.any_player_snapping, any_player_dropping); // draw wrapping around before going to single step pause mode diff --git a/src/game_em/graphics.c b/src/game_em/graphics.c index 398ae6e0..a3e941a5 100644 --- a/src/game_em/graphics.c +++ b/src/game_em/graphics.c @@ -49,6 +49,14 @@ static int crumbled_state[MAX_PLAYFIELD_WIDTH + 2][MAX_PLAYFIELD_HEIGHT + 2]; struct GraphicInfo_EM graphic_info_em_object[GAME_TILE_MAX][8]; struct GraphicInfo_EM graphic_info_em_player[MAX_PLAYERS][PLY_MAX][8]; +static struct XY xy_topdown[] = +{ + { 0, -1 }, + { -1, 0 }, + { +1, 0 }, + { 0, +1 } +}; + static void setScreenCenteredToAllPlayers(int *, int *); int getFieldbufferOffsetX_EM(void) @@ -301,13 +309,7 @@ static void animscreen(void) int x, y, i; int left = screen_x / TILEX; int top = screen_y / TILEY; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (!game.use_native_emc_graphics_engine) for (y = lev.top; y < lev.bottom; y++) @@ -333,8 +335,8 @@ static void animscreen(void) { for (i = 0; i < 4; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int tile_next; if (xx < 0 || xx >= CAVE_BUFFER_WIDTH || diff --git a/src/game_em/logic.c b/src/game_em/logic.c index 1eaefcfc..b73edab2 100644 --- a/src/game_em/logic.c +++ b/src/game_em/logic.c @@ -293,7 +293,7 @@ static boolean player_killed(struct PLAYER *ply) if (!ply->alive) return FALSE; - if (lev.killed_out_of_time && setup.time_limit) + if (lev.killed_out_of_time && game.time_limit) return TRUE; switch (cave[x][y-1]) @@ -777,6 +777,9 @@ static boolean player_digfield(struct PLAYER *ply, int dx, int dy) if (dy) break; + if (game_em.use_push_delay && RANDOM(32) < 16) + goto stone_push_anim; + switch (cave[x+dx][y]) { case Xblank: @@ -817,6 +820,8 @@ static boolean player_digfield(struct PLAYER *ply, int dx, int dy) break; } + stone_push_anim: + ply->anim = PLY_push_n + anim; break; @@ -824,6 +829,9 @@ static boolean player_digfield(struct PLAYER *ply, int dx, int dy) if (dy) break; + if (game_em.use_push_delay && RANDOM(32) < 22) + goto bomb_push_anim; + switch (cave[x+dx][y]) { case Xblank: @@ -864,6 +872,8 @@ static boolean player_digfield(struct PLAYER *ply, int dx, int dy) break; } + bomb_push_anim: + ply->anim = PLY_push_n + anim; break; @@ -871,6 +881,9 @@ static boolean player_digfield(struct PLAYER *ply, int dx, int dy) if (dy) break; + if (game_em.use_push_delay && RANDOM(32) < 19) + goto nut_push_anim; + switch (cave[x+dx][y]) { case Xblank: @@ -911,6 +924,8 @@ static boolean player_digfield(struct PLAYER *ply, int dx, int dy) break; } + nut_push_anim: + ply->anim = PLY_push_n + anim; break; @@ -1469,6 +1484,8 @@ static void check_player(struct PLAYER *ply) ply->dynamite_cnt = 0; /* reset dynamite timer if we move */ + seed = game_em.random; + if (!ply->joy_snap) /* player wants to move */ { boolean moved = FALSE; @@ -1500,6 +1517,8 @@ static void check_player(struct PLAYER *ply) { game_em.any_player_snapping = player_digfield(ply, dx, dy); } + + game_em.random = seed; } static void set_nearest_player_xy(int x, int y, int *dx, int *dy) diff --git a/src/game_em/reademc.c b/src/game_em/reademc.c index dfa293a6..48eb9121 100644 --- a/src/game_em/reademc.c +++ b/src/game_em/reademc.c @@ -801,7 +801,7 @@ if(len >= 2110 && (buf[2106] == 255 && buf[2107] == 53 && buf[2108] == 48 && buf if(len >= 2106 && (buf[1983] == 116 || buf[2047] == 116)) // v4 if(len >= 2106 && (buf[1983] == 27 || buf[2047] == 219)) // v3 -buf[0]=241;buf[1]=248;for(i=0,j=101;i<2106;i++,j+=7)buf[i]=(buf[i]^j)-17; // decrypt +buf[0] = 241; buf[1] = 248; for(i = 0, j = 101; i < 2106; i++, j += 7) buf[i] = (buf[i] ^ j) - 17; // decrypt number of movements (calls to logic) = time * 50 / 8 diff --git a/src/game_mm/export.h b/src/game_mm/export.h index 4a63b249..1d063969 100644 --- a/src/game_mm/export.h +++ b/src/game_mm/export.h @@ -27,12 +27,14 @@ #define MM_LEVEL_SCORE_ELEMENTS 16 +#define MM_MAX_BALL_CONTENTS 16 + #define MM_MAX_LEVEL_NAME_LEN 32 #define MM_MAX_LEVEL_AUTHOR_LEN 32 #define EL_MM_START_1_NATIVE 0 -#define EL_MM_END_1_NATIVE 155 +#define EL_MM_END_1_NATIVE 159 #define EL_MM_CHAR_START_NATIVE 160 #define EL_MM_CHAR_END_NATIVE 239 @@ -40,12 +42,12 @@ #define EL_MM_START_2_NATIVE 240 #define EL_MM_END_2_NATIVE 430 +#define EL_MM_START_3_NATIVE 431 +#define EL_MM_END_3_NATIVE 450 + #define EL_MM_RUNTIME_START_NATIVE 500 #define EL_MM_RUNTIME_END_NATIVE 504 -#define EL_MM_DUMMY_START_NATIVE 700 -#define EL_MM_DUMMY_END_NATIVE 709 - // elements to be specially mapped #define EL_MM_EMPTY_NATIVE 0 #define EL_DF_EMPTY_NATIVE 304 @@ -115,6 +117,9 @@ struct LaserInfo int fuse_x, fuse_y; int dest_element; + int dest_element_last; + int dest_element_last_x; + int dest_element_last_y; boolean stops_inside_element; boolean redraw; @@ -138,10 +143,14 @@ struct GameInfo_MM int kettles_still_needed; int lights_still_needed; int num_keys; + int ball_choice_pos; // current content element choice position + boolean laser_red, laser_green, laser_blue; + boolean has_mcduffin; boolean level_solved; boolean game_over; int game_over_cause; + char *game_over_message; boolean cheat_no_overload; boolean cheat_no_explosion; @@ -161,7 +170,8 @@ struct LevelInfo_MM int time; int kettles_needed; boolean auto_count_kettles; - boolean laser_red, laser_green, laser_blue; + boolean mm_laser_red, mm_laser_green, mm_laser_blue; + boolean df_laser_red, df_laser_green, df_laser_blue; char name[MM_MAX_LEVEL_NAME_LEN + 1]; char author[MM_MAX_LEVEL_AUTHOR_LEN + 1]; int score[MM_LEVEL_SCORE_ELEMENTS]; @@ -171,6 +181,12 @@ struct LevelInfo_MM int time_ball; int time_block; + int num_ball_contents; + int ball_choice_mode; + int ball_content[MM_MAX_BALL_CONTENTS]; + boolean rotate_ball_content; + boolean explode_ball; + short field[MAX_PLAYFIELD_WIDTH][MAX_PLAYFIELD_HEIGHT]; }; @@ -184,19 +200,20 @@ struct EngineSnapshotInfo_MM short Hit[MAX_PLAYFIELD_WIDTH][MAX_PLAYFIELD_HEIGHT]; short Box[MAX_PLAYFIELD_WIDTH][MAX_PLAYFIELD_HEIGHT]; short Angle[MAX_PLAYFIELD_WIDTH][MAX_PLAYFIELD_HEIGHT]; - short Frame[MAX_PLAYFIELD_WIDTH][MAX_PLAYFIELD_HEIGHT]; - short LX,LY, XS,YS, ELX,ELY; - short CT,Ct; + short LX, LY; + short XS, YS; + short ELX, ELY; + short CT, Ct; int last_LX, last_LY, last_hit_mask; int hold_x, hold_y; int pacman_nr; - unsigned int rotate_delay; - unsigned int pacman_delay; - unsigned int energy_delay; - unsigned int overload_delay; + DelayCounter rotate_delay; + DelayCounter pacman_delay; + DelayCounter energy_delay; + DelayCounter overload_delay; }; @@ -211,7 +228,6 @@ extern struct EngineSnapshotInfo_MM engine_snapshot_mm; extern short Ur[MM_MAX_PLAYFIELD_WIDTH][MM_MAX_PLAYFIELD_HEIGHT]; void mm_open_all(void); -void mm_close_all(void); void InitElementProperties_MM(void); @@ -219,10 +235,10 @@ void InitGfxBuffers_MM(void); void InitGameEngine_MM(void); void InitGameActions_MM(void); -void GameActions_MM(struct MouseActionInfo, boolean); +void GameActions_MM(struct MouseActionInfo); void DrawLaser_MM(void); -void DrawTileCursor_MM(int, boolean); +void DrawTileCursor_MM(int, int, boolean); boolean ClickElement(int, int, int); @@ -236,11 +252,15 @@ void SaveNativeLevel_MM(char *); int getFieldbufferOffsetX_MM(void); int getFieldbufferOffsetY_MM(void); +int getFlippedTileX_MM(int); +int getFlippedTileY_MM(int); +int getFlippedTileXY_MM(int); + void BlitScreenToBitmap_MM(Bitmap *); void RedrawPlayfield_MM(void); void LoadEngineSnapshotValues_MM(void); -void SaveEngineSnapshotValues_MM(ListNode **); +void SaveEngineSnapshotValues_MM(void); int getButtonFromTouchPosition(int, int, int, int); diff --git a/src/game_mm/main_mm.h b/src/game_mm/main_mm.h index 27cec727..94ee023e 100644 --- a/src/game_mm/main_mm.h +++ b/src/game_mm/main_mm.h @@ -35,6 +35,10 @@ extern int TILESIZE_VAR; #define TILEX_VAR TILESIZE_VAR #define TILEY_VAR TILESIZE_VAR +#define MINI_TILESIZE (TILESIZE / 2) +#define MINI_TILEX (TILEX / 2) +#define MINI_TILEY (TILEY / 2) + extern int SCR_FIELDX, SCR_FIELDY; #define MAX_BUF_XSIZE SCR_FIELDX diff --git a/src/game_mm/mm_files.c b/src/game_mm/mm_files.c index 542c847d..4a853609 100644 --- a/src/game_mm/mm_files.c +++ b/src/game_mm/mm_files.c @@ -112,9 +112,12 @@ void setLevelInfoToDefaults_MM(void) native_mm_level.time_bomb = 75; native_mm_level.time_ball = 75; native_mm_level.time_block = 75; - native_mm_level.laser_red = FALSE; - native_mm_level.laser_green = FALSE; - native_mm_level.laser_blue = TRUE; + native_mm_level.mm_laser_red = FALSE; + native_mm_level.mm_laser_green = FALSE; + native_mm_level.mm_laser_blue = TRUE; + native_mm_level.df_laser_red = TRUE; + native_mm_level.df_laser_green = TRUE; + native_mm_level.df_laser_blue = FALSE; for (i = 0; i < MAX_LEVEL_NAME_LEN; i++) native_mm_level.name[i] = '\0'; @@ -127,6 +130,25 @@ void setLevelInfoToDefaults_MM(void) for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++) native_mm_level.score[i] = 10; + int ball_content[] = + { + EL_MIRROR_START, + EL_MIRROR_FIXED_START, + EL_POLAR_START, + EL_POLAR_CROSS_START, + EL_PACMAN_START, + EL_KETTLE, + EL_BOMB, + EL_PRISM + }; + int num_ball_contents = sizeof(ball_content) / sizeof(int); + + native_mm_level.num_ball_contents = num_ball_contents; + native_mm_level.ball_choice_mode = ANIM_RANDOM; + + for (i = 0; i < num_ball_contents; i++) + native_mm_level.ball_content[i] = ball_content[i]; + native_mm_level.field[0][0] = Ur[0][0] = EL_MCDUFFIN_RIGHT; native_mm_level.field[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_EXIT_CLOSED; @@ -190,9 +212,9 @@ static int LoadLevel_MM_HEAD(File *file, int chunk_size, level->time_fuse = 25; laser_color = getFile8Bit(file); - level->laser_red = (laser_color >> 2) & 0x01; - level->laser_green = (laser_color >> 1) & 0x01; - level->laser_blue = (laser_color >> 0) & 0x01; + level->mm_laser_red = (laser_color >> 2) & 0x01; + level->mm_laser_green = (laser_color >> 1) & 0x01; + level->mm_laser_blue = (laser_color >> 0) & 0x01; level->encoding_16bit_field = (getFile8Bit(file) == 1 ? TRUE : FALSE); @@ -382,9 +404,9 @@ static void SaveLevel_MM_HEAD(FILE *file, struct LevelInfo_MM *level) fputc(level->amoeba_speed, file); fputc(level->time_fuse, file); - laser_color = ((level->laser_red << 2) | - (level->laser_green << 1) | - (level->laser_blue << 0)); + laser_color = ((level->mm_laser_red << 2) | + (level->mm_laser_green << 1) | + (level->mm_laser_blue << 0)); fputc(laser_color, file); fputc((level->encoding_16bit_field ? 1 : 0), file); diff --git a/src/game_mm/mm_game.c b/src/game_mm/mm_game.c index a1577285..b258e374 100644 --- a/src/game_mm/mm_game.c +++ b/src/game_mm/mm_game.c @@ -25,9 +25,8 @@ // values for Explode_MM() #define EX_PHASE_START 0 -#define EX_NORMAL 0 -#define EX_KETTLE 1 -#define EX_SHORT 2 +#define EX_TYPE_NONE 0 +#define EX_TYPE_NORMAL (1 << 0) // special positions in the game control window (relative to control window) #define XX_LEVEL 36 @@ -98,7 +97,22 @@ static void RaiseScoreElement_MM(int); static void RemoveMovingField_MM(int, int); static void InitMovingField_MM(int, int, int); static void ContinueMoving_MM(int, int); -static void Moving2Blocked_MM(int, int, int *, int *); + +static void AddLaserEdge(int, int); +static void ScanLaser(void); +static void DrawLaser(int, int); +static boolean HitElement(int, int); +static boolean HitOnlyAnEdge(int); +static boolean HitPolarizer(int, int); +static boolean HitBlock(int, int); +static boolean HitLaserSource(int, int); +static boolean HitLaserDestination(int, int); +static boolean HitReflectingWalls(int, int); +static boolean HitAbsorbingWalls(int, int); +static void RotateMirror(int, int, int); +static boolean ObjHit(int, int, int); +static void DeletePacMan(int, int); +static void MovePacMen(void); // bitmap for laser beam detection static Bitmap *laser_bitmap = NULL; @@ -111,13 +125,31 @@ static int hold_x = -1, hold_y = -1; static int pacman_nr = -1; // various game engine delay counters -static unsigned int rotate_delay = 0; -static unsigned int pacman_delay = 0; -static unsigned int energy_delay = 0; -static unsigned int overload_delay = 0; +static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY }; +static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY }; +static DelayCounter energy_delay = { ENERGY_DELAY }; +static DelayCounter overload_delay = { 0 }; + +// element mask positions for scanning pixels of MM elements +#define MM_MASK_MCDUFFIN_RIGHT 0 +#define MM_MASK_MCDUFFIN_UP 1 +#define MM_MASK_MCDUFFIN_LEFT 2 +#define MM_MASK_MCDUFFIN_DOWN 3 +#define MM_MASK_GRID_1 4 +#define MM_MASK_GRID_2 5 +#define MM_MASK_GRID_3 6 +#define MM_MASK_GRID_4 7 +#define MM_MASK_SLOPE_1 8 +#define MM_MASK_SLOPE_2 9 +#define MM_MASK_SLOPE_3 10 +#define MM_MASK_SLOPE_4 11 +#define MM_MASK_RECTANGLE 12 +#define MM_MASK_CIRCLE 13 + +#define NUM_MM_MASKS 14 // element masks for scanning pixels of MM elements -static const char mm_masks[10][16][16 + 1] = +static const char mm_masks[NUM_MM_MASKS][16][16 + 1] = { { " ", @@ -263,6 +295,78 @@ static const char mm_masks[10][16][16 + 1] = " XXX XXXX ", " XX XXXXX ", }, + { + " X", + " XX", + " XXX", + " XXXX", + " XXXXX", + " XXXXXX", + " XXXXXXX", + " XXXXXXXX", + " XXXXXXXXX", + " XXXXXXXXXX", + " XXXXXXXXXXX", + " XXXXXXXXXXXX", + " XXXXXXXXXXXXX", + " XXXXXXXXXXXXXX", + " XXXXXXXXXXXXXXX", + "XXXXXXXXXXXXXXXX", + }, + { + "X ", + "XX ", + "XXX ", + "XXXX ", + "XXXXX ", + "XXXXXX ", + "XXXXXXX ", + "XXXXXXXX ", + "XXXXXXXXX ", + "XXXXXXXXXX ", + "XXXXXXXXXXX ", + "XXXXXXXXXXXX ", + "XXXXXXXXXXXXX ", + "XXXXXXXXXXXXXX ", + "XXXXXXXXXXXXXXX ", + "XXXXXXXXXXXXXXXX", + }, + { + "XXXXXXXXXXXXXXXX", + "XXXXXXXXXXXXXXX ", + "XXXXXXXXXXXXXX ", + "XXXXXXXXXXXXX ", + "XXXXXXXXXXXX ", + "XXXXXXXXXXX ", + "XXXXXXXXXX ", + "XXXXXXXXX ", + "XXXXXXXX ", + "XXXXXXX ", + "XXXXXX ", + "XXXXX ", + "XXXX ", + "XXX ", + "XX ", + "X ", + }, + { + "XXXXXXXXXXXXXXXX", + " XXXXXXXXXXXXXXX", + " XXXXXXXXXXXXXX", + " XXXXXXXXXXXXX", + " XXXXXXXXXXXX", + " XXXXXXXXXXX", + " XXXXXXXXXX", + " XXXXXXXXX", + " XXXXXXXX", + " XXXXXXX", + " XXXXXX", + " XXXXX", + " XXXX", + " XXX", + " XX", + " X", + }, { "XXXXXXXXXXXXXXXX", "XXXXXXXXXXXXXXXX", @@ -310,6 +414,8 @@ static int get_element_angle(int element) IS_LASER(element) || IS_RECEIVER(element)) return 4 * element_phase; + else if (IS_DF_SLOPE(element)) + return 4 + (element_phase % 2) * 8; else return element_phase; } @@ -335,7 +441,7 @@ static void DrawLaserLines(struct XY *points, int num_points, int mode) Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg); Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL); - DrawLines(drawto, points, num_points, pixel_drawto); + DrawLines(drawto_mm, points, num_points, pixel_drawto); BEGIN_NO_HEADLESS { @@ -411,6 +517,20 @@ static void CheckExitMM(void) PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING); } +static void SetLaserColor(int brightness) +{ + int color_min = 0x00; + int color_max = brightness; // (0x00 <= brightness <= 0xFF) + int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD; + int color_down = color_max - color_up; + + pen_ray = + GetPixelFromRGB(window, + (game_mm.laser_red ? color_max : color_up), + (game_mm.laser_green ? color_down : color_min), + (game_mm.laser_blue ? color_down : color_min)); +} + static void InitMovDir_MM(int x, int y) { int element = Tile[x][y]; @@ -448,7 +568,7 @@ static void InitField(int x, int y, boolean init_game) case EL_KETTLE: case EL_CELL: - if (native_mm_level.auto_count_kettles) + if (init_game && native_mm_level.auto_count_kettles) game_mm.kettles_still_needed++; break; @@ -508,9 +628,27 @@ static void InitField(int x, int y, boolean init_game) } else if (IS_MCDUFFIN(element) || IS_LASER(element)) { - laser.start_edge.x = x; - laser.start_edge.y = y; - laser.start_angle = get_element_angle(element); + if (init_game) + { + laser.start_edge.x = x; + laser.start_edge.y = y; + laser.start_angle = get_element_angle(element); + } + + if (IS_MCDUFFIN(element)) + { + game_mm.laser_red = native_mm_level.mm_laser_red; + game_mm.laser_green = native_mm_level.mm_laser_green; + game_mm.laser_blue = native_mm_level.mm_laser_blue; + } + else + { + game_mm.laser_red = native_mm_level.df_laser_red; + game_mm.laser_green = native_mm_level.df_laser_green; + game_mm.laser_blue = native_mm_level.df_laser_blue; + } + + game_mm.has_mcduffin = (IS_MCDUFFIN(element)); } break; @@ -537,7 +675,6 @@ static void InitCycleElements_RotateSingleStep(void) Tile[x][y] = next_element; - DrawField_MM(x, y); game_mm.cycle[i].steps -= step; } } @@ -574,10 +711,7 @@ static void InitLaser(void) AddLaserEdge(LX, LY); // set laser starting edge - pen_ray = GetPixelFromRGB(window, - native_mm_level.laser_red * 0xFF, - native_mm_level.laser_green * 0xFF, - native_mm_level.laser_blue * 0xFF); + SetLaserColor(0xFF); } void InitGameEngine_MM(void) @@ -587,8 +721,8 @@ void InitGameEngine_MM(void) BEGIN_NO_HEADLESS { // initialize laser bitmap to current playfield (screen) size - ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height); - ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height); + ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height); + ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height); } END_NO_HEADLESS @@ -602,10 +736,17 @@ void InitGameEngine_MM(void) (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed); game_mm.lights_still_needed = 0; game_mm.num_keys = 0; + game_mm.ball_choice_pos = 0; + + game_mm.laser_red = FALSE; + game_mm.laser_green = FALSE; + game_mm.laser_blue = TRUE; + game_mm.has_mcduffin = TRUE; game_mm.level_solved = FALSE; game_mm.game_over = FALSE; game_mm.game_over_cause = 0; + game_mm.game_over_message = NULL; game_mm.laser_overload_value = 0; game_mm.laser_enabled = FALSE; @@ -624,6 +765,9 @@ void InitGameEngine_MM(void) laser.fuse_x = laser.fuse_y = -1; laser.dest_element = EL_EMPTY; + laser.dest_element_last = EL_EMPTY; + laser.dest_element_last_x = -1; + laser.dest_element_last_y = -1; laser.wall_mask = 0; last_LX = 0; @@ -637,10 +781,10 @@ void InitGameEngine_MM(void) CT = Ct = 0; - rotate_delay = 0; - pacman_delay = 0; - energy_delay = 0; - overload_delay = 0; + rotate_delay.count = 0; + pacman_delay.count = 0; + energy_delay.count = 0; + overload_delay.count = 0; ClickElement(-1, -1, -1); @@ -653,7 +797,6 @@ void InitGameEngine_MM(void) Angle[x][y] = 0; MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0; Store[x][y] = Store2[x][y] = 0; - Frame[x][y] = 0; Stop[x][y] = FALSE; InitField(x, y, TRUE); @@ -691,16 +834,27 @@ void InitGameActions_MM(void) cycle_steps_done++; } - BackToFront(); + AdvanceFrameCounter(); + AdvanceGfxFrame(); - ColorCycling(); + if (PendingEscapeKeyEvent()) + continue; #ifdef DEBUG if (setup.quick_doors) continue; #endif + + DrawLevel_MM(); + + BackToFront_MM(); } +#ifdef DEBUG + if (setup.quick_doors) + DrawLevel_MM(); +#endif + ScanLaser(); if (game_mm.kettles_still_needed == 0) @@ -708,14 +862,64 @@ void InitGameActions_MM(void) SetTileCursorXY(laser.start_edge.x, laser.start_edge.y); SetTileCursorActive(TRUE); + + // restart all delay counters after initially cycling game elements + ResetFrameCounter(&rotate_delay); + ResetFrameCounter(&pacman_delay); + ResetFrameCounter(&energy_delay); + ResetFrameCounter(&overload_delay); +} + +static void FadeOutLaser(void) +{ + int i; + + for (i = 15; i >= 0; i--) + { + SetLaserColor(0x11 * i); + + DrawLaser(0, DL_LASER_ENABLED); + + BackToFront_MM(); + Delay_WithScreenUpdates(50); + } + + DrawLaser(0, DL_LASER_DISABLED); + + StopSound_MM(SND_MM_GAME_HEALTH_CHARGING); +} + +static void GameOver_MM(int game_over_cause) +{ + game_mm.game_over = TRUE; + game_mm.game_over_cause = game_over_cause; + game_mm.game_over_message = (game_mm.has_mcduffin ? + (game_over_cause == GAME_OVER_BOMB ? + "Bomb killed Mc Duffin!" : + game_over_cause == GAME_OVER_NO_ENERGY ? + "Out of magic energy!" : + game_over_cause == GAME_OVER_OVERLOADED ? + "Magic spell hit Mc Duffin!" : + NULL) : + (game_over_cause == GAME_OVER_BOMB ? + "Bomb destroyed laser cannon!" : + game_over_cause == GAME_OVER_NO_ENERGY ? + "Out of laser energy!" : + game_over_cause == GAME_OVER_OVERLOADED ? + "Laser beam hit laser cannon!" : + NULL)); + + SetTileCursorActive(FALSE); } -void AddLaserEdge(int lx, int ly) +static void AddLaserEdge(int lx, int ly) { - int clx = dSX + lx; - int cly = dSY + ly; + int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX); + int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY); - if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2) + // check if laser is still inside visible playfield area (or inside level) + if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize || + cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize) { Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly); @@ -729,8 +933,15 @@ void AddLaserEdge(int lx, int ly) laser.redraw = TRUE; } -void AddDamagedField(int ex, int ey) +static void AddDamagedField(int ex, int ey) { + // prevent adding the same field position again + if (laser.num_damages > 0 && + laser.damage[laser.num_damages - 1].x == ex && + laser.damage[laser.num_damages - 1].y == ey && + laser.damage[laser.num_damages - 1].edge == laser.num_edges) + return; + laser.damage[laser.num_damages].is_mirror = FALSE; laser.damage[laser.num_damages].angle = laser.current_angle; laser.damage[laser.num_damages].edge = laser.num_edges; @@ -756,14 +967,33 @@ static boolean StepBehind(void) static int getMaskFromElement(int element) { - if (IS_GRID(element)) - return IMG_MM_MASK_GRID_1 + get_element_phase(element); - else if (IS_MCDUFFIN(element)) - return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element); - else if (IS_RECTANGLE(element) || IS_DF_GRID(element)) - return IMG_MM_MASK_RECTANGLE; + if (IS_MCDUFFIN(element)) + return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element); + else if (IS_GRID(element)) + return MM_MASK_GRID_1 + get_element_phase(element); + else if (IS_DF_GRID(element)) + return MM_MASK_RECTANGLE; + else if (IS_DF_SLOPE(element)) + return MM_MASK_SLOPE_1 + get_element_phase(element); + else if (IS_RECTANGLE(element)) + return MM_MASK_RECTANGLE; else - return IMG_MM_MASK_CIRCLE; + return MM_MASK_CIRCLE; +} + +static int getPixelFromMask(int pos, int dx, int dy) +{ + return (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0); +} + +static int getLevelFromLaserX(int x) +{ + return x / TILEX - (x < 0 ? 1 : 0); // correct negative values +} + +static int getLevelFromLaserY(int y) +{ + return y / TILEY - (y < 0 ? 1 : 0); // correct negative values } static int ScanPixel(void) @@ -791,14 +1021,34 @@ static int ScanPixel(void) } #endif + // check if laser scan has crossed element boundaries (not just mini tiles) + boolean cross_x = (LX / TILEX != (LX + 2) / TILEX); + boolean cross_y = (LY / TILEY != (LY + 2) / TILEY); + + if (cross_x && cross_y) + { + int elx1 = (LX - XS) / TILEX; + int ely1 = (LY + YS) / TILEY; + int elx2 = (LX + XS) / TILEX; + int ely2 = (LY - YS) / TILEY; + + // add element corners left and right from the laser beam to damage list + + if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY) + AddDamagedField(elx1, ely1); + + if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY) + AddDamagedField(elx2, ely2); + } + for (i = 0; i < 4; i++) { int px = LX + (i % 2) * 2; int py = LY + (i / 2) * 2; int dx = px % TILEX; int dy = py % TILEY; - int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct - int ly = (py + TILEY) / TILEY - 1; // negative values! + int lx = getLevelFromLaserX(px); + int ly = getLevelFromLaserY(py); Pixel pixel; if (IN_LEV_FIELD(lx, ly)) @@ -817,13 +1067,14 @@ static int ScanPixel(void) } else { - int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT; + int pos = getMaskFromElement(element); - pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0); + pixel = getPixelFromMask(pos, dx, dy); } } else { + // check if laser is still inside visible playfield area pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE || cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE); } @@ -843,15 +1094,46 @@ static int ScanPixel(void) return hit_mask; } -void ScanLaser(void) +static void DeactivateLaserTargetElement(void) { - int element; + if (laser.dest_element_last == EL_BOMB_ACTIVE || + laser.dest_element_last == EL_MINE_ACTIVE || + laser.dest_element_last == EL_GRAY_BALL_ACTIVE || + laser.dest_element_last == EL_GRAY_BALL_OPENING) + { + int x = laser.dest_element_last_x; + int y = laser.dest_element_last_y; + int element = laser.dest_element_last; + + if (Tile[x][y] == element) + Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB : + element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL); + + if (Tile[x][y] == EL_GRAY_BALL) + MovDelay[x][y] = 0; + + laser.dest_element_last = EL_EMPTY; + laser.dest_element_last_x = -1; + laser.dest_element_last_y = -1; + } +} + +static void ScanLaser(void) +{ + int element = EL_EMPTY; + int last_element = EL_EMPTY; int end = 0, rf = laser.num_edges; // do not scan laser again after the game was lost for whatever reason if (game_mm.game_over) return; + // do not scan laser if fuse is off + if (laser.fuse_off) + return; + + DeactivateLaserTargetElement(); + laser.overloaded = FALSE; laser.stops_inside_element = FALSE; @@ -883,41 +1165,92 @@ void ScanLaser(void) LX, LY, XS, YS); #endif - // hit something -- check out what it was - ELX = (LX + XS) / TILEX; - ELY = (LY + YS) / TILEY; + // check if laser scan has hit two diagonally adjacent element corners + boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1); + boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2); + + // check if laser scan has crossed element boundaries (not just mini tiles) + boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2)); + boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2)); + + if (cross_x || cross_y) + { + // hit something at next tile -- check out what it was + ELX = getLevelFromLaserX(LX + XS); + ELY = getLevelFromLaserY(LY + YS); + } + else + { + // hit something at same tile -- check out what it was + ELX = getLevelFromLaserX(LX); + ELY = getLevelFromLaserY(LY); + } #if 0 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)", hit_mask, LX, LY, ELX, ELY); #endif - if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY)) + if (!IN_LEV_FIELD(ELX, ELY)) { + // laser next step position + int x = cSX + LX + XS; + int y = cSY + LY + YS; + + // check if next step of laser is still inside visible playfield area + if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE && + y >= REAL_SY && y < REAL_SY + FULL_SYSIZE) + { + // go on with another step + LX += XS; + LY += YS; + + continue; + } + element = EL_EMPTY; laser.dest_element = element; break; } - if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT)) + // handle special case of laser hitting two diagonally adjacent elements + // (with or without a third corner element behind these two elements) + if ((diag_1 || diag_2) && cross_x && cross_y) { - /* we have hit the top-right and bottom-left element -- - choose the bottom-left one */ - /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE - ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE - THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */ - ELX = (LX - 2) / TILEX; - ELY = (LY + 2) / TILEY; - } + // compare the two diagonally adjacent elements + int xoffset = 2; + int yoffset = 2 * (diag_1 ? -1 : +1); + int elx1 = (LX - xoffset) / TILEX; + int ely1 = (LY + yoffset) / TILEY; + int elx2 = (LX + xoffset) / TILEX; + int ely2 = (LY - yoffset) / TILEY; + int e1 = Tile[elx1][ely1]; + int e2 = Tile[elx2][ely2]; + boolean use_element_1 = FALSE; + + if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2)) + { + if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2)) + use_element_1 = (RND(2) ? TRUE : FALSE); + else if (IS_WALL_ICE(e1)) + use_element_1 = TRUE; + } + else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2)) + { + // if both tiles match, we can just select the first one + if (IS_WALL_AMOEBA(e1)) + use_element_1 = TRUE; + } + else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2)) + { + // if both tiles match, we can just select the first one + if (IS_ABSORBING_BLOCK(e1)) + use_element_1 = TRUE; + } - if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT)) - { - /* we have hit the top-left and bottom-right element -- - choose the top-left one */ - // !!! SEE ABOVE !!! - ELX = (LX - 2) / TILEX; - ELY = (LY - 2) / TILEY; + ELX = (use_element_1 ? elx1 : elx2); + ELY = (use_element_1 ? ely1 : ely2); } #if 0 @@ -925,6 +1258,8 @@ void ScanLaser(void) hit_mask, LX, LY, ELX, ELY); #endif + last_element = element; + element = Tile[ELX][ELY]; laser.dest_element = element; @@ -943,9 +1278,15 @@ void ScanLaser(void) ELX, ELY, element); #endif + // special case: leaving fixed MM steel grid (upwards) with non-90° angle + if (element == EL_EMPTY && + IS_GRID_STEEL(last_element) && + laser.current_angle % 4) // angle is not 90° + element = last_element; + if (element == EL_EMPTY) { - if (!HitOnlyAnEdge(element, hit_mask)) + if (!HitOnlyAnEdge(hit_mask)) break; } else if (element == EL_FUSE_ON) @@ -1001,6 +1342,14 @@ void ScanLaser(void) if (rf) DrawLaser(rf - 1, DL_LASER_ENABLED); rf = laser.num_edges; + + if (!IS_DF_WALL_STEEL(element)) + { + // only used for scanning DF steel walls; reset for all other elements + last_LX = 0; + last_LY = 0; + last_hit_mask = 0; + } } #if 0 @@ -1035,6 +1384,22 @@ void ScanLaser(void) #endif } +static void ScanLaser_FromLastMirror(void) +{ + int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); + int i; + + for (i = start_pos; i >= 0; i--) + if (laser.damage[i].is_mirror) + break; + + int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0); + + DrawLaser(start_edge, DL_LASER_DISABLED); + + ScanLaser(); +} + static void DrawLaserExt(int start_edge, int num_edges, int mode) { int element; @@ -1227,6 +1592,13 @@ static void DrawLaserExt(int start_edge, int num_edges, int mode) void DrawLaser(int start_edge, int mode) { + // do not draw laser if fuse is off + if (laser.fuse_off && mode == DL_LASER_ENABLED) + return; + + if (mode == DL_LASER_DISABLED) + DeactivateLaserTargetElement(); + if (laser.num_edges - start_edge < 0) { Warn("DrawLaser: laser.num_edges - start_edge < 0"); @@ -1313,10 +1685,80 @@ void DrawLaser_MM(void) DrawLaser(0, game_mm.laser_enabled); } -boolean HitElement(int element, int hit_mask) +static boolean HitElement(int element, int hit_mask) { - if (HitOnlyAnEdge(element, hit_mask)) - return FALSE; + if (IS_DF_SLOPE(element)) + { + // check if laser scan has crossed element boundaries (not just mini tiles) + boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2)); + boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2)); + int element_angle = get_element_angle(element); + int mirrored_angle = get_mirrored_angle(laser.current_angle, element_angle); + int opposite_angle = get_opposite_angle(laser.current_angle); + + // check if wall (horizontal or vertical) side of slope was hit + if (hit_mask == HIT_MASK_LEFT || + hit_mask == HIT_MASK_RIGHT || + hit_mask == HIT_MASK_TOP || + hit_mask == HIT_MASK_BOTTOM) + { + boolean hit_slope_corner_in_laser_direction = + ((hit_mask == HIT_MASK_LEFT && (element == EL_DF_SLOPE_01 || + element == EL_DF_SLOPE_02)) || + (hit_mask == HIT_MASK_RIGHT && (element == EL_DF_SLOPE_00 || + element == EL_DF_SLOPE_03)) || + (hit_mask == HIT_MASK_TOP && (element == EL_DF_SLOPE_02 || + element == EL_DF_SLOPE_03)) || + (hit_mask == HIT_MASK_BOTTOM && (element == EL_DF_SLOPE_00 || + element == EL_DF_SLOPE_01))); + + boolean hit_slope_corner_in_laser_direction_double_checked = + (cross_x && cross_y && + laser.current_angle == mirrored_angle && + hit_slope_corner_in_laser_direction); + + // check special case of laser hitting the corner of a slope and another + // element (either wall or another slope), following the diagonal side + // of the slope which has the same angle as the direction of the laser + if (!hit_slope_corner_in_laser_direction_double_checked) + return HitReflectingWalls(element, hit_mask); + } + + // check if an edge was hit while crossing element borders + if (cross_x && cross_y && get_number_of_bits(hit_mask) == 1) + { + // check both sides of potentially diagonal side of slope + int dx1 = (LX + XS) % TILEX; + int dy1 = (LY + YS) % TILEY; + int dx2 = (LX + XS + 2) % TILEX; + int dy2 = (LY + YS + 2) % TILEY; + int pos = getMaskFromElement(element); + + // check if we are entering empty space area after hitting edge + if (!getPixelFromMask(pos, dx1, dy1) && + !getPixelFromMask(pos, dx2, dy2)) + { + // we already know that we hit an edge, but use this function to go on + if (HitOnlyAnEdge(hit_mask)) + return FALSE; + } + } + + // check if laser is reflected by slope by 180° + if (mirrored_angle == opposite_angle) + { + AddDamagedField(LX / TILEX, LY / TILEY); + + laser.overloaded = TRUE; + + return TRUE; + } + } + else + { + if (HitOnlyAnEdge(hit_mask)) + return FALSE; + } if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY)) element = MovingOrBlocked2Element_MM(ELX, ELY); @@ -1336,23 +1778,35 @@ boolean HitElement(int element, int hit_mask) AddDamagedField(ELX, ELY); + boolean through_center = ((ELX * TILEX + 14 - LX) * YS == + (ELY * TILEY + 14 - LY) * XS); + // this is more precise: check if laser would go through the center - if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS) + if (!IS_DF_SLOPE(element) && !through_center) { + int skip_count = 0; + + // prevent cutting through laser emitter with laser beam + if (IS_LASER(element)) + return TRUE; + // skip the whole element before continuing the scan do { LX += XS; LY += YS; + + skip_count++; } while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0); - if (LX/TILEX > ELX || LY/TILEY > ELY) + if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1) { /* skipping scan positions to the right and down skips one scan position too much, because this is only the top left scan position of totally four scan positions (plus one to the right, one to the bottom and one to the bottom right) */ + /* ... but only roll back scan position if more than one step done */ LX -= XS; LY -= YS; @@ -1399,10 +1853,28 @@ boolean HitElement(int element, int hit_mask) return TRUE; } - if (!IS_BEAMER(element) && - !IS_FIBRE_OPTIC(element) && - !IS_GRID_WOOD(element) && - element != EL_FUEL_EMPTY) + if (IS_DF_SLOPE(element) && !through_center) + { + int correction = 2; + + if (hit_mask == HIT_MASK_ALL) + { + // laser already inside slope -- go back half step + LX -= XS / 2; + LY -= YS / 2; + + correction = 1; + } + + AddLaserEdge(LX, LY); + + LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0); + LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0); + } + else if (!IS_BEAMER(element) && + !IS_FIBRE_OPTIC(element) && + !IS_GRID_WOOD(element) && + element != EL_FUEL_EMPTY) { #if 0 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS) @@ -1423,6 +1895,8 @@ boolean HitElement(int element, int hit_mask) IS_POLAR_CROSS(element) || IS_DF_MIRROR(element) || IS_DF_MIRROR_AUTO(element) || + IS_DF_MIRROR_FIXED(element) || + IS_DF_SLOPE(element) || element == EL_PRISM || element == EL_REFRACTOR) { @@ -1441,7 +1915,9 @@ boolean HitElement(int element, int hit_mask) if (IS_MIRROR(element) || IS_MIRROR_FIXED(element) || IS_DF_MIRROR(element) || - IS_DF_MIRROR_AUTO(element)) + IS_DF_MIRROR_AUTO(element) || + IS_DF_MIRROR_FIXED(element) || + IS_DF_SLOPE(element)) laser.current_angle = get_mirrored_angle(laser.current_angle, get_element_angle(element)); @@ -1451,22 +1927,36 @@ boolean HitElement(int element, int hit_mask) XS = 2 * Step[laser.current_angle].x; YS = 2 * Step[laser.current_angle].y; - if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle - step_size = 8; - else - step_size = 4; + if (through_center) + { + // start from center position for all game elements but slope + if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle + step_size = 8; + else + step_size = 4; - LX += step_size * XS; - LY += step_size * YS; + LX += step_size * XS; + LY += step_size * YS; + } + else + { + // advance laser position until reaching the next tile (slopes) + while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX && + LY / TILEY == ELY && (LY + 2) / TILEY == ELY) + { + LX += XS; + LY += YS; + } + } -#if 0 // draw sparkles on mirror - if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) && + if ((IS_MIRROR(element) || + IS_MIRROR_FIXED(element) || + element == EL_PRISM) && current_angle != laser.current_angle) { - MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1); + MovDelay[ELX][ELY] = 11; // start animation } -#endif if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) && current_angle != laser.current_angle) @@ -1476,6 +1966,68 @@ boolean HitElement(int element, int hit_mask) (get_opposite_angle(laser.current_angle) == laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE); + if (IS_DF_SLOPE(element)) + { + // handle special cases for slope element + + if (IS_45_ANGLE(laser.current_angle)) + { + int elx, ely; + + elx = getLevelFromLaserX(LX + XS); + ely = getLevelFromLaserY(LY + YS); + + if (IN_LEV_FIELD(elx, ely)) + { + int element_next = Tile[elx][ely]; + + // check if slope is followed by slope with opposite orientation + if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2) + laser.overloaded = TRUE; + } + + int nr = element - EL_DF_SLOPE_START; + int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) : + nr == 1 ? (XS > 0 ? TILEX : 0) : + nr == 2 ? (XS > 0 ? TILEX : 0) : + nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0); + int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) : + nr == 1 ? (YS > 0 ? TILEY - 1 : -1) : + nr == 2 ? (YS > 0 ? TILEY : 0) : + nr == 3 ? (YS > 0 ? TILEY : 0) : 0); + + int px = ELX * TILEX + dx; + int py = ELY * TILEY + dy; + + dx = px % TILEX; + dy = py % TILEY; + + elx = getLevelFromLaserX(px); + ely = getLevelFromLaserY(py); + + if (IN_LEV_FIELD(elx, ely)) + { + int element_side = Tile[elx][ely]; + + // check if end of slope is blocked by other element + if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side)) + { + int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX; + + if (element & (1 << pos)) + laser.overloaded = TRUE; + } + else + { + int pos = getMaskFromElement(element_side); + + if (getPixelFromMask(pos, dx, dy)) + laser.overloaded = TRUE; + } + } + } + } + return (laser.overloaded ? TRUE : FALSE); } @@ -1486,10 +2038,20 @@ boolean HitElement(int element, int hit_mask) return TRUE; } - if (element == EL_BOMB || element == EL_MINE) + if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL) { PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING); + Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : + element == EL_MINE ? EL_MINE_ACTIVE : + EL_GRAY_BALL_ACTIVE); + + GfxFrame[ELX][ELY] = 0; // restart animation + + laser.dest_element_last = Tile[ELX][ELY]; + laser.dest_element_last_x = ELX; + laser.dest_element_last_y = ELY; + if (element == EL_MINE) laser.overloaded = TRUE; } @@ -1499,9 +2061,11 @@ boolean HitElement(int element, int hit_mask) element == EL_KEY || element == EL_LIGHTBALL || element == EL_PACMAN || - IS_PACMAN(element)) + IS_PACMAN(element) || + IS_ENVELOPE(element)) { - if (!IS_PACMAN(element)) + if (!IS_PACMAN(element) && + !IS_ENVELOPE(element)) Bang_MM(ELX, ELY); if (element == EL_PACMAN) @@ -1529,6 +2093,10 @@ boolean HitElement(int element, int hit_mask) { DeletePacMan(ELX, ELY); } + else if (IS_ENVELOPE(element)) + { + Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]); + } RaiseScoreElement_MM(element); @@ -1638,7 +2206,7 @@ boolean HitElement(int element, int hit_mask) return TRUE; } -boolean HitOnlyAnEdge(int element, int hit_mask) +static boolean HitOnlyAnEdge(int hit_mask) { // check if the laser hit only the edge of an element and, if so, go on @@ -1695,9 +2263,9 @@ boolean HitOnlyAnEdge(int element, int hit_mask) return FALSE; } -boolean HitPolarizer(int element, int hit_mask) +static boolean HitPolarizer(int element, int hit_mask) { - if (HitOnlyAnEdge(element, hit_mask)) + if (HitOnlyAnEdge(hit_mask)) return FALSE; if (IS_DF_GRID(element)) @@ -1762,17 +2330,23 @@ boolean HitPolarizer(int element, int hit_mask) } else if (IS_GRID_STEEL(element)) { + // may be required if graphics for steel grid redefined + AddDamagedField(ELX, ELY); + return HitReflectingWalls(element, hit_mask); } else // IS_GRID_WOOD { + // may be required if graphics for wooden grid redefined + AddDamagedField(ELX, ELY); + return HitAbsorbingWalls(element, hit_mask); } return TRUE; } -boolean HitBlock(int element, int hit_mask) +static boolean HitBlock(int element, int hit_mask) { boolean check = FALSE; @@ -1815,11 +2389,9 @@ boolean HitBlock(int element, int hit_mask) if (element == EL_GATE_STONE || element == EL_GATE_WOOD) { int xs = XS / 2, ys = YS / 2; - int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT; - int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT; - if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 || - (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2) + if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 || + (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2) { laser.overloaded = (element == EL_GATE_STONE); @@ -1858,11 +2430,9 @@ boolean HitBlock(int element, int hit_mask) if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD) { int xs = XS / 2, ys = YS / 2; - int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT; - int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT; - if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 || - (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2) + if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 || + (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2) { laser.overloaded = (element == EL_BLOCK_STONE); @@ -1893,9 +2463,9 @@ boolean HitBlock(int element, int hit_mask) return TRUE; } -boolean HitLaserSource(int element, int hit_mask) +static boolean HitLaserSource(int element, int hit_mask) { - if (HitOnlyAnEdge(element, hit_mask)) + if (HitOnlyAnEdge(hit_mask)) return FALSE; PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING); @@ -1905,9 +2475,9 @@ boolean HitLaserSource(int element, int hit_mask) return TRUE; } -boolean HitLaserDestination(int element, int hit_mask) +static boolean HitLaserDestination(int element, int hit_mask) { - if (HitOnlyAnEdge(element, hit_mask)) + if (HitOnlyAnEdge(hit_mask)) return FALSE; if (element != EL_EXIT_OPEN && @@ -1951,7 +2521,7 @@ boolean HitLaserDestination(int element, int hit_mask) return TRUE; } -boolean HitReflectingWalls(int element, int hit_mask) +static boolean HitReflectingWalls(int element, int hit_mask) { // check if laser hits side of a wall with an angle that is not 90° if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP || @@ -2143,7 +2713,7 @@ boolean HitReflectingWalls(int element, int hit_mask) } } - if (!HitOnlyAnEdge(element, hit_mask)) + if (!HitOnlyAnEdge(hit_mask)) { laser.overloaded = TRUE; @@ -2153,9 +2723,9 @@ boolean HitReflectingWalls(int element, int hit_mask) return FALSE; } -boolean HitAbsorbingWalls(int element, int hit_mask) +static boolean HitAbsorbingWalls(int element, int hit_mask) { - if (HitOnlyAnEdge(element, hit_mask)) + if (HitOnlyAnEdge(hit_mask)) return FALSE; if (ABS(XS) == 4 && @@ -2192,10 +2762,18 @@ boolean HitAbsorbingWalls(int element, int hit_mask) if (IS_WALL_ICE(element)) { + int lx = LX + XS; + int ly = LY + YS; int mask; - mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal) - mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical) + // check if laser hit adjacent edges of two diagonal tiles + if (ELX != lx / TILEX) + lx = LX - XS; + if (ELY != ly / TILEY) + ly = LY - YS; + + mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal) + mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical) // check if laser hits wall with an angle of 90° if (IS_90_ANGLE(laser.current_angle)) @@ -2247,7 +2825,7 @@ boolean HitAbsorbingWalls(int element, int hit_mask) if (IS_90_ANGLE(laser.current_angle)) mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2); - laser.dest_element = element2 | EL_WALL_AMOEBA; + laser.dest_element = element2 | EL_WALL_AMOEBA_BASE; laser.wall_mask = mask; } @@ -2280,12 +2858,25 @@ static void OpenExit(int x, int y) } } -static void OpenSurpriseBall(int x, int y) +static void OpenGrayBall(int x, int y) { int delay = 2; if (!MovDelay[x][y]) // next animation frame + { + if (IS_WALL(Store[x][y])) + { + DrawWalls_MM(x, y, Store[x][y]); + + // copy wall tile to spare bitmap for "melting" animation + BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY, + TILEX, TILEY, x * TILEX, y * TILEY); + + DrawElement_MM(x, y, EL_GRAY_BALL); + } + MovDelay[x][y] = 50 * delay; + } if (MovDelay[x][y]) // wait some time before next frame { @@ -2294,25 +2885,77 @@ static void OpenSurpriseBall(int x, int y) if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y)) { Bitmap *bitmap; - int graphic = el2gfx(Store[x][y]); int gx, gy; int dx = RND(26), dy = RND(26); - getGraphicSource(graphic, 0, &bitmap, &gx, &gy); + if (IS_WALL(Store[x][y])) + { + // copy wall tile from spare bitmap for "melting" animation + bitmap = bitmap_db_field; + gx = x * TILEX; + gy = y * TILEY; + } + else + { + int graphic = el2gfx(Store[x][y]); - BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6, + getGraphicSource(graphic, 0, &bitmap, &gx, &gy); + } + + BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6, cSX + x * TILEX + dx, cSY + y * TILEY + dy); + laser.redraw = TRUE; + MarkTileDirty(x, y); } if (!MovDelay[x][y]) { Tile[x][y] = Store[x][y]; - Store[x][y] = 0; + Store[x][y] = Store2[x][y] = 0; + MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0; + + InitField(x, y, FALSE); + DrawField_MM(x, y); + + ScanLaser_FromLastMirror(); + } + } +} + +static void OpenEnvelope(int x, int y) +{ + int num_frames = 8; // seven frames plus final empty space + + if (!MovDelay[x][y]) // next animation frame + MovDelay[x][y] = num_frames; + + if (MovDelay[x][y]) // wait some time before next frame + { + int nr = ENVELOPE_OPENING_NR(Tile[x][y]); + + MovDelay[x][y]--; + + if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y)) + { + int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING); + int frame = num_frames - MovDelay[x][y] - 1; + + DrawGraphicAnimation_MM(x, y, graphic, frame); + + laser.redraw = TRUE; + } + + if (MovDelay[x][y] == 0) + { + Tile[x][y] = EL_EMPTY; + DrawField_MM(x, y); ScanLaser(); + + ShowEnvelope(nr); } } } @@ -2329,33 +2972,22 @@ static void MeltIce(int x, int y) { int phase; int wall_mask = Store2[x][y]; - int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE; + int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE; MovDelay[x][y]--; phase = frames - MovDelay[x][y] / delay - 1; if (!MovDelay[x][y]) { - int i; - Tile[x][y] = real_element & (wall_mask ^ 0xFF); Store[x][y] = Store2[x][y] = 0; DrawWalls_MM(x, y, Tile[x][y]); - if (Tile[x][y] == EL_WALL_ICE) + if (Tile[x][y] == EL_WALL_ICE_BASE) Tile[x][y] = EL_EMPTY; - for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--) - if (laser.damage[i].is_mirror) - break; - - if (i > 0) - DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED); - else - DrawLaser(0, DL_LASER_DISABLED); - - ScanLaser(); + ScanLaser_FromLastMirror(); } else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y)) { @@ -2378,7 +3010,7 @@ static void GrowAmoeba(int x, int y) { int phase; int wall_mask = Store2[x][y]; - int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA; + int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE; MovDelay[x][y]--; phase = MovDelay[x][y] / delay; @@ -2398,17 +3030,58 @@ static void GrowAmoeba(int x, int y) } } +static void DrawFieldAnimated_MM(int x, int y) +{ + DrawField_MM(x, y); + + laser.redraw = TRUE; +} + +static void DrawFieldAnimatedIfNeeded_MM(int x, int y) +{ + int element = Tile[x][y]; + int graphic = el2gfx(element); + + if (!getGraphicInfo_NewFrame(x, y, graphic)) + return; + + DrawField_MM(x, y); + + laser.redraw = TRUE; +} + +static void DrawFieldTwinkle(int x, int y) +{ + if (MovDelay[x][y] != 0) // wait some time before next frame + { + MovDelay[x][y]--; + + DrawField_MM(x, y); + + if (MovDelay[x][y] != 0) + { + int graphic = IMG_TWINKLE_WHITE; + int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]); + + DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame); + } + + laser.redraw = TRUE; + } +} + static void Explode_MM(int x, int y, int phase, int mode) { int num_phase = 9, delay = 2; int last_phase = num_phase * delay; int half_phase = (num_phase / 2) * delay; + int center_element; laser.redraw = TRUE; if (phase == EX_PHASE_START) // initialize 'Store[][]' field { - int center_element = Tile[x][y]; + center_element = Tile[x][y]; if (IS_MOVING(x, y) || IS_BLOCKED(x, y)) { @@ -2419,22 +3092,32 @@ static void Explode_MM(int x, int y, int phase, int mode) Tile[x][y] = center_element; } - if (center_element == EL_BOMB || IS_MCDUFFIN(center_element)) - Store[x][y] = center_element; - else + if (center_element != EL_GRAY_BALL_ACTIVE) Store[x][y] = EL_EMPTY; + Store2[x][y] = center_element; - Store2[x][y] = mode; Tile[x][y] = EL_EXPLODING_OPAQUE; + + GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB : + center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL : + IS_MCDUFFIN(center_element) ? EL_MCDUFFIN : + center_element); + MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0; - Frame[x][y] = 1; + + ExplodePhase[x][y] = 1; return; } - Frame[x][y] = (phase < last_phase ? phase + 1 : 0); + if (phase == 1) + GfxFrame[x][y] = 0; // restart explosion animation + + ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0); - if (phase == half_phase) + center_element = Store2[x][y]; + + if (phase == half_phase && Store[x][y] == EL_EMPTY) { Tile[x][y] = EL_EXPLODING_TRANSP; @@ -2444,75 +3127,37 @@ static void Explode_MM(int x, int y, int phase, int mode) if (phase == last_phase) { - if (Store[x][y] == EL_BOMB) + if (center_element == EL_BOMB_ACTIVE) { DrawLaser(0, DL_LASER_DISABLED); InitLaser(); Bang_MM(laser.start_edge.x, laser.start_edge.y); - Store[x][y] = EL_EMPTY; - - game_mm.game_over = TRUE; - game_mm.game_over_cause = GAME_OVER_BOMB; - - SetTileCursorActive(FALSE); laser.overloaded = FALSE; } - else if (IS_MCDUFFIN(Store[x][y])) + else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element)) { - Store[x][y] = EL_EMPTY; - - game.restart_game_message = "Bomb killed Mc Duffin! Play it again?"; + GameOver_MM(GAME_OVER_BOMB); } Tile[x][y] = Store[x][y]; + Store[x][y] = Store2[x][y] = 0; MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0; InitField(x, y, FALSE); DrawField_MM(x, y); + + if (center_element == EL_GRAY_BALL_ACTIVE) + ScanLaser_FromLastMirror(); } else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y))) { - int graphic = IMG_MM_DEFAULT_EXPLODING; - int graphic_phase = (phase / delay - 1); - Bitmap *bitmap; - int src_x, src_y; + int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING); + int frame = getGraphicAnimationFrameXY(graphic, x, y); - if (Store2[x][y] == EX_KETTLE) - { - if (graphic_phase < 3) - { - graphic = IMG_MM_KETTLE_EXPLODING; - } - else if (graphic_phase < 5) - { - graphic_phase += 3; - } - else - { - graphic = IMG_EMPTY; - graphic_phase = 0; - } - } - else if (Store2[x][y] == EX_SHORT) - { - if (graphic_phase < 4) - { - graphic_phase += 4; - } - else - { - graphic = IMG_EMPTY; - graphic_phase = 0; - } - } - - getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y); - - BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY, - cFX + x * TILEX, cFY + y * TILEY); + DrawGraphicAnimation_MM(x, y, graphic, frame); MarkTileDirty(x, y); } @@ -2521,67 +3166,48 @@ static void Explode_MM(int x, int y, int phase, int mode) static void Bang_MM(int x, int y) { int element = Tile[x][y]; - int mode = EX_NORMAL; - -#if 0 - DrawLaser(0, DL_LASER_ENABLED); -#endif - - switch (element) - { - case EL_KETTLE: - mode = EX_KETTLE; - break; - - case EL_GATE_STONE: - case EL_GATE_WOOD: - mode = EX_SHORT; - break; - - default: - mode = EX_NORMAL; - break; - } if (IS_PACMAN(element)) PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING); - else if (element == EL_BOMB || IS_MCDUFFIN(element)) + else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element)) PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING); else if (element == EL_KEY) PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING); else PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING); - Explode_MM(x, y, EX_PHASE_START, mode); + Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL); } -void TurnRound(int x, int y) +static void TurnRound(int x, int y) { static struct { int x, y; } move_xy[] = { - { 0, 0 }, - {-1, 0 }, - {+1, 0 }, - { 0, 0 }, - { 0, -1 }, - { 0, 0 }, { 0, 0 }, { 0, 0 }, - { 0, +1 } + { 0, 0 }, + { -1, 0 }, + { +1, 0 }, + { 0, 0 }, + { 0, -1 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, +1 } }; static struct { int left, right, back; } turn[] = { - { 0, 0, 0 }, + { 0, 0, 0 }, { MV_DOWN, MV_UP, MV_RIGHT }, - { MV_UP, MV_DOWN, MV_LEFT }, - { 0, 0, 0 }, - { MV_LEFT, MV_RIGHT, MV_DOWN }, - { 0,0,0 }, { 0,0,0 }, { 0,0,0 }, - { MV_RIGHT, MV_LEFT, MV_UP } + { MV_UP, MV_DOWN, MV_LEFT }, + { 0, 0, 0 }, + { MV_LEFT, MV_RIGHT, MV_DOWN }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { MV_RIGHT, MV_LEFT, MV_UP } }; int element = Tile[x][y]; @@ -2629,7 +3255,7 @@ static void StartMoving_MM(int x, int y) // now make next step - Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position + Moving2Blocked(x, y, &newx, &newy); // get next screen position if (element == EL_PACMAN && IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) && @@ -2709,8 +3335,7 @@ static void ContinueMoving_MM(int x, int y) boolean ClickElement(int x, int y, int button) { - static unsigned int click_delay = 0; - static int click_delay_value = CLICK_DELAY; + static DelayCounter click_delay = { CLICK_DELAY }; static boolean new_button = TRUE; boolean element_clicked = FALSE; int element; @@ -2718,8 +3343,8 @@ boolean ClickElement(int x, int y, int button) if (button == -1) { // initialize static variables - click_delay = 0; - click_delay_value = CLICK_DELAY; + click_delay.count = 0; + click_delay.value = CLICK_DELAY; new_button = TRUE; return FALSE; @@ -2732,7 +3357,7 @@ boolean ClickElement(int x, int y, int button) if (button == MB_RELEASED) { new_button = TRUE; - click_delay_value = CLICK_DELAY; + click_delay.value = CLICK_DELAY; // release eventually hold auto-rotating mirror RotateMirror(x, y, MB_RELEASED); @@ -2740,7 +3365,7 @@ boolean ClickElement(int x, int y, int button) return FALSE; } - if (!FrameReached(&click_delay, click_delay_value) && !new_button) + if (!FrameReached(&click_delay) && !new_button) return FALSE; if (button == MB_MIDDLEBUTTON) // middle button has no function @@ -2767,29 +3392,25 @@ boolean ClickElement(int x, int y, int button) } else if (IS_MCDUFFIN(element)) { - if (!laser.fuse_off) - { - DrawLaser(0, DL_LASER_DISABLED); + boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y); - /* - BackToFront(); - */ - } + if (has_laser && !laser.fuse_off) + DrawLaser(0, DL_LASER_DISABLED); element = get_rotated_element(element, BUTTON_ROTATION(button)); - laser.start_angle = get_element_angle(element); - - InitLaser(); Tile[x][y] = element; DrawField_MM(x, y); - /* - BackToFront(); - */ + if (has_laser) + { + laser.start_angle = get_element_angle(element); - if (!laser.fuse_off) - ScanLaser(); + InitLaser(); + + if (!laser.fuse_off) + ScanLaser(); + } element_clicked = TRUE; } @@ -2826,14 +3447,20 @@ boolean ClickElement(int x, int y, int button) element_clicked = TRUE; } + else if (IS_ENVELOPE(element)) + { + Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element); - click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY); + element_clicked = TRUE; + } + + click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY); new_button = FALSE; return element_clicked; } -void RotateMirror(int x, int y, int button) +static void RotateMirror(int x, int y, int button) { if (button == MB_RELEASED) { @@ -2908,8 +3535,6 @@ void RotateMirror(int x, int y, int button) IS_POLAR(Tile[x][y]) || IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY) { - check = 0; - if (IS_BEAMER(Tile[x][y])) { #if 0 @@ -2917,10 +3542,13 @@ void RotateMirror(int x, int y, int button) LX, LY, laser.beamer_edge, laser.beamer[1].num); #endif - laser.num_edges--; + if (check == 1) + laser.num_edges--; } ScanLaser(); + + check = 0; } if (check == 2) @@ -2932,7 +3560,7 @@ static void AutoRotateMirrors(void) { int x, y; - if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY)) + if (!FrameReached(&rotate_delay)) return; for (x = 0; x < lev_fieldx; x++) @@ -2949,12 +3577,16 @@ static void AutoRotateMirrors(void) IS_GRID_WOOD_AUTO(element) || IS_GRID_STEEL_AUTO(element) || element == EL_REFRACTOR) + { RotateMirror(x, y, MB_RIGHTBUTTON); + + laser.redraw = TRUE; + } } } } -boolean ObjHit(int obx, int oby, int bits) +static boolean ObjHit(int obx, int oby, int bits) { int i; @@ -2987,7 +3619,7 @@ boolean ObjHit(int obx, int oby, int bits) return FALSE; } -void DeletePacMan(int px, int py) +static void DeletePacMan(int px, int py) { int i, j; @@ -3013,51 +3645,7 @@ void DeletePacMan(int px, int py) } } -void ColorCycling(void) -{ - static int CC, Cc = 0; - - static int color, old = 0xF00, new = 0x010, mult = 1; - static unsigned short red, green, blue; - - if (color_status == STATIC_COLORS) - return; - - CC = FrameCounter; - - if (CC < Cc || CC > Cc + 2) - { - Cc = CC; - - color = old + new * mult; - if (mult > 0) - mult++; - else - mult--; - - if (ABS(mult) == 16) - { - mult =- mult / 16; - old = color; - new = new << 4; - - if (new > 0x100) - new = 0x001; - } - - red = 0x0e00 * ((color & 0xF00) >> 8); - green = 0x0e00 * ((color & 0x0F0) >> 4); - blue = 0x0e00 * ((color & 0x00F)); - SetRGB(pen_magicolor[0], red, green, blue); - - red = 0x1111 * ((color & 0xF00) >> 8); - green = 0x1111 * ((color & 0x0F0) >> 4); - blue = 0x1111 * ((color & 0x00F)); - SetRGB(pen_magicolor[1], red, green, blue); - } -} - -static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) +static void GameActions_MM_Ext(void) { int element; int x, y, i; @@ -3076,15 +3664,27 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) else if (IS_MOVING(x, y)) ContinueMoving_MM(x, y); else if (IS_EXPLODING(element)) - Explode_MM(x, y, Frame[x][y], EX_NORMAL); + Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL); else if (element == EL_EXIT_OPENING) OpenExit(x, y); else if (element == EL_GRAY_BALL_OPENING) - OpenSurpriseBall(x, y); - else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE) + OpenGrayBall(x, y); + else if (IS_ENVELOPE_OPENING(element)) + OpenEnvelope(x, y); + else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE) MeltIce(x, y); - else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA) + else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE) GrowAmoeba(x, y); + else if (IS_MIRROR(element) || + IS_MIRROR_FIXED(element) || + element == EL_PRISM) + DrawFieldTwinkle(x, y); + else if (element == EL_GRAY_BALL_ACTIVE || + element == EL_BOMB_ACTIVE || + element == EL_MINE_ACTIVE) + DrawFieldAnimated_MM(x, y); + else if (!IS_BLOCKED(x, y)) + DrawFieldAnimatedIfNeeded_MM(x, y); } AutoRotateMirrors(); @@ -3100,7 +3700,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) CT = FrameCounter; - if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY)) + if (game_mm.num_pacman && FrameReached(&pacman_delay)) { MovePacMen(); @@ -3111,61 +3711,25 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) } } - if (FrameReached(&energy_delay, ENERGY_DELAY)) - { - if (game_mm.energy_left > 0) - { - game_mm.energy_left--; - - redraw_mask |= REDRAW_DOOR_1; - } - else if (setup.time_limit && !game_mm.game_over) - { - int i; - - for (i = 15; i >= 0; i--) - { -#if 0 - SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale); -#endif - pen_ray = GetPixelFromRGB(window, - native_mm_level.laser_red * 0x11 * i, - native_mm_level.laser_green * 0x11 * i, - native_mm_level.laser_blue * 0x11 * i); - - DrawLaser(0, DL_LASER_ENABLED); - BackToFront(); - Delay_WithScreenUpdates(50); - } - - StopSound_MM(SND_MM_GAME_HEALTH_CHARGING); -#if 0 - FadeMusic(); -#endif + // skip all following game actions if game is over + if (game_mm.game_over) + return; - DrawLaser(0, DL_LASER_DISABLED); - game_mm.game_over = TRUE; - game_mm.game_over_cause = GAME_OVER_NO_ENERGY; + if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit) + { + FadeOutLaser(); - SetTileCursorActive(FALSE); + GameOver_MM(GAME_OVER_NO_ENERGY); - game.restart_game_message = "Out of magic energy! Play it again?"; + return; + } -#if 0 - if (Request("Out of magic energy! Play it again?", - REQ_ASK | REQ_STAY_CLOSED)) - { - InitGame(); - } - else - { - game_status = MAINMENU; - DrawMainMenu(); - } -#endif + if (FrameReached(&energy_delay)) + { + if (game_mm.energy_left > 0) + game_mm.energy_left--; - return; - } + // when out of energy, wait another frame to play "out of time" sound } element = laser.dest_element; @@ -3180,8 +3744,11 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) if (!laser.overloaded && laser.overload_value == 0 && element != EL_BOMB && + element != EL_BOMB_ACTIVE && element != EL_MINE && - element != EL_BALL_GRAY && + element != EL_MINE_ACTIVE && + element != EL_GRAY_BALL && + element != EL_GRAY_BALL_ACTIVE && element != EL_BLOCK_STONE && element != EL_BLOCK_WOOD && element != EL_FUSE_ON && @@ -3190,9 +3757,11 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) !IS_WALL_AMOEBA(element)) return; + overload_delay.value = HEALTH_DELAY(laser.overloaded); + if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) || (!laser.overloaded && laser.overload_value > 0)) && - FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded))) + FrameReached(&overload_delay)) { if (laser.overloaded) laser.overload_value++; @@ -3209,18 +3778,7 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) if (laser.overload_value < MAX_LASER_OVERLOAD - 8) { - int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD; - int color_down = 0xFF - color_up; - -#if 0 - SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000, - (15 - (laser.overload_value / 6)) * color_scale); -#endif - pen_ray = - GetPixelFromRGB(window, - (native_mm_level.laser_red ? 0xFF : color_up), - (native_mm_level.laser_green ? color_down : 0x00), - (native_mm_level.laser_blue ? color_down : 0x00)); + SetLaserColor(0xFF); DrawLaser(0, DL_LASER_ENABLED); } @@ -3232,70 +3790,13 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) else PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING); - if (laser.overloaded) - { -#if 0 - BlitBitmap(pix[PIX_DOOR], drawto, - DOOR_GFX_PAGEX4 + XX_OVERLOAD, - DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE - - laser.overload_value, - OVERLOAD_XSIZE, laser.overload_value, - DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE - - laser.overload_value); -#endif - redraw_mask |= REDRAW_DOOR_1; - } - else - { -#if 0 - BlitBitmap(pix[PIX_DOOR], drawto, - DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD, - OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value, - DX_OVERLOAD, DY_OVERLOAD); -#endif - redraw_mask |= REDRAW_DOOR_1; - } - if (laser.overload_value == MAX_LASER_OVERLOAD) { - int i; - UpdateAndDisplayGameControlValues(); - for (i = 15; i >= 0; i--) - { -#if 0 - SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000); -#endif + FadeOutLaser(); - pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00); - - DrawLaser(0, DL_LASER_ENABLED); - BackToFront(); - Delay_WithScreenUpdates(50); - } - - DrawLaser(0, DL_LASER_DISABLED); - - game_mm.game_over = TRUE; - game_mm.game_over_cause = GAME_OVER_OVERLOADED; - - SetTileCursorActive(FALSE); - - game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?"; - -#if 0 - if (Request("Magic spell hit Mc Duffin! Play it again?", - REQ_ASK | REQ_STAY_CLOSED)) - { - InitGame(); - } - else - { - game_status = MAINMENU; - DrawMainMenu(); - } -#endif + GameOver_MM(GAME_OVER_OVERLOADED); return; } @@ -3311,36 +3812,10 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) if (game_mm.cheat_no_explosion) return; -#if 0 - laser.num_damages--; - DrawLaser(0, DL_LASER_DISABLED); - laser.num_edges = 0; -#endif - Bang_MM(ELX, ELY); laser.dest_element = EL_EXPLODING_OPAQUE; -#if 0 - Bang_MM(ELX, ELY); - laser.num_damages--; - DrawLaser(0, DL_LASER_DISABLED); - - laser.num_edges = 0; - Bang_MM(laser.start_edge.x, laser.start_edge.y); - - if (Request("Bomb killed Mc Duffin! Play it again?", - REQ_ASK | REQ_STAY_CLOSED)) - { - InitGame(); - } - else - { - game_status = MAINMENU; - DrawMainMenu(); - } -#endif - return; } @@ -3354,117 +3829,50 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE); } - if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball) + if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball) { - static int new_elements[] = + if (!Store2[ELX][ELY]) // check if content element not yet determined { - EL_MIRROR_START, - EL_MIRROR_FIXED_START, - EL_POLAR_START, - EL_POLAR_CROSS_START, - EL_PACMAN_START, - EL_KETTLE, - EL_BOMB, - EL_PRISM - }; - int num_new_elements = sizeof(new_elements) / sizeof(int); - int new_element = new_elements[RND(num_new_elements)]; - - Store[ELX][ELY] = new_element + RND(get_num_elements(new_element)); - Tile[ELX][ELY] = EL_GRAY_BALL_OPENING; - - // !!! CHECK AGAIN: Laser on Polarizer !!! - ScanLaser(); + int last_anim_random_frame = gfx.anim_random_frame; + int element_pos; - return; + if (native_mm_level.ball_choice_mode == ANIM_RANDOM) + gfx.anim_random_frame = RND(native_mm_level.num_ball_contents); -#if 0 - int graphic; + element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1, + native_mm_level.ball_choice_mode, 0, + game_mm.ball_choice_pos); - switch (RND(5)) - { - case 0: - element = EL_MIRROR_START + RND(16); - break; - case 1: - { - int rnd = RND(3); + if (native_mm_level.ball_choice_mode == ANIM_RANDOM) + gfx.anim_random_frame = last_anim_random_frame; - element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM); - } - break; - default: - { - int rnd = RND(3); + game_mm.ball_choice_pos++; - element = (rnd == 0 ? EL_FUSE_ON : - rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 : - rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 : - rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 : - EL_MIRROR_FIXED_START + rnd - 25); - } - break; - } - - graphic = el2gfx(element); + int new_element = native_mm_level.ball_content[element_pos]; + int new_element_base = map_wall_to_base_element(new_element); - for (i = 0; i < 50; i++) - { - int x = RND(26); - int y = RND(26); - -#if 0 - BlitBitmap(pix[PIX_BACK], drawto, - SX + (graphic % GFX_PER_LINE) * TILEX + x, - SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6, - SX + ELX * TILEX + x, - SY + ELY * TILEY + y); -#endif - MarkTileDirty(ELX, ELY); - BackToFront(); - - DrawLaser(0, DL_LASER_ENABLED); + if (IS_WALL(new_element_base)) + { + // always use completely filled wall element + new_element = new_element_base | 0x000f; + } + else if (native_mm_level.rotate_ball_content && + get_num_elements(new_element) > 1) + { + // randomly rotate newly created game element + new_element = get_rotated_element(new_element, RND(16)); + } - Delay_WithScreenUpdates(50); + Store[ELX][ELY] = new_element; + Store2[ELX][ELY] = TRUE; } - Tile[ELX][ELY] = element; - DrawField_MM(ELX, ELY); - -#if 0 - Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY); -#endif - - // above stuff: GRAY BALL -> PRISM !!! -/* - LX = ELX * TILEX + 14; - LY = ELY * TILEY + 14; - if (laser.current_angle == (laser.current_angle >> 1) << 1) - OK = 8; - else - OK = 4; - LX -= OK * XS; - LY -= OK * YS; - - laser.num_edges -= 2; - laser.num_damages--; -*/ - -#if 0 - for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--) - if (laser.damage[i].is_mirror) - break; - - if (i > 0) - DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED); + if (native_mm_level.explode_ball) + Bang_MM(ELX, ELY); else - DrawLaser(0, DL_LASER_DISABLED); -#else - DrawLaser(0, DL_LASER_DISABLED); -#endif + Tile[ELX][ELY] = EL_GRAY_BALL_OPENING; - ScanLaser(); -#endif + laser.dest_element = laser.dest_element_last = Tile[ELX][ELY]; return; } @@ -3473,57 +3881,18 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) { PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING); - { - Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING; - Store[ELX][ELY] = EL_WALL_ICE; - Store2[ELX][ELY] = laser.wall_mask; - - laser.dest_element = Tile[ELX][ELY]; + Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE; + Store[ELX][ELY] = EL_WALL_ICE_BASE; + Store2[ELX][ELY] = laser.wall_mask; - return; - } - - for (i = 0; i < 5; i++) - { - int phase = i + 1; - - if (i == 4) - { - Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF); - phase = 0; - } - - DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask); - BackToFront(); - Delay_WithScreenUpdates(100); - } - - if (Tile[ELX][ELY] == EL_WALL_ICE) - Tile[ELX][ELY] = EL_EMPTY; - -/* - laser.num_edges--; - LX = laser.edge[laser.num_edges].x - cSX2; - LY = laser.edge[laser.num_edges].y - cSY2; -*/ - - for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--) - if (laser.damage[i].is_mirror) - break; - - if (i > 0) - DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED); - else - DrawLaser(0, DL_LASER_DISABLED); - - ScanLaser(); + laser.dest_element = Tile[ELX][ELY]; return; } if (IS_WALL_AMOEBA(element) && CT > 60) { - int k1, k2, k3, dx, dy, de, dm; + int k1, k2, k3; int element2 = Tile[ELX][ELY]; if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element)) @@ -3594,44 +3963,17 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) Tile[ELX][ELY] = element | laser.wall_mask; - dx = ELX; - dy = ELY; - de = Tile[ELX][ELY]; - dm = laser.wall_mask; - -#if 1 - { - int x = ELX, y = ELY; - int wall_mask = laser.wall_mask; + int x = ELX, y = ELY; + int wall_mask = laser.wall_mask; - ScanLaser(); - DrawLaser(0, DL_LASER_ENABLED); - - PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING); - - Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING; - Store[x][y] = EL_WALL_AMOEBA; - Store2[x][y] = wall_mask; - - return; - } -#endif - - DrawWallsAnimation_MM(dx, dy, de, 4, dm); ScanLaser(); DrawLaser(0, DL_LASER_ENABLED); - PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING); + PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING); - for (i = 4; i >= 0; i--) - { - DrawWallsAnimation_MM(dx, dy, de, i, dm); - - BackToFront(); - Delay_WithScreenUpdates(20); - } - - DrawLaser(0, DL_LASER_ENABLED); + Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE; + Store[x][y] = EL_WALL_AMOEBA_BASE; + Store2[x][y] = wall_mask; return; } @@ -3696,45 +4038,46 @@ static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode) if (element == EL_FUEL_FULL && CT > 10) { - for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2) + int num_init_game_frames = INIT_GAME_ACTIONS_DELAY; + int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time; + + for (i = start; i <= num_init_game_frames; i++) { -#if 0 - BlitBitmap(pix[PIX_DOOR], drawto, - DOOR_GFX_PAGEX4 + XX_ENERGY, - DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i, - ENERGY_XSIZE, i, DX_ENERGY, - DY_ENERGY + ENERGY_YSIZE - i); -#endif + if (i == num_init_game_frames) + StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING); + else if (setup.sound_loops) + PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING); + else + PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING); - redraw_mask |= REDRAW_DOOR_1; - BackToFront(); + game_mm.energy_left = native_mm_level.time * i / num_init_game_frames; + + UpdateAndDisplayGameControlValues(); - Delay_WithScreenUpdates(20); + BackToFront_MM(); } - game_mm.energy_left = MAX_LASER_ENERGY; - Tile[ELX][ELY] = EL_FUEL_EMPTY; + Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY; + DrawField_MM(ELX, ELY); DrawLaser(0, DL_LASER_ENABLED); return; } - - return; } -void GameActions_MM(struct MouseActionInfo action, boolean warp_mode) +void GameActions_MM(struct MouseActionInfo action) { boolean element_clicked = ClickElement(action.lx, action.ly, action.button); boolean button_released = (action.button == MB_RELEASED); - GameActions_MM_Ext(action, warp_mode); + GameActions_MM_Ext(); CheckSingleStepMode_MM(element_clicked, button_released); } -void MovePacMen(void) +static void MovePacMen(void) { int mx, my, ox, oy, nx, ny; int element; @@ -3810,7 +4153,7 @@ void MovePacMen(void) } DrawField_MM(nx, ny); - BackToFront(); + BackToFront_MM(); if (!laser.fuse_off) { @@ -3839,182 +4182,6 @@ void MovePacMen(void) } } -void GameWon_MM(void) -{ - int hi_pos; - boolean raise_level = FALSE; - -#if 0 - if (local_player->MovPos) - return; - - local_player->LevelSolved = FALSE; -#endif - - if (game_mm.energy_left) - { - if (setup.sound_loops) - PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, - SND_CTRL_PLAY_LOOP); - - while (game_mm.energy_left > 0) - { - if (!setup.sound_loops) - PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT); - - /* - if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10)) - RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]); - */ - - RaiseScore_MM(5); - - game_mm.energy_left--; - if (game_mm.energy_left >= 0) - { -#if 0 - BlitBitmap(pix[PIX_DOOR], drawto, - DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY, - ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left, - DX_ENERGY, DY_ENERGY); -#endif - redraw_mask |= REDRAW_DOOR_1; - } - - BackToFront(); - Delay_WithScreenUpdates(10); - } - - if (setup.sound_loops) - StopSound(SND_SIRR); - } - else if (native_mm_level.time == 0) // level without time limit - { - if (setup.sound_loops) - PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, - SND_CTRL_PLAY_LOOP); - - while (TimePlayed < 999) - { - if (!setup.sound_loops) - PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT); - if (TimePlayed < 999 && !(TimePlayed % 10)) - RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]); - if (TimePlayed < 900 && !(TimePlayed % 10)) - TimePlayed += 10; - else - TimePlayed++; - - /* - DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2); - */ - - BackToFront(); - Delay_WithScreenUpdates(10); - } - - if (setup.sound_loops) - StopSound(SND_SIRR); - } - - CloseDoor(DOOR_CLOSE_1); - - Request("Level solved!", REQ_CONFIRM); - - if (level_nr == leveldir_current->handicap_level) - { - leveldir_current->handicap_level++; - SaveLevelSetup_SeriesInfo(); - } - - if (level_editor_test_game) - game_mm.score = -1; // no highscore when playing from editor - else if (level_nr < leveldir_current->last_level) - raise_level = TRUE; // advance to next level - - if ((hi_pos = NewHiScore_MM()) >= 0) - { - game_status = HALLOFFAME; - - // DrawHallOfFame(hi_pos); - - if (raise_level) - level_nr++; - } - else - { - game_status = MAINMENU; - - if (raise_level) - level_nr++; - - // DrawMainMenu(); - } - - BackToFront(); -} - -int NewHiScore_MM(void) -{ - int k, l; - int position = -1; - - // LoadScore(level_nr); - - if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 || - game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score) - return -1; - - for (k = 0; k < MAX_SCORE_ENTRIES; k++) - { - if (game_mm.score > highscore[k].Score) - { - // player has made it to the hall of fame - - if (k < MAX_SCORE_ENTRIES - 1) - { - int m = MAX_SCORE_ENTRIES - 1; - -#ifdef ONE_PER_NAME - for (l = k; l < MAX_SCORE_ENTRIES; l++) - if (!strcmp(setup.player_name, highscore[l].Name)) - m = l; - if (m == k) // player's new highscore overwrites his old one - goto put_into_list; -#endif - - for (l = m; l>k; l--) - { - strcpy(highscore[l].Name, highscore[l - 1].Name); - highscore[l].Score = highscore[l - 1].Score; - } - } - -#ifdef ONE_PER_NAME - put_into_list: -#endif - strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN); - highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0'; - highscore[k].Score = game_mm.score; - position = k; - - break; - } - -#ifdef ONE_PER_NAME - else if (!strncmp(setup.player_name, highscore[k].Name, - MAX_PLAYER_NAME_LEN)) - break; // player already there with a higher score -#endif - - } - - // if (position >= 0) - // SaveScore(level_nr); - - return position; -} - static void InitMovingField_MM(int x, int y, int direction) { int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); @@ -4027,35 +4194,6 @@ static void InitMovingField_MM(int x, int y, int direction) Tile[newx][newy] = EL_BLOCKED; } -static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y) -{ - int direction = MovDir[x][y]; - int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); - int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); - - *goes_to_x = newx; - *goes_to_y = newy; -} - -static void Blocked2Moving_MM(int x, int y, - int *comes_from_x, int *comes_from_y) -{ - int oldx = x, oldy = y; - int direction = MovDir[x][y]; - - if (direction == MV_LEFT) - oldx++; - else if (direction == MV_RIGHT) - oldx--; - else if (direction == MV_UP) - oldy++; - else if (direction == MV_DOWN) - oldy--; - - *comes_from_x = oldx; - *comes_from_y = oldy; -} - static int MovingOrBlocked2Element_MM(int x, int y) { int element = Tile[x][y]; @@ -4064,7 +4202,7 @@ static int MovingOrBlocked2Element_MM(int x, int y) { int oldx, oldy; - Blocked2Moving_MM(x, y, &oldx, &oldy); + Blocked2Moving(x, y, &oldx, &oldy); return Tile[oldx][oldy]; } @@ -4072,16 +4210,6 @@ static int MovingOrBlocked2Element_MM(int x, int y) return element; } -#if 0 -static void RemoveField(int x, int y) -{ - Tile[x][y] = EL_EMPTY; - MovPos[x][y] = 0; - MovDir[x][y] = 0; - MovDelay[x][y] = 0; -} -#endif - static void RemoveMovingField_MM(int x, int y) { int oldx = x, oldy = y, newx = x, newy = y; @@ -4091,13 +4219,13 @@ static void RemoveMovingField_MM(int x, int y) if (IS_MOVING(x, y)) { - Moving2Blocked_MM(x, y, &newx, &newy); + Moving2Blocked(x, y, &newx, &newy); if (Tile[newx][newy] != EL_BLOCKED) return; } else if (Tile[x][y] == EL_BLOCKED) { - Blocked2Moving_MM(x, y, &oldx, &oldy); + Blocked2Moving(x, y, &oldx, &oldy); if (!IS_MOVING(oldx, oldy)) return; } @@ -4111,44 +4239,6 @@ static void RemoveMovingField_MM(int x, int y) DrawLevelField_MM(newx, newy); } -void PlaySoundLevel(int x, int y, int sound_nr) -{ - int sx = SCREENX(x), sy = SCREENY(y); - int volume, stereo; - int silence_distance = 8; - - if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) || - (!setup.sound_loops && IS_LOOP_SOUND(sound_nr))) - return; - - if (!IN_LEV_FIELD(x, y) || - sx < -silence_distance || sx >= SCR_FIELDX+silence_distance || - sy < -silence_distance || sy >= SCR_FIELDY+silence_distance) - return; - - volume = SOUND_MAX_VOLUME; - -#ifndef MSDOS - stereo = (sx - SCR_FIELDX/2) * 12; -#else - stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5; - if (stereo > SOUND_MAX_RIGHT) - stereo = SOUND_MAX_RIGHT; - if (stereo < SOUND_MAX_LEFT) - stereo = SOUND_MAX_LEFT; -#endif - - if (!IN_SCR_FIELD(sx, sy)) - { - int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2; - int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2; - - volume -= volume * (dx > dy ? dx : dy) / silence_distance; - } - - PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND); -} - static void RaiseScore_MM(int value) { game_mm.score += value; @@ -4189,7 +4279,7 @@ void RaiseScoreElement_MM(int element) // Mirror Magic game engine snapshot handling functions // ---------------------------------------------------------------------------- -void SaveEngineSnapshotValues_MM(ListNode **buffers) +void SaveEngineSnapshotValues_MM(void) { int x, y; @@ -4204,7 +4294,6 @@ void SaveEngineSnapshotValues_MM(ListNode **buffers) engine_snapshot_mm.Hit[x][y] = Hit[x][y]; engine_snapshot_mm.Box[x][y] = Box[x][y]; engine_snapshot_mm.Angle[x][y] = Angle[x][y]; - engine_snapshot_mm.Frame[x][y] = Frame[x][y]; } } @@ -4247,7 +4336,6 @@ void LoadEngineSnapshotValues_MM(void) Hit[x][y] = engine_snapshot_mm.Hit[x][y]; Box[x][y] = engine_snapshot_mm.Box[x][y]; Angle[x][y] = engine_snapshot_mm.Angle[x][y]; - Frame[x][y] = engine_snapshot_mm.Frame[x][y]; } } diff --git a/src/game_mm/mm_game.h b/src/game_mm/mm_game.h index f01f3fe1..e499cefa 100644 --- a/src/game_mm/mm_game.h +++ b/src/game_mm/mm_game.h @@ -14,31 +14,4 @@ #include "main_mm.h" - -void GameWon_MM(void); -int NewHiScore_MM(void); - -void TurnRound(int, int); - -void PlaySoundLevel(int, int, int); - -void AddLaserEdge(int, int); -void AddDamagedField(int, int); -void ScanLaser(void); -void DrawLaser(int, int); -boolean HitElement(int, int); -boolean HitOnlyAnEdge(int, int); -boolean HitPolarizer(int, int); -boolean HitBlock(int, int); -boolean HitLaserSource(int, int); -boolean HitLaserDestination(int, int); -boolean HitReflectingWalls(int, int); -boolean HitAbsorbingWalls(int, int); -void RotateMirror(int, int, int); -boolean ObjHit(int, int, int); -void DeletePacMan(int, int); - -void ColorCycling(void); -void MovePacMen(void); - #endif diff --git a/src/game_mm/mm_init.c b/src/game_mm/mm_init.c index 0fe4c9fc..b7dcbe35 100644 --- a/src/game_mm/mm_init.c +++ b/src/game_mm/mm_init.c @@ -14,9 +14,16 @@ #include "mm_main.h" +Bitmap *drawto_mm; + struct EngineSnapshotInfo_MM engine_snapshot_mm; +void InitGfxBuffers_MM(void) +{ + ReCreateBitmap(&drawto_mm, video.width, video.height); +} + unsigned int InitEngineRandom_MM(int seed) { return InitEngineRandom(seed); @@ -24,7 +31,7 @@ unsigned int InitEngineRandom_MM(int seed) void InitElementProperties_MM(void) { - int i,j; + int i, j; static int ep_grid[] = { @@ -171,6 +178,15 @@ void InitElementProperties_MM(void) }; static int ep_pacman_num = sizeof(ep_pacman) / sizeof(int); + static int ep_envelope[] = + { + EL_ENVELOPE_1, + EL_ENVELOPE_2, + EL_ENVELOPE_3, + EL_ENVELOPE_4, + }; + static int ep_envelope_num = sizeof(ep_envelope) / sizeof(int); + static long ep_bit[] = { EP_BIT_GRID, @@ -186,6 +202,7 @@ void InitElementProperties_MM(void) EP_BIT_INACTIVE, EP_BIT_WALL, EP_BIT_PACMAN, + EP_BIT_ENVELOPE, }; static int *ep_array[] = { @@ -202,6 +219,7 @@ void InitElementProperties_MM(void) ep_inactive, ep_wall, ep_pacman, + ep_envelope, }; static int *ep_num[] = { @@ -218,6 +236,7 @@ void InitElementProperties_MM(void) &ep_inactive_num, &ep_wall_num, &ep_pacman_num, + &ep_envelope_num, }; static int num_properties = sizeof(ep_num) / sizeof(int *); @@ -239,7 +258,3 @@ void mm_open_all(void) { InitElementProperties_MM(); } - -void mm_close_all(void) -{ -} diff --git a/src/game_mm/mm_main.c b/src/game_mm/mm_main.c index 77f7e3a2..048d8fd3 100644 --- a/src/game_mm/mm_main.c +++ b/src/game_mm/mm_main.c @@ -28,8 +28,10 @@ unsigned int Elementeigenschaften[MAX_ELEMENTS]; struct LaserInfo laser; -short LX,LY, XS,YS, ELX,ELY; -short CT,Ct; +short LX, LY; +short XS, YS; +short ELX, ELY; +short CT, Ct; int dSX, dSY; int cSX, cSY; diff --git a/src/game_mm/mm_main.h b/src/game_mm/mm_main.h index 1beb5074..684647ce 100644 --- a/src/game_mm/mm_main.h +++ b/src/game_mm/mm_main.h @@ -48,6 +48,7 @@ #define EP_BIT_INACTIVE (1 << 11) #define EP_BIT_WALL (1 << 12) #define EP_BIT_PACMAN (1 << 13) +#define EP_BIT_ENVELOPE (1 << 14) #define IS_GRID(e) (Elementeigenschaften[e] & EP_BIT_GRID) #define IS_MCDUFFIN(e) (Elementeigenschaften[e] & EP_BIT_MCDUFFIN) @@ -63,6 +64,7 @@ #define IS_INACTIVE(e) (Elementeigenschaften[e] & EP_BIT_INACTIVE) #define IS_MM_WALL(e) (Elementeigenschaften[e] & EP_BIT_WALL) #define IS_PACMAN(e) (Elementeigenschaften[e] & EP_BIT_PACMAN) +#define IS_ENVELOPE(e) (Elementeigenschaften[e] & EP_BIT_ENVELOPE) #define IS_WALL_STEEL(e) ((e) >= EL_WALL_STEEL_START && \ (e) <= EL_WALL_STEEL_END) @@ -101,6 +103,10 @@ (e) <= EL_DF_MIRROR_END) #define IS_DF_MIRROR_AUTO(e) ((e) >= EL_DF_MIRROR_AUTO_START && \ (e) <= EL_DF_MIRROR_AUTO_END) +#define IS_DF_MIRROR_FIXED(e) ((e) >= EL_DF_MIRROR_FIXED_START && \ + (e) <= EL_DF_MIRROR_FIXED_END) +#define IS_DF_SLOPE(e) ((e) >= EL_DF_SLOPE_START && \ + (e) <= EL_DF_SLOPE_END) #define IS_LASER(e) ((e) >= EL_LASER_START && \ (e) <= EL_LASER_END) #define IS_RECEIVER(e) ((e) >= EL_RECEIVER_START && \ @@ -122,32 +128,29 @@ (e) == EL_BOMB || \ IS_WALL_AMOEBA(e)) -#define CAN_MOVE(e) ((e) == EL_PACMAN) -#define IS_FREE(x,y) (Tile[x][y] == EL_EMPTY) +#define IS_ABSORBING_BLOCK(e) (IS_WALL_WOOD(e) || \ + IS_DF_WALL_WOOD(e) || \ + (e) == EL_BLOCK_WOOD || \ + (e) == EL_GATE_WOOD || \ + (e) == EL_EXIT_CLOSED || \ + (e) == EL_EXIT_OPEN) -#define IS_MOVING(x,y) (MovPos[x][y] != 0) -#define IS_BLOCKED(x,y) (Tile[x][y] == EL_BLOCKED) -#define IS_DRAWABLE(e) ((e) < EL_BLOCKED) -#define IS_NOT_DRAWABLE(e) ((e) >= EL_BLOCKED) +#define IS_ENVELOPE_OPENING(e) ((e) >= EL_ENVELOPE_OPENING_START && \ + (e) <= EL_ENVELOPE_OPENING_END) -#define PLAYERINFO(x,y) (&stored_player[StorePlayer[x][y]-EL_SPIELER1]) +#define ENVELOPE_NR(e) ((e) - EL_ENVELOPE_1) +#define ENVELOPE_OPENING_NR(e) ((e) - EL_ENVELOPE_1_OPENING) -#define WALL_BASE(e) ((e) & 0xfff0) -#define WALL_BITS(e) ((e) & 0x000f) +#define CAN_MOVE(e) ((e) == EL_PACMAN) +#define IS_FREE(x, y) (Tile[x][y] == EL_EMPTY) -// Bitmaps with graphic file -#define PIX_BACK 0 -#define PIX_DOOR 1 -#define PIX_TOONS 2 -#define PIX_DF 3 -#define PIX_BIGFONT 4 -#define PIX_SMALLFONT 5 -#define PIX_MEDIUMFONT 6 -// Bitmaps without graphic file -#define PIX_DB_DOOR 7 +#define IS_MOVING(x, y) (MovPos[x][y] != 0) +#define IS_BLOCKED(x, y) (Tile[x][y] == EL_BLOCKED) +#define IS_DRAWABLE(e) ((e) < EL_BLOCKED) +#define IS_NOT_DRAWABLE(e) ((e) >= EL_BLOCKED) -#define NUM_PICTURES 7 -#define NUM_BITMAPS 8 +#define WALL_BASE(e) ((e) & 0xfff0) +#define WALL_BITS(e) ((e) & 0x000f) // boundaries of arrays etc. #define MAX_PLAYER_NAME_LEN 10 @@ -180,13 +183,8 @@ #define LEVEL_SCORE_ELEMENTS 16 // level elements with score -struct HiScore_MM -{ - char Name[MAX_PLAYER_NAME_LEN + 1]; - int Score; -}; - -extern DrawBuffer *drawto_field; +extern DrawBuffer *drawto_mm; +extern DrawBuffer *bitmap_db_field; extern int game_status; extern boolean level_editor_test_game; @@ -209,7 +207,13 @@ extern short StorePlayer[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern short Frame[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern boolean Stop[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern short AmoebaNr[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; -extern short AmoebaCnt[MAX_NUM_AMOEBA], AmoebaCnt2[MAX_NUM_AMOEBA]; +extern short AmoebaCnt[MAX_NUM_AMOEBA]; +extern short AmoebaCnt2[MAX_NUM_AMOEBA]; +extern short ExplodePhase[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; + +extern int GfxFrame[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; +extern int GfxElement[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; + extern unsigned int Elementeigenschaften[MAX_ELEMENTS]; extern int level_nr; @@ -225,7 +229,6 @@ extern int SBY_Upper, SBY_Lower; extern int TimeFrames, TimePlayed, TimeLeft; extern struct LevelInfo_MM native_mm_level; -extern struct HiScore_MM highscore[]; extern struct GameInfo_MM game_mm; extern struct LaserInfo laser; @@ -246,61 +249,6 @@ extern short Sign[16]; extern char *element_info[]; extern int num_element_info; -// often used screen positions -#define DX 534 -#define DY 60 -#define EX DX -#define EY (DY + 178) -#define TILEX TILESIZE -#define TILEY TILESIZE -#define MINI_TILESIZE (TILESIZE / 2) -#define MINI_TILEX (TILEX / 2) -#define MINI_TILEY (TILEY / 2) -#define MICRO_TILEX (TILEX / 4) -#define MICRO_TILEY (TILEY / 4) -#define MICRO_WALLX (TILEX / 8) -#define MICRO_WALLY (TILEY / 8) -#define MIDPOSX (SCR_FIELDX / 2) -#define MIDPOSY (SCR_FIELDY / 2) -#define DXSIZE 100 -#define DYSIZE 280 -#define VXSIZE DXSIZE -#define VYSIZE 100 -#define EXSIZE DXSIZE -#define EYSIZE (VXSIZE + 44) -#define FULL_SXSIZE (2 + SXSIZE + 2) -#define FULL_SYSIZE (2 + SYSIZE + 2) -#define MICROLEV_XPOS (SX + 12 * TILEX) -#define MICROLEV_YPOS (SY + 6 * TILEY) -#define MICROLEV_XSIZE (STD_LEV_FIELDX * MICRO_TILEX) -#define MICROLEV_YSIZE (STD_LEV_FIELDY * MICRO_TILEY) -#define MICROLABEL_XPOS (SY) -#define MICROLABEL_YPOS (SY + 352) -#define MICROLABEL_XSIZE (SXSIZE) -#define MICROLABEL_YSIZE (FONT4_YSIZE) - -#define GFX_STARTX SX -#define GFX_STARTY SY -#define MINI_GFX_STARTX (SX + 8 * TILEX) -#define MINI_GFX_STARTY (SY + 6 * TILEY) -#define MICRO_GFX_STARTX (MINI_GFX_STARTX + 8 * MINI_TILEX) -#define MICRO_GFX_STARTY (MINI_GFX_STARTY + 6 * MINI_TILEY) -#define GFX_PER_LINE 16 -#define MINI_GFX_PER_LINE GFX_PER_LINE -#define MICRO_GFX_PER_LINE GFX_PER_LINE - -#define MINI_DF_STARTX (8 * TILEX) -#define MINI_DF_STARTY (8 * TILEY) -#define MICRO_DF_STARTX (MINI_DF_STARTX + 8 * MINI_TILEX) -#define MICRO_DF_STARTY (MINI_DF_STARTY + 8 * MINI_TILEY) -#define DF_PER_LINE 16 -#define MINI_DF_PER_LINE DF_PER_LINE -#define MICRO_DF_PER_LINE DF_PER_LINE - -#define MICRO_FONT_STARTX (MICRO_DF_STARTX + 8 * MICRO_TILEX) -#define MICRO_FONT_STARTY (MICRO_DF_STARTY + 8 * MICRO_TILEY) -#define MICRO_FONT_PER_LINE 8 - // wall positions (that can be OR'ed together) #define WALL_TOPLEFT 1 #define WALL_TOPRIGHT 2 @@ -357,31 +305,22 @@ extern int num_element_info; #define EL_KETTLE 29 #define EL_BOMB 30 #define EL_PRISM 31 -#define EL_WALL_START 32 -#define EL_WALL_EMPTY EL_WALL_START -#define EL_WALL_00 EL_WALL_START -#define EL_WALL_STEEL EL_WALL_00 -#define EL_WALL_STEEL_START EL_WALL_00 -#define EL_WALL_15 47 -#define EL_WALL_STEEL_END EL_WALL_15 -#define EL_WALL_16 48 -#define EL_WALL_WOOD EL_WALL_16 -#define EL_WALL_WOOD_START EL_WALL_16 -#define EL_WALL_31 63 -#define EL_WALL_WOOD_END EL_WALL_31 -#define EL_WALL_32 64 -#define EL_WALL_ICE EL_WALL_32 -#define EL_WALL_ICE_START EL_WALL_32 -#define EL_WALL_47 79 -#define EL_WALL_ICE_END EL_WALL_47 -#define EL_WALL_48 80 -#define EL_WALL_AMOEBA EL_WALL_48 -#define EL_WALL_AMOEBA_START EL_WALL_48 -#define EL_WALL_63 95 -#define EL_WALL_AMOEBA_END EL_WALL_63 -#define EL_WALL_END EL_WALL_63 +#define EL_WALL_START EL_WALL_STEEL_START +#define EL_WALL_STEEL_BASE 32 +#define EL_WALL_STEEL_START (EL_WALL_STEEL_BASE + 0) +#define EL_WALL_STEEL_END (EL_WALL_STEEL_BASE + 15) +#define EL_WALL_WOOD_BASE 48 +#define EL_WALL_WOOD_START (EL_WALL_WOOD_BASE + 0) +#define EL_WALL_WOOD_END (EL_WALL_WOOD_BASE + 15) +#define EL_WALL_ICE_BASE 64 +#define EL_WALL_ICE_START (EL_WALL_ICE_BASE + 0) +#define EL_WALL_ICE_END (EL_WALL_ICE_BASE + 15) +#define EL_WALL_AMOEBA_BASE 80 +#define EL_WALL_AMOEBA_START (EL_WALL_AMOEBA_BASE + 0) +#define EL_WALL_AMOEBA_END (EL_WALL_AMOEBA_BASE + 15) +#define EL_WALL_END EL_WALL_AMOEBA_END #define EL_BLOCK_WOOD 96 -#define EL_BALL_GRAY 97 +#define EL_GRAY_BALL 97 #define EL_BEAMER_START 98 #define EL_BEAMER_00 (EL_BEAMER_START + 0) #define EL_BEAMER_01 (EL_BEAMER_START + 1) @@ -452,8 +391,12 @@ extern int num_element_info; #define EL_GRID_WOOD_03 (EL_GRID_WOOD_START + 3) #define EL_GRID_WOOD_END EL_GRID_WOOD_03 #define EL_FUEL_EMPTY 155 +#define EL_ENVELOPE_1 156 +#define EL_ENVELOPE_2 157 +#define EL_ENVELOPE_3 158 +#define EL_ENVELOPE_4 159 -#define EL_MM_END_1 155 +#define EL_MM_END_1 159 #define EL_CHAR_START 160 #define EL_CHAR_ASCII0 (EL_CHAR_START - 32) @@ -540,14 +483,15 @@ extern int num_element_info; #define EL_GRID_STEEL_FIXED_07 (EL_GRID_STEEL_FIXED_START + 7) // 157.5° #define EL_GRID_STEEL_FIXED_END EL_GRID_STEEL_FIXED_07 -#define EL_DF_WALL_WOOD 272 -#define EL_DF_WALL_START EL_DF_WALL_WOOD_START -#define EL_DF_WALL_WOOD_START (EL_DF_WALL_WOOD + 0) -#define EL_DF_WALL_WOOD_END (EL_DF_WALL_WOOD + 15) +#define EL_DF_WALL_WOOD_BASE 272 +#define EL_DF_WALL_WOOD_START (EL_DF_WALL_WOOD_BASE + 0) +#define EL_DF_WALL_WOOD_END (EL_DF_WALL_WOOD_BASE + 15) -#define EL_DF_WALL_STEEL 288 -#define EL_DF_WALL_STEEL_START (EL_DF_WALL_STEEL + 0) -#define EL_DF_WALL_STEEL_END (EL_DF_WALL_STEEL + 15) +#define EL_DF_WALL_STEEL_BASE 288 +#define EL_DF_WALL_STEEL_START (EL_DF_WALL_STEEL_BASE + 0) +#define EL_DF_WALL_STEEL_END (EL_DF_WALL_STEEL_BASE + 15) + +#define EL_DF_WALL_START EL_DF_WALL_WOOD_START #define EL_DF_WALL_END EL_DF_WALL_STEEL_END #define EL_DF_EMPTY 304 @@ -636,28 +580,64 @@ extern int num_element_info; #define EL_MCDUFFIN 420 #define EL_PACMAN 421 #define EL_FUSE_OFF 422 -#define EL_STEEL_WALL 423 -#define EL_WOODEN_WALL 424 -#define EL_ICE_WALL 425 -#define EL_AMOEBA_WALL 426 +#define EL_WALL_STEEL 423 +#define EL_WALL_WOOD 424 +#define EL_WALL_ICE 425 +#define EL_WALL_AMOEBA 426 #define EL_LASER 427 #define EL_RECEIVER 428 -#define EL_DF_STEEL_WALL 429 -#define EL_DF_WOODEN_WALL 430 - -#define EL_MM_END_2 430 +#define EL_DF_WALL_STEEL 429 +#define EL_DF_WALL_WOOD 430 + +#define EL_DF_MIRROR_FIXED_START 431 +#define EL_DF_MIRROR_FIXED_00 (EL_DF_MIRROR_FIXED_START + 0) +#define EL_DF_MIRROR_FIXED_01 (EL_DF_MIRROR_FIXED_START + 1) +#define EL_DF_MIRROR_FIXED_02 (EL_DF_MIRROR_FIXED_START + 2) +#define EL_DF_MIRROR_FIXED_03 (EL_DF_MIRROR_FIXED_START + 3) +#define EL_DF_MIRROR_FIXED_04 (EL_DF_MIRROR_FIXED_START + 4) +#define EL_DF_MIRROR_FIXED_05 (EL_DF_MIRROR_FIXED_START + 5) +#define EL_DF_MIRROR_FIXED_06 (EL_DF_MIRROR_FIXED_START + 6) +#define EL_DF_MIRROR_FIXED_07 (EL_DF_MIRROR_FIXED_START + 7) +#define EL_DF_MIRROR_FIXED_08 (EL_DF_MIRROR_FIXED_START + 8) +#define EL_DF_MIRROR_FIXED_09 (EL_DF_MIRROR_FIXED_START + 9) +#define EL_DF_MIRROR_FIXED_10 (EL_DF_MIRROR_FIXED_START + 10) +#define EL_DF_MIRROR_FIXED_11 (EL_DF_MIRROR_FIXED_START + 11) +#define EL_DF_MIRROR_FIXED_12 (EL_DF_MIRROR_FIXED_START + 12) +#define EL_DF_MIRROR_FIXED_13 (EL_DF_MIRROR_FIXED_START + 13) +#define EL_DF_MIRROR_FIXED_14 (EL_DF_MIRROR_FIXED_START + 14) +#define EL_DF_MIRROR_FIXED_15 (EL_DF_MIRROR_FIXED_START + 15) +#define EL_DF_MIRROR_FIXED_END EL_DF_MIRROR_FIXED_15 + +#define EL_DF_SLOPE_START 447 +#define EL_DF_SLOPE_00 (EL_DF_SLOPE_START + 0) +#define EL_DF_SLOPE_01 (EL_DF_SLOPE_START + 1) +#define EL_DF_SLOPE_02 (EL_DF_SLOPE_START + 2) +#define EL_DF_SLOPE_03 (EL_DF_SLOPE_START + 3) +#define EL_DF_SLOPE_END EL_DF_SLOPE_03 + +#define EL_MM_END_2 450 #define EL_MM_END EL_MM_END_2 // "real" (and therefore drawable) runtime elements #define EL_EXIT_OPENING 500 #define EL_EXIT_CLOSING 501 -#define EL_GRAY_BALL_OPENING 502 -#define EL_ICE_WALL_SHRINKING 503 -#define EL_AMOEBA_WALL_GROWING 504 - -#define EL_WALL_CHANGING 512 -#define EL_WALL_CHANGING_START (EL_WALL_CHANGING + 0) -#define EL_WALL_CHANGING_END (EL_WALL_CHANGING + 15) +#define EL_GRAY_BALL_ACTIVE 502 +#define EL_GRAY_BALL_OPENING 503 +#define EL_WALL_ICE_SHRINKING 504 +#define EL_WALL_AMOEBA_GROWING 505 +#define EL_BOMB_ACTIVE 506 +#define EL_MINE_ACTIVE 507 +#define EL_ENVELOPE_1_OPENING 508 +#define EL_ENVELOPE_2_OPENING 509 +#define EL_ENVELOPE_3_OPENING 510 +#define EL_ENVELOPE_4_OPENING 511 + +#define EL_ENVELOPE_OPENING_START EL_ENVELOPE_1_OPENING +#define EL_ENVELOPE_OPENING_END EL_ENVELOPE_4_OPENING + +#define EL_WALL_CHANGING_BASE 512 +#define EL_WALL_CHANGING_START (EL_WALL_CHANGING_BASE + 0) +#define EL_WALL_CHANGING_END (EL_WALL_CHANGING_BASE + 15) #define EL_FIRST_RUNTIME_EL EL_EXIT_OPENING @@ -666,18 +646,6 @@ extern int num_element_info; #define EL_EXPLODING_OPAQUE 601 #define EL_EXPLODING_TRANSP 602 -// dummy elements (never used as game elements, only used as graphics) -#define EL_MM_MASK_MCDUFFIN_RIGHT 700 -#define EL_MM_MASK_MCDUFFIN_UP 701 -#define EL_MM_MASK_MCDUFFIN_LEFT 702 -#define EL_MM_MASK_MCDUFFIN_DOWN 703 -#define EL_MM_MASK_GRID_1 704 -#define EL_MM_MASK_GRID_2 705 -#define EL_MM_MASK_GRID_3 706 -#define EL_MM_MASK_GRID_4 707 -#define EL_MM_MASK_RECTANGE 708 -#define EL_MM_MASK_CIRCLE 709 - // game graphics: // 0 - 191: graphics from "MirrorScreen" @@ -687,324 +655,6 @@ extern int num_element_info; #define IMG_EMPTY IMG_EMPTY_SPACE -#define GFX_START_MIRRORSCREEN 0 -#define GFX_END_MIRRORSCREEN 191 -#define GFX_START_PSEUDO 192 -#define GFX_END_PSEUDO 255 -#define GFX_START_MIRRORFONT 256 -#define GFX_END_MIRRORFONT 511 -#define GFX_START_MIRRORDF 512 -#define GFX_END_MIRRORDF 767 - -#define NUM_TILES 512 - -// graphics from "MirrorScreen" -#define GFX_EMPTY (-1) -// row 0 (0) -#define GFX_MIRROR_START 0 -#define GFX_MIRROR GFX_MIRROR_START -#define GFX_MIRROR_00 (GFX_MIRROR_START + 0) -#define GFX_MIRROR_01 (GFX_MIRROR_START + 1) -#define GFX_MIRROR_02 (GFX_MIRROR_START + 2) -#define GFX_MIRROR_03 (GFX_MIRROR_START + 3) -#define GFX_MIRROR_04 (GFX_MIRROR_START + 4) -#define GFX_MIRROR_05 (GFX_MIRROR_START + 5) -#define GFX_MIRROR_06 (GFX_MIRROR_START + 6) -#define GFX_MIRROR_07 (GFX_MIRROR_START + 7) -#define GFX_MIRROR_08 (GFX_MIRROR_START + 8) -#define GFX_MIRROR_09 (GFX_MIRROR_START + 9) -#define GFX_MIRROR_10 (GFX_MIRROR_START + 10) -#define GFX_MIRROR_11 (GFX_MIRROR_START + 11) -#define GFX_MIRROR_12 (GFX_MIRROR_START + 12) -#define GFX_MIRROR_13 (GFX_MIRROR_START + 13) -#define GFX_MIRROR_14 (GFX_MIRROR_START + 14) -#define GFX_MIRROR_15 (GFX_MIRROR_START + 15) -#define GFX_MIRROR_END GFX_MIRROR_15 -// row 1 (16) -#define GFX_GRID_STEEL_START 16 -#define GFX_GRID_STEEL GFX_GRID_STEEL_START -#define GFX_GRID_STEEL_00 (GFX_GRID_STEEL_START + 0) -#define GFX_GRID_STEEL_01 (GFX_GRID_STEEL_START + 1) -#define GFX_GRID_STEEL_02 (GFX_GRID_STEEL_START + 2) -#define GFX_GRID_STEEL_03 (GFX_GRID_STEEL_START + 3) -#define GFX_MCDUFFIN_START 20 -#define GFX_MCDUFFIN GFX_MCDUFFIN_START -#define GFX_MCDUFFIN_RIGHT (GFX_MCDUFFIN_START + 0) -#define GFX_MCDUFFIN_UP (GFX_MCDUFFIN_START + 1) -#define GFX_MCDUFFIN_LEFT (GFX_MCDUFFIN_START + 2) -#define GFX_MCDUFFIN_DOWN (GFX_MCDUFFIN_START + 3) -#define GFX_EXIT_CLOSED 24 -#define GFX_EXIT_OPENING_1 25 -#define GFX_EXIT_OPENING_2 26 -#define GFX_EXIT_OPEN 27 -#define GFX_KETTLE 28 -#define GFX_EXPLOSION_KETTLE 29 -// row 2 (32) -#define GFX_PRISM 32 -#define GFX_WALL_SEVERAL 33 -#define GFX_WALL_ANIMATION 34 -#define GFX_BLOCK_WOOD 36 -#define GFX_BOMB 37 -#define GFX_FUSE_ON 38 -#define GFX_FUSE_OFF 39 -#define GFX_GATE_STONE 40 -#define GFX_KEY 41 -#define GFX_LIGHTBULB_OFF 42 -#define GFX_LIGHTBULB_ON 43 -#define GFX_BALL_RED 44 -#define GFX_BALL_BLUE 45 -#define GFX_BALL_YELLOW 46 -#define GFX_BALL_GRAY 47 -// row 3 (48) -#define GFX_BEAMER_START 48 -#define GFX_BEAMER_END 63 -// row 4 (64) -#define GFX_PACMAN_START 64 -#define GFX_PACMAN GFX_PACMAN_START -#define GFX_PACMAN_RIGHT (GFX_PACMAN_START + 0) -#define GFX_PACMAN_UP (GFX_PACMAN_START + 1) -#define GFX_PACMAN_LEFT (GFX_PACMAN_START + 2) -#define GFX_PACMAN_DOWN (GFX_PACMAN_START + 3) -#define GFX_EXPLOSION_START 72 -#define GFX_EXPLOSION_SHORT 76 -#define GFX_EXPLOSION_LAST 78 -// row 5 (80) -#define GFX_POLAR_START 80 -#define GFX_POLAR_END 95 -// row 6 (96) -#define GFX_POLAR_CROSS_START 96 -#define GFX_POLAR_CROSS GFX_POLAR_CROSS_START -#define GFX_POLAR_CROSS_00 (GFX_POLAR_CROSS_START + 0) -#define GFX_POLAR_CROSS_01 (GFX_POLAR_CROSS_START + 1) -#define GFX_POLAR_CROSS_02 (GFX_POLAR_CROSS_START + 2) -#define GFX_POLAR_CROSS_03 (GFX_POLAR_CROSS_START + 3) -#define GFX_MIRROR_FIXED_START 100 -#define GFX_MIRROR_FIXED GFX_MIRROR_FIXED_START -#define GFX_MIRROR_FIXED_00 (GFX_MIRROR_FIXED_START + 0) -#define GFX_MIRROR_FIXED_01 (GFX_MIRROR_FIXED_START + 1) -#define GFX_MIRROR_FIXED_02 (GFX_MIRROR_FIXED_START + 2) -#define GFX_MIRROR_FIXED_03 (GFX_MIRROR_FIXED_START + 3) -// row 7 (112) -#define GFX_BLOCK_STONE 112 -#define GFX_GATE_WOOD 113 -#define GFX_FUEL_FULL 114 -#define GFX_FUEL_EMPTY 115 -#define GFX_GRID_WOOD_00 116 -#define GFX_GRID_WOOD_01 117 -#define GFX_GRID_WOOD_02 118 -#define GFX_GRID_WOOD_03 119 -// row 8 (128) -#define GFX_ARROW_BLUE_LEFT 128 -#define GFX_ARROW_BLUE_RIGHT 129 -#define GFX_ARROW_BLUE_UP 130 -#define GFX_ARROW_BLUE_DOWN 131 -#define GFX_ARROW_RED_LEFT 132 -#define GFX_ARROW_RED_RIGHT 133 -#define GFX_ARROW_RED_UP 134 -#define GFX_ARROW_RED_DOWN 135 -// row 9 (144) -#define GFX_SCROLLBAR_BLUE 144 -#define GFX_SCROLLBAR_RED 145 -// row 10 (160) -#define GFX_MASK_CIRCLE 160 -#define GFX_MASK_RECTANGLE 161 -#define GFX_MASK_RECTANGLE2 162 -#define GFX_MASK_RECTANGLE3 163 -#define GFX_MASK_GRID_00 164 -#define GFX_MASK_GRID_01 165 -#define GFX_MASK_GRID_02 166 -#define GFX_MASK_GRID_03 167 -// row 11 (176) -#define GFX_MASK_MCDUFFIN_00 176 -#define GFX_MASK_MCDUFFIN_01 177 -#define GFX_MASK_MCDUFFIN_02 178 -#define GFX_MASK_MCDUFFIN_03 179 - -// pseudo-graphics; will be mapped to other graphics -#define GFX_WALL_STEEL 192 -#define GFX_WALL_WOOD 193 -#define GFX_WALL_ICE 194 -#define GFX_WALL_AMOEBA 195 -#define GFX_DF_WALL_STEEL 196 -#define GFX_DF_WALL_WOOD 197 - -#define GFX_KUGEL_ROT GFX_BALL_RED -#define GFX_KUGEL_BLAU GFX_BALL_BLUE -#define GFX_KUGEL_GELB GFX_BALL_YELLOW -#define GFX_KUGEL_GRAU GFX_BALL_GRAY - -// graphics from "MirrorFont" -#define GFX_CHAR_START (GFX_START_MIRRORFONT) -#define GFX_CHAR_ASCII0 (GFX_CHAR_START - 32) -#define GFX_CHAR_AUSRUF (GFX_CHAR_ASCII0 + 33) -#define GFX_CHAR_ZOLL (GFX_CHAR_ASCII0 + 34) -#define GFX_CHAR_DOLLAR (GFX_CHAR_ASCII0 + 36) -#define GFX_CHAR_PROZ (GFX_CHAR_ASCII0 + 37) -#define GFX_CHAR_APOSTR (GFX_CHAR_ASCII0 + 39) -#define GFX_CHAR_KLAMM1 (GFX_CHAR_ASCII0 + 40) -#define GFX_CHAR_KLAMM2 (GFX_CHAR_ASCII0 + 41) -#define GFX_CHAR_PLUS (GFX_CHAR_ASCII0 + 43) -#define GFX_CHAR_KOMMA (GFX_CHAR_ASCII0 + 44) -#define GFX_CHAR_MINUS (GFX_CHAR_ASCII0 + 45) -#define GFX_CHAR_PUNKT (GFX_CHAR_ASCII0 + 46) -#define GFX_CHAR_SLASH (GFX_CHAR_ASCII0 + 47) -#define GFX_CHAR_0 (GFX_CHAR_ASCII0 + 48) -#define GFX_CHAR_9 (GFX_CHAR_ASCII0 + 57) -#define GFX_CHAR_DOPPEL (GFX_CHAR_ASCII0 + 58) -#define GFX_CHAR_SEMIKL (GFX_CHAR_ASCII0 + 59) -#define GFX_CHAR_LT (GFX_CHAR_ASCII0 + 60) -#define GFX_CHAR_GLEICH (GFX_CHAR_ASCII0 + 61) -#define GFX_CHAR_GT (GFX_CHAR_ASCII0 + 62) -#define GFX_CHAR_FRAGE (GFX_CHAR_ASCII0 + 63) -#define GFX_CHAR_AT (GFX_CHAR_ASCII0 + 64) -#define GFX_CHAR_A (GFX_CHAR_ASCII0 + 65) -#define GFX_CHAR_Z (GFX_CHAR_ASCII0 + 90) -#define GFX_CHAR_AE (GFX_CHAR_ASCII0 + 91) -#define GFX_CHAR_OE (GFX_CHAR_ASCII0 + 92) -#define GFX_CHAR_UE (GFX_CHAR_ASCII0 + 93) -#define GFX_CHAR_COPY (GFX_CHAR_ASCII0 + 94) -#define GFX_CHAR_END (GFX_CHAR_START + 79) - -// graphics from "MirrorDF" -#define GFX_DF_MIRROR_00 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 0) -#define GFX_DF_MIRROR_01 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 1) -#define GFX_DF_MIRROR_02 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 2) -#define GFX_DF_MIRROR_03 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 3) -#define GFX_DF_MIRROR_04 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 4) -#define GFX_DF_MIRROR_05 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 5) -#define GFX_DF_MIRROR_06 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 6) -#define GFX_DF_MIRROR_07 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 7) -#define GFX_DF_MIRROR_08 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 8) -#define GFX_DF_MIRROR_09 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 9) -#define GFX_DF_MIRROR_10 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 10) -#define GFX_DF_MIRROR_11 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 11) -#define GFX_DF_MIRROR_12 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 12) -#define GFX_DF_MIRROR_13 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 13) -#define GFX_DF_MIRROR_14 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 14) -#define GFX_DF_MIRROR_15 (GFX_START_MIRRORDF + 0 * DF_PER_LINE + 15) - -#define GFX_DF_MIRROR_AUTO_00 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 0) -#define GFX_DF_MIRROR_AUTO_01 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 1) -#define GFX_DF_MIRROR_AUTO_02 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 2) -#define GFX_DF_MIRROR_AUTO_03 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 3) -#define GFX_DF_MIRROR_AUTO_04 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 4) -#define GFX_DF_MIRROR_AUTO_05 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 5) -#define GFX_DF_MIRROR_AUTO_06 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 6) -#define GFX_DF_MIRROR_AUTO_07 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 7) -#define GFX_DF_MIRROR_AUTO_08 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 8) -#define GFX_DF_MIRROR_AUTO_09 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 9) -#define GFX_DF_MIRROR_AUTO_10 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 10) -#define GFX_DF_MIRROR_AUTO_11 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 11) -#define GFX_DF_MIRROR_AUTO_12 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 12) -#define GFX_DF_MIRROR_AUTO_13 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 13) -#define GFX_DF_MIRROR_AUTO_14 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 14) -#define GFX_DF_MIRROR_AUTO_15 (GFX_START_MIRRORDF + 1 * DF_PER_LINE + 15) - -#define GFX_GRID_STEEL_FIXED_00 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 0) -#define GFX_GRID_STEEL_FIXED_01 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 1) -#define GFX_GRID_STEEL_FIXED_02 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 2) -#define GFX_GRID_STEEL_FIXED_03 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 3) -#define GFX_GRID_STEEL_FIXED_04 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 4) -#define GFX_GRID_STEEL_FIXED_05 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 5) -#define GFX_GRID_STEEL_FIXED_06 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 6) -#define GFX_GRID_STEEL_FIXED_07 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 7) -#define GFX_GRID_STEEL_FIXED GFX_GRID_STEEL_FIXED_00 - -#define GFX_GRID_WOOD_FIXED_00 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 8) -#define GFX_GRID_WOOD_FIXED_01 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 9) -#define GFX_GRID_WOOD_FIXED_02 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 10) -#define GFX_GRID_WOOD_FIXED_03 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 11) -#define GFX_GRID_WOOD_FIXED_04 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 12) -#define GFX_GRID_WOOD_FIXED_05 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 13) -#define GFX_GRID_WOOD_FIXED_06 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 14) -#define GFX_GRID_WOOD_FIXED_07 (GFX_START_MIRRORDF + 2 * DF_PER_LINE + 15) -#define GFX_GRID_WOOD_FIXED GFX_GRID_WOOD_FIXED_00 - -#define GFX_GRID_STEEL_AUTO_00 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 0) -#define GFX_GRID_STEEL_AUTO_01 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 1) -#define GFX_GRID_STEEL_AUTO_02 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 2) -#define GFX_GRID_STEEL_AUTO_03 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 3) -#define GFX_GRID_STEEL_AUTO_04 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 4) -#define GFX_GRID_STEEL_AUTO_05 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 5) -#define GFX_GRID_STEEL_AUTO_06 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 6) -#define GFX_GRID_STEEL_AUTO_07 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 7) -#define GFX_GRID_STEEL_AUTO GFX_GRID_STEEL_AUTO_00 - -#define GFX_GRID_WOOD_AUTO_00 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 8) -#define GFX_GRID_WOOD_AUTO_01 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 9) -#define GFX_GRID_WOOD_AUTO_02 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 10) -#define GFX_GRID_WOOD_AUTO_03 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 11) -#define GFX_GRID_WOOD_AUTO_04 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 12) -#define GFX_GRID_WOOD_AUTO_05 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 13) -#define GFX_GRID_WOOD_AUTO_06 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 14) -#define GFX_GRID_WOOD_AUTO_07 (GFX_START_MIRRORDF + 3 * DF_PER_LINE + 15) -#define GFX_GRID_WOOD_AUTO GFX_GRID_WOOD_AUTO_00 - -#define GFX_BEAMER_RED_START (GFX_START_MIRRORDF + 4 * DF_PER_LINE + 0) -#define GFX_BEAMER_RED_END (GFX_START_MIRRORDF + 4 * DF_PER_LINE + 15) -#define GFX_BEAMER_YELLOW_START (GFX_START_MIRRORDF + 5 * DF_PER_LINE + 0) -#define GFX_BEAMER_YELLOW_END (GFX_START_MIRRORDF + 5 * DF_PER_LINE + 15) -#define GFX_BEAMER_GREEN_START (GFX_START_MIRRORDF + 6 * DF_PER_LINE + 0) -#define GFX_BEAMER_GREEN_END (GFX_START_MIRRORDF + 6 * DF_PER_LINE + 15) -#define GFX_BEAMER_BLUE_START (GFX_START_MIRRORDF + 7 * DF_PER_LINE + 0) -#define GFX_BEAMER_BLUE_END (GFX_START_MIRRORDF + 7 * DF_PER_LINE + 15) - -#define GFX_DF_WALL_SEVERAL (GFX_START_MIRRORDF + 8 * DF_PER_LINE + 0) -#define GFX_REFRACTOR (GFX_START_MIRRORDF + 8 * DF_PER_LINE + 1) -#define GFX_CELL (GFX_START_MIRRORDF + 8 * DF_PER_LINE + 2) -#define GFX_MINE (GFX_START_MIRRORDF + 8 * DF_PER_LINE + 4) - -#define GFX_LASER_RIGHT (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 0) -#define GFX_LASER_UP (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 1) -#define GFX_LASER_LEFT (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 2) -#define GFX_LASER_DOWN (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 3) -#define GFX_RECEIVER_RIGHT (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 4) -#define GFX_RECEIVER_UP (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 5) -#define GFX_RECEIVER_LEFT (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 6) -#define GFX_RECEIVER_DOWN (GFX_START_MIRRORDF + 9 * DF_PER_LINE + 7) - -#define GFX_FIBRE_OPTIC_00 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 0) -#define GFX_FIBRE_OPTIC_01 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 1) -#define GFX_FIBRE_OPTIC_02 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 2) -#define GFX_FIBRE_OPTIC_03 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 3) -#define GFX_FIBRE_OPTIC_04 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 4) -#define GFX_FIBRE_OPTIC_05 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 5) -#define GFX_FIBRE_OPTIC_06 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 6) -#define GFX_FIBRE_OPTIC_07 (GFX_START_MIRRORDF + 10 * DF_PER_LINE + 7) - -#define GFX_FIBRE_OPTIC_ED_00 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 0) -#define GFX_FIBRE_OPTIC_ED_01 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 1) -#define GFX_FIBRE_OPTIC_ED_02 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 2) -#define GFX_FIBRE_OPTIC_ED_03 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 3) -#define GFX_FIBRE_OPTIC_ED_04 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 4) -#define GFX_FIBRE_OPTIC_ED_05 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 5) -#define GFX_FIBRE_OPTIC_ED_06 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 6) -#define GFX_FIBRE_OPTIC_ED_07 (GFX_START_MIRRORDF + 11 * DF_PER_LINE + 7) - -// the names of the sounds -#define SND_AMOEBE 0 -#define SND_ANTIGRAV 1 -#define SND_AUTSCH 2 -#define SND_BONG 3 -#define SND_FUEL 4 -#define SND_HALLOFFAME 5 -#define SND_HOLZ 6 -#define SND_HUI 7 -#define SND_KABUMM 8 -#define SND_KINK 9 -#define SND_KLING 10 -#define SND_LASER 11 -#define SND_OEFFNEN 12 -#define SND_QUIEK 13 -#define SND_RHYTHMLOOP 14 -#define SND_ROAAAR 15 -#define SND_SIRR 16 -#define SND_SLURP 17 -#define SND_WARNTON 18 -#define SND_WHOOSH 19 - -#define NUM_SOUNDS 20 - // values for graphics/sounds action types #define MM_ACTION_DEFAULT 0 #define MM_ACTION_WAITING 1 @@ -1073,8 +723,9 @@ extern int num_element_info; #define ANG_RAY_270 12 #define IS_22_5_ANGLE(angle) ((angle) % 2) #define IS_90_ANGLE(angle) (!((angle) % 4)) +#define IS_45_ANGLE(angle) (!(((angle) + 2) % 4)) #define IS_HORIZ_ANGLE(angle) (!((angle) % 8)) -#define IS_VERT_ANGLE(angle) ((angle) % 8) +#define IS_VERT_ANGLE(angle) (!(((angle) + 4) % 8)) // mirror angles #define ANG_MIRROR_0 0 @@ -1097,6 +748,8 @@ extern int num_element_info; #define HIT_MASK_RIGHT (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMRIGHT) #define HIT_MASK_TOP (HIT_MASK_TOPLEFT | HIT_MASK_TOPRIGHT) #define HIT_MASK_BOTTOM (HIT_MASK_BOTTOMLEFT | HIT_MASK_BOTTOMRIGHT) +#define HIT_MASK_DIAGONAL_1 (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT) +#define HIT_MASK_DIAGONAL_2 (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT) #define HIT_MASK_ALL (HIT_MASK_LEFT | HIT_MASK_RIGHT) // step values for rotating elements @@ -1112,45 +765,11 @@ extern int num_element_info; #define GAME_OVER_OVERLOADED 2 #define GAME_OVER_BOMB 3 -// values for game_status -#define EXITGAME 0 -#define MAINMENU 1 -#define PLAYING 2 -#define LEVELED 3 -#define HELPSCREEN 4 -#define CHOOSELEVEL 5 -#define TYPENAME 6 -#define HALLOFFAME 7 -#define SETUP 8 - -// return values for GameActions -#define ACT_GO_ON 0 -#define ACT_GAME_OVER 1 -#define ACT_NEW_GAME 2 - -// values for color_status -#define STATIC_COLORS 0 -#define DYNAMIC_COLORS 1 - #define PROGRAM_VERSION_MAJOR 2 #define PROGRAM_VERSION_MINOR 0 #define PROGRAM_VERSION_PATCH 2 #define PROGRAM_VERSION_STRING "2.0.2" -#define PROGRAM_TITLE_STRING "Mirror Magic II" -#define PROGRAM_AUTHOR_STRING "Holger Schemel" -#define PROGRAM_RIGHTS_STRING "Copyright ^1994-2001" -#define PROGRAM_DOS_PORT_STRING "DOS port based on code by Guido Schulz" -#define PROGRAM_IDENT_STRING PROGRAM_VERSION_STRING " " TARGET_STRING -#define WINDOW_TITLE_STRING PROGRAM_TITLE_STRING " " PROGRAM_IDENT_STRING -#define WINDOW_SUBTITLE_STRING PROGRAM_RIGHTS_STRING " " PROGRAM_AUTHOR_STRING -#define ICON_TITLE_STRING PROGRAM_TITLE_STRING -#define UNIX_USERDATA_DIRECTORY ".mirrormagic" - -#define X11_ICON_FILENAME "mirrormagic_icon.xbm" -#define X11_ICONMASK_FILENAME "mirrormagic_iconmask.xbm" -#define MSDOS_POINTER_FILENAME "mouse.pcx" - // functions for version handling #define MM_VERSION_IDENT(x,y,z) VERSION_IDENT(x,y,z,0) #define MM_VERSION_MAJOR(x) VERSION_PART_1(x) @@ -1174,8 +793,4 @@ extern int num_element_info; PROGRAM_VERSION_MINOR, \ PROGRAM_VERSION_PATCH) -// sound control - -#define ST(x) (((x) - 8) * 16) - #endif // MM_MAIN_H diff --git a/src/game_mm/mm_tools.c b/src/game_mm/mm_tools.c index e71fb1d4..12ca83ce 100644 --- a/src/game_mm/mm_tools.c +++ b/src/game_mm/mm_tools.c @@ -29,8 +29,8 @@ void SetDrawtoField_MM(int mode) // for convenience, absolute screen position to centered level playfield cSX = SX + dSX; cSY = SY + dSY; - cSX2 = SX + dSX + 2; // including playfield border - cSY2 = SY + dSY + 2; // including playfield border + cSX2 = SX + dSX + 2; // including half laser line size + cSY2 = SY + dSY + 2; // including half laser line size if (mode == DRAW_TO_BACKBUFFER) { @@ -41,9 +41,16 @@ void SetDrawtoField_MM(int mode) SetTileCursorSXSY(cSX, cSY); } +void BackToFront_MM(void) +{ + BlitScreenToBitmap_MM(backbuffer); + + BackToFront(); +} + void ClearWindow(void) { - ClearRectangle(backbuffer, REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE); + ClearRectangle(drawto_mm, REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE); SetDrawtoField(DRAW_TO_BACKBUFFER); SetDrawtoField_MM(DRAW_TO_BACKBUFFER); @@ -58,14 +65,14 @@ void DrawGraphicAnimation_MM(int x, int y, int graphic, int frame) getGraphicSource(graphic, frame, &bitmap, &src_x, &src_y); - BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY, + BlitBitmap(bitmap, drawto_mm, src_x, src_y, TILEX, TILEY, cFX + x * TILEX, cFY + y * TILEY); } void DrawGraphic_MM(int x, int y, int graphic) { #if DEBUG - if (!IN_SCR_FIELD(x,y)) + if (!IN_SCR_FIELD(x, y)) { Debug("game:mm:DrawGraphic_MM", "x = %d, y = %d, graphic = %d", x, y, graphic); @@ -75,7 +82,9 @@ void DrawGraphic_MM(int x, int y, int graphic) } #endif - DrawGraphicExt_MM(drawto_field, cFX + x * TILEX, cFY + y * TILEY, graphic); + int frame = getGraphicAnimationFrameXY(graphic, x, y); + + DrawGraphicAnimation_MM(x, y, graphic, frame); MarkTileDirty(x, y); } @@ -90,12 +99,12 @@ void DrawGraphicExt_MM(DrawBuffer *d, int x, int y, int graphic) BlitBitmap(bitmap, d, src_x, src_y, TILEX, TILEY, x, y); } -void DrawGraphicThruMask_MM(int x, int y, int graphic) +void DrawGraphicThruMask_MM(int x, int y, int graphic, int frame) { #if DEBUG - if (!IN_SCR_FIELD(x,y)) + if (!IN_SCR_FIELD(x, y)) { - Debug("game:mm:DrawGraphicThruMask_MM", "x = %d,y = %d, graphic = %d", + Debug("game:mm:DrawGraphicThruMask_MM", "x = %d, y = %d, graphic = %d", x, y, graphic); Debug("game:mm:DrawGraphicThruMask_MM", "This should never happen!"); @@ -103,14 +112,14 @@ void DrawGraphicThruMask_MM(int x, int y, int graphic) } #endif - DrawGraphicThruMaskExt_MM(drawto_field, cFX + x * TILEX, cFY + y * TILEY, - graphic); + DrawGraphicThruMaskExt_MM(drawto_mm, cFX + x * TILEX, cFY + y * TILEY, + graphic, frame); - MarkTileDirty(x,y); + MarkTileDirty(x, y); } void DrawGraphicThruMaskExt_MM(DrawBuffer *d, int dest_x, int dest_y, - int graphic) + int graphic, int frame) { int src_x, src_y; Bitmap *src_bitmap; @@ -118,26 +127,19 @@ void DrawGraphicThruMaskExt_MM(DrawBuffer *d, int dest_x, int dest_y, if (graphic == IMG_EMPTY) return; - getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y); + getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y); BlitBitmapMasked(src_bitmap, d, src_x, src_y, TILEX, TILEY, dest_x, dest_y); } void DrawMiniGraphic_MM(int x, int y, int graphic) { - DrawMiniGraphicExt_MM(drawto, cSX + x * MINI_TILEX, cSY + y * MINI_TILEY, + DrawMiniGraphicExt_MM(drawto_mm, cSX + x * MINI_TILEX, cSY + y * MINI_TILEY, graphic); MarkTileDirty(x / 2, y / 2); } -#if 0 -static void getMicroGraphicSource(int graphic, Bitmap **bitmap, int *x, int *y) -{ - getSizedGraphicSource(graphic, 0, TILESIZE / 4, bitmap, x, y); -} -#endif - void DrawMiniGraphicExt_MM(DrawBuffer *d, int x, int y, int graphic) { Bitmap *bitmap; @@ -148,8 +150,8 @@ void DrawMiniGraphicExt_MM(DrawBuffer *d, int x, int y, int graphic) BlitBitmap(bitmap, d, src_x, src_y, MINI_TILEX, MINI_TILEY, x, y); } -void DrawGraphicShifted_MM(int x,int y, int dx,int dy, int graphic, - int cut_mode, int mask_mode) +void DrawGraphicShifted_MM(int x, int y, int dx, int dy, int graphic, + int cut_mode, int mask_mode) { int width = TILEX, height = TILEY; int cx = 0, cy = 0; @@ -240,7 +242,7 @@ void DrawGraphicShifted_MM(int x,int y, int dx,int dy, int graphic, dest_y = cFY + y * TILEY + dy; #if DEBUG - if (!IN_SCR_FIELD(x,y)) + if (!IN_SCR_FIELD(x, y)) { Debug("game:mm:DrawGraphicShifted_MM", "x = %d, y = %d, graphic = %d", x, y, graphic); @@ -251,19 +253,13 @@ void DrawGraphicShifted_MM(int x,int y, int dx,int dy, int graphic, #endif if (mask_mode == USE_MASKING) - BlitBitmapMasked(src_bitmap, drawto_field, + BlitBitmapMasked(src_bitmap, drawto_mm, src_x, src_y, TILEX, TILEY, dest_x, dest_y); else - BlitBitmap(src_bitmap, drawto_field, + BlitBitmap(src_bitmap, drawto_mm, src_x, src_y, width, height, dest_x, dest_y); - MarkTileDirty(x,y); -} - -void DrawGraphicShiftedThruMask_MM(int x,int y, int dx,int dy, int graphic, - int cut_mode) -{ - DrawGraphicShifted_MM(x, y, dx, dy, graphic, cut_mode, USE_MASKING); + MarkTileDirty(x, y); } void DrawScreenElementExt_MM(int x, int y, int dx, int dy, int element, @@ -290,7 +286,7 @@ void DrawScreenElementExt_MM(int x, int y, int dx, int dy, int element, if (dx || dy) DrawGraphicShifted_MM(x, y, dx, dy, graphic, cut_mode, mask_mode); else if (mask_mode == USE_MASKING) - DrawGraphicThruMask_MM(x, y, graphic); + DrawGraphicThruMask_MM(x, y, graphic, 0); else DrawGraphic_MM(x, y, graphic); } @@ -309,38 +305,11 @@ void DrawScreenElementShifted_MM(int x, int y, int dx, int dy, int element, DrawScreenElementExt_MM(x, y, dx, dy, element, cut_mode, NO_MASKING); } -void DrawLevelElementShifted_MM(int x, int y, int dx, int dy, int element, - int cut_mode) -{ - DrawLevelElementExt_MM(x, y, dx, dy, element, cut_mode, NO_MASKING); -} - -void DrawScreenElementThruMask_MM(int x, int y, int element) -{ - DrawScreenElementExt_MM(x, y, 0, 0, element, NO_CUTTING, USE_MASKING); -} - -void DrawLevelElementThruMask_MM(int x, int y, int element) -{ - DrawLevelElementExt_MM(x, y, 0, 0, element, NO_CUTTING, USE_MASKING); -} - -void DrawLevelFieldThruMask_MM(int x, int y) -{ - DrawLevelElementExt_MM(x, y, 0, 0, Tile[x][y], NO_CUTTING, USE_MASKING); -} - void DrawScreenElement_MM(int x, int y, int element) { DrawScreenElementExt_MM(x, y, 0, 0, element, NO_CUTTING, NO_MASKING); } -void DrawLevelElement_MM(int x, int y, int element) -{ - if (IN_LEV_FIELD(x, y) && IN_SCR_FIELD(SCREENX(x), SCREENY(y))) - DrawScreenElement_MM(SCREENX(x), SCREENY(y), element); -} - void DrawScreenField_MM(int x, int y) { int element = Tile[x][y]; @@ -395,7 +364,24 @@ void DrawScreenField_MM(int x, int y) void DrawLevelField_MM(int x, int y) { - DrawScreenField_MM(x, y); + if (IN_SCR_FIELD(SCREENX(x), SCREENY(y))) + DrawScreenField_MM(SCREENX(x), SCREENY(y)); + else if (IS_MOVING(x, y)) + { + int newx, newy; + + Moving2Blocked(x, y, &newx, &newy); + if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy))) + DrawScreenField_MM(SCREENX(newx), SCREENY(newy)); + } + else if (IS_BLOCKED(x, y)) + { + int oldx, oldy; + + Blocked2Moving(x, y, &oldx, &oldy); + if (IN_SCR_FIELD(SCREENX(oldx), SCREENY(oldy))) + DrawScreenField_MM(SCREENX(oldx), SCREENY(oldy)); + } } void DrawMiniElement_MM(int x, int y, int element) @@ -433,7 +419,7 @@ void DrawField_MM(int x, int y) void DrawLevel_MM(void) { - int x,y; + int x, y; ClearWindow(); @@ -472,10 +458,10 @@ void DrawWallsExt_MM(int x, int y, int element, int draw_mask) continue; if (element & (1 << i)) - BlitBitmap(bitmap, drawto, gx, gy, MINI_TILEX, MINI_TILEY, + BlitBitmap(bitmap, drawto_mm, gx, gy, MINI_TILEX, MINI_TILEY, dest_x, dest_y); else - ClearRectangle(drawto, dest_x, dest_y, MINI_TILEX, MINI_TILEY); + ClearRectangle(drawto_mm, dest_x, dest_y, MINI_TILEX, MINI_TILEY); } MarkTileDirty(x, y); @@ -526,7 +512,7 @@ void DrawWallsAnimation_MM(int x, int y, int element, int phase, int bit_mask) getSizedGraphicSource(graphic, frame, MINI_TILESIZE, &bitmap, &src_x, &src_y); - BlitBitmap(bitmap, drawto, src_x, src_y, MINI_TILEX, MINI_TILEY, + BlitBitmap(bitmap, drawto_mm, src_x, src_y, MINI_TILEX, MINI_TILEY, dst_x, dst_y); } } @@ -555,79 +541,18 @@ void DrawElement_MM(int x, int y, int element) laser.fuse_x == x && laser.fuse_y == y) DrawGraphic_MM(x, y, IMG_MM_FUSE); + else if (element == EL_GRAY_BALL_ACTIVE) + DrawGraphic_MM(x, y, el_act2gfx(EL_GRAY_BALL, MM_ACTION_ACTIVE)); + else if (element == EL_GRAY_BALL_OPENING) + DrawGraphic_MM(x, y, el_act2gfx(EL_GRAY_BALL, MM_ACTION_OPENING)); + else if (element == EL_BOMB_ACTIVE) + DrawGraphic_MM(x, y, el_act2gfx(EL_BOMB, MM_ACTION_ACTIVE)); + else if (element == EL_MINE_ACTIVE) + DrawGraphic_MM(x, y, el_act2gfx(EL_MINE, MM_ACTION_ACTIVE)); else DrawGraphic_MM(x, y, el2gfx(element)); } -#if 0 -static void DrawMicroWalls_MM(int x, int y, int element) -{ - Bitmap *bitmap; - int graphic = el2gfx(WALL_BASE(element)); - int gx, gy; - int i; - - getMicroGraphicSource(graphic, &bitmap, &gx, &gy); - - for (i = 0; i < 4; i++) - { - int xpos = MICROLEV_XPOS + x * MICRO_TILEX + MICRO_WALLX * (i % 2); - int ypos = MICROLEV_YPOS + y * MICRO_TILEY + MICRO_WALLY * (i / 2); - - if (element & (1 << i)) - BlitBitmap(bitmap, drawto, gx, gy, MICRO_WALLX, MICRO_WALLY, xpos, ypos); - else - ClearRectangle(drawto, xpos, ypos, MICRO_WALLX, MICRO_WALLY); - } -} - -static void DrawMicroElement_MM(int x, int y, int element) -{ - Bitmap *bitmap; - int graphic = el2gfx(element); - int gx, gy; - - if (element == EL_EMPTY) - return; - - if (IS_WALL(element)) - { - DrawMicroWalls_MM(x, y, element); - - return; - } - - getMicroGraphicSource(graphic, &bitmap, &gx, &gy); - - BlitBitmap(bitmap, drawto, gx, gy, MICRO_TILEX, MICRO_TILEY, - MICROLEV_XPOS + x * MICRO_TILEX, MICROLEV_YPOS + y * MICRO_TILEY); -} - -static void DrawMicroLevelExt_MM(int xpos, int ypos) -{ - int x, y; - - ClearRectangle(drawto, xpos, ypos, MICROLEV_XSIZE, MICROLEV_YSIZE); - - for (x = 0; x < STD_LEV_FIELDX; x++) - for (y = 0; y < STD_LEV_FIELDY; y++) - DrawMicroElement_MM(x, y, Ur[x][y]); - - redraw_mask |= REDRAW_FIELD; -} -#endif - -void DrawMiniLevel_MM(int size_x, int size_y, int scroll_x, int scroll_y) -{ - int x, y; - - for (x = 0; x < size_x; x++) - for (y = 0; y < size_y; y++) - DrawMiniElementOrWall_MM(x, y, scroll_x, scroll_y); - - redraw_mask |= REDRAW_FIELD; -} - // ---------------------------------------------------------------------------- // XSN @@ -722,7 +647,7 @@ static int xsn_percent(void) int xsn_m3 = xsn_m2 + 10; time_t xsn_e0 = time(NULL); struct tm *xsn_t0 = localtime(&xsn_e0); - struct tm xsn_t1 = { 0,0,0, xsn_m2*3, xsn_m3/3, xsn_t0->tm_year, 0,0,-1 }; + struct tm xsn_t1 = { 0,0,0, xsn_m2 * 3, xsn_m3 / 3, xsn_t0->tm_year, 0,0,-1 }; time_t xsn_e1 = mktime(&xsn_t1); int xsn_c0 = (25 * xsn_m3) << xsn_m1; int xsn_c1 = (xsn_t1.tm_wday - xsn_m1) * !!xsn_t1.tm_wday; @@ -899,16 +824,11 @@ static void DrawTileCursor_Xsn(int draw_target) static boolean started = FALSE; static boolean active = FALSE; static boolean debug = FALSE; - static unsigned int check_delay = 0; - static unsigned int start_delay = 0; - static unsigned int growth_delay = 0; - static unsigned int update_delay = 0; - static unsigned int change_delay = 0; - static unsigned int check_delay_value = XSN_CHECK_DELAY * 1000; - static unsigned int start_delay_value = 0; - static unsigned int growth_delay_value = 0; - static unsigned int update_delay_value = 0; - static unsigned int change_delay_value = 0; + static DelayCounter check_delay = { XSN_CHECK_DELAY * 1000 }; + static DelayCounter start_delay = { 0 }; + static DelayCounter growth_delay = { 0 }; + static DelayCounter update_delay = { 0 }; + static DelayCounter change_delay = { 0 }; static int percent = 0; static int debug_value = 0; boolean reinitialize = FALSE; @@ -918,7 +838,7 @@ static void DrawTileCursor_Xsn(int draw_target) if (draw_target != DRAW_TO_SCREEN) return; - if (DelayReached(&check_delay, check_delay_value)) + if (DelayReached(&check_delay)) { percent = (debug ? debug_value * 100 / XSN_DEBUG_STEPS : xsn_percent()); @@ -946,7 +866,7 @@ static void DrawTileCursor_Xsn(int draw_target) debug = TRUE; active = FALSE; - DelayReached(&check_delay, 0); + ResetDelayCounter(&check_delay); setup.debug.xsn_mode = (debug_value > 0); tile_cursor.xsn_debug = FALSE; @@ -996,11 +916,11 @@ static void DrawTileCursor_Xsn(int draw_target) if (!active_last) { - start_delay_value = (debug || setup.debug.xsn_mode == TRUE ? 0 : + start_delay.value = (debug || setup.debug.xsn_mode == TRUE ? 0 : (XSN_START_DELAY + XSN_RND(XSN_START_DELAY)) * 1000); started = FALSE; - DelayReached(&start_delay, 0); + ResetDelayCounter(&start_delay); reinitialize = TRUE; } @@ -1072,40 +992,40 @@ static void DrawTileCursor_Xsn(int draw_target) if (!started) { - if (!DelayReached(&start_delay, start_delay_value)) + if (!DelayReached(&start_delay)) return; - update_delay_value = XSN_UPDATE_DELAY; - growth_delay_value = XSN_GROWTH_DELAY * 1000; - change_delay_value = XSN_CHANGE_DELAY * 1000; + update_delay.value = XSN_UPDATE_DELAY; + growth_delay.value = XSN_GROWTH_DELAY * 1000; + change_delay.value = XSN_CHANGE_DELAY * 1000; - DelayReached(&growth_delay, 0); - DelayReached(&update_delay, 0); - DelayReached(&change_delay, 0); + ResetDelayCounter(&growth_delay); + ResetDelayCounter(&update_delay); + ResetDelayCounter(&change_delay); started = TRUE; } if (xsn.num_items < xsn.max_items) { - if (DelayReached(&growth_delay, growth_delay_value)) + if (DelayReached(&growth_delay)) { xsn.num_items += XSN_RND(XSN_GROWTH_RATE * 2); xsn.num_items = MIN(xsn.num_items, xsn.max_items); } } - if (DelayReached(&update_delay, update_delay_value)) + if (DelayReached(&update_delay)) { for (i = 0; i < xsn.num_items; i++) xsn_update_item(i); } - if (DelayReached(&change_delay, change_delay_value)) + if (DelayReached(&change_delay)) { xsn_update_change(); - change_delay_value = xsn.change_delay * 1000; + change_delay.value = xsn.change_delay * 1000; } int xsn_alpha_dx = (gfx.mouse_y > xsn.area_ysize - xsn.max_height ? @@ -1137,7 +1057,8 @@ static void DrawTileCursor_Xsn(int draw_target) } } -void DrawTileCursor_MM(int draw_target, boolean tile_cursor_active) +void DrawTileCursor_MM(int draw_target, int drawing_stage, + boolean tile_cursor_active) { if (program.headless) return; @@ -1152,7 +1073,12 @@ void DrawTileCursor_MM(int draw_target, boolean tile_cursor_active) int width = tilesize; int height = tilesize; - DrawTileCursor_Xsn(draw_target); + if (!drawing_stage) + { + DrawTileCursor_Xsn(draw_target); + + return; + } if (!tile_cursor.enabled || !tile_cursor.active || @@ -1198,31 +1124,11 @@ void DrawTileCursor_MM(int draw_target, boolean tile_cursor_active) dst_x, dst_y); } -#if 0 -static int REQ_in_range(int x, int y) -{ - if (y > DY + 249 && y < DY + 278) - { - if (x > DX + 1 && x < DX + 48) - return 1; - else if (x > DX + 51 && x < DX + 98) - return 2; - } - - return 0; -} -#endif - Pixel ReadPixel(DrawBuffer *bitmap, int x, int y) { return GetPixel(bitmap, x, y); } -void SetRGB(unsigned int pixel, - unsigned short red, unsigned short green, unsigned short blue) -{ -} - int get_base_element(int element) { if (IS_MIRROR(element)) @@ -1247,6 +1153,10 @@ int get_base_element(int element) return EL_DF_MIRROR_START; else if (IS_DF_MIRROR_AUTO(element)) return EL_DF_MIRROR_AUTO_START; + else if (IS_DF_MIRROR_FIXED(element)) + return EL_DF_MIRROR_FIXED_START; + else if (IS_DF_SLOPE(element)) + return EL_DF_SLOPE_START; else if (IS_PACMAN(element)) return EL_PACMAN_START; else if (IS_GRID_STEEL(element)) @@ -1290,7 +1200,8 @@ int get_num_elements(int element) IS_POLAR(element) || IS_BEAMER(element) || IS_DF_MIRROR(element) || - IS_DF_MIRROR_AUTO(element)) + IS_DF_MIRROR_AUTO(element) || + IS_DF_MIRROR_FIXED(element)) return 16; else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_WOOD_FIXED(element) || @@ -1304,7 +1215,8 @@ int get_num_elements(int element) IS_RECEIVER(element) || IS_PACMAN(element) || IS_GRID_STEEL(element) || - IS_GRID_WOOD(element)) + IS_GRID_WOOD(element) || + IS_DF_SLOPE(element)) return 4; else return 1; @@ -1319,35 +1231,150 @@ int get_rotated_element(int element, int step) return base_element + (element_phase + step + num_elements) % num_elements; } -static int map_element(int element) +static boolean has_full_rotation(int element) +{ + return (IS_BEAMER(element) || + IS_MCDUFFIN(element) || + IS_LASER(element) || + IS_RECEIVER(element) || + IS_PACMAN(element)); +} + +#define MM_FLIP_X 0 +#define MM_FLIP_Y 1 +#define MM_FLIP_XY 2 + +static int getFlippedTileExt_MM(int element, int mode) +{ + if (IS_WALL(element)) + { + int base = WALL_BASE(element); + int bits = WALL_BITS(element); + + if (mode == MM_FLIP_X) + { + bits = ((bits & 1) << 1 | + (bits & 2) >> 1 | + (bits & 4) << 1 | + (bits & 8) >> 1); + } + else if (mode == MM_FLIP_Y) + { + bits = ((bits & 1) << 2 | + (bits & 2) << 2 | + (bits & 4) >> 2 | + (bits & 8) >> 2); + } + else if (mode == MM_FLIP_XY) + { + bits = ((bits & 1) << 0 | + (bits & 2) << 1 | + (bits & 4) >> 1 | + (bits & 8) >> 0); + } + + element = base | bits; + } + else + { + int base_element = get_base_element(element); + int num_elements = get_num_elements(element); + int element_phase = element - base_element; + + if (IS_GRID_STEEL(element) || IS_GRID_WOOD(element)) + { + if ((mode == MM_FLIP_XY && element_phase < 2) || + (mode != MM_FLIP_XY && element_phase > 1)) + element_phase ^= 1; + } + else if (IS_DF_SLOPE(element)) + { + element_phase = (mode == MM_FLIP_X ? 5 - element_phase : + mode == MM_FLIP_Y ? 3 - element_phase : + mode == MM_FLIP_XY ? 4 - element_phase : + element_phase); + } + else + { + int num_elements_flip = num_elements; + + if (has_full_rotation(element)) + { + if (mode == MM_FLIP_X) + num_elements_flip = num_elements / 2; + else if (mode == MM_FLIP_XY) + num_elements_flip = num_elements * 3 / 4; + } + else + { + if (mode == MM_FLIP_XY) + num_elements_flip = num_elements / 2; + } + + element_phase = num_elements_flip - element_phase; + } + + element = base_element + (element_phase + num_elements) % num_elements; + } + + return element; +} + +int getFlippedTileX_MM(int element) +{ + return getFlippedTileExt_MM(element, MM_FLIP_X); +} + +int getFlippedTileY_MM(int element) +{ + return getFlippedTileExt_MM(element, MM_FLIP_Y); +} + +int getFlippedTileXY_MM(int element) +{ + return getFlippedTileExt_MM(element, MM_FLIP_XY); +} + +int map_wall_from_base_element(int element) { switch (element) { - case EL_WALL_STEEL: return EL_STEEL_WALL; - case EL_WALL_WOOD: return EL_WOODEN_WALL; - case EL_WALL_ICE: return EL_ICE_WALL; - case EL_WALL_AMOEBA: return EL_AMOEBA_WALL; - case EL_DF_WALL_STEEL: return EL_DF_STEEL_WALL; - case EL_DF_WALL_WOOD: return EL_DF_WOODEN_WALL; + case EL_WALL_STEEL_BASE: return EL_WALL_STEEL; + case EL_WALL_WOOD_BASE: return EL_WALL_WOOD; + case EL_WALL_ICE_BASE: return EL_WALL_ICE; + case EL_WALL_AMOEBA_BASE: return EL_WALL_AMOEBA; + case EL_DF_WALL_STEEL_BASE: return EL_DF_WALL_STEEL; + case EL_DF_WALL_WOOD_BASE: return EL_DF_WALL_WOOD; default: return element; } } -int el2gfx(int element) +int map_wall_to_base_element(int element) { - element = map_element(element); - switch (element) { - case EL_LIGHTBALL: - return IMG_MM_LIGHTBALL_RED + RND(3); + case EL_WALL_STEEL: return EL_WALL_STEEL_BASE; + case EL_WALL_WOOD: return EL_WALL_WOOD_BASE; + case EL_WALL_ICE: return EL_WALL_ICE_BASE; + case EL_WALL_AMOEBA: return EL_WALL_AMOEBA_BASE; + case EL_DF_WALL_STEEL: return EL_DF_WALL_STEEL_BASE; + case EL_DF_WALL_WOOD: return EL_DF_WALL_WOOD_BASE; - default: - return el2img_mm(element); + default: return element; } } +int el2gfx(int element) +{ + return el2img_mm(map_wall_from_base_element(element)); +} + +int el_act2gfx(int element, int action) +{ + return el_act2img_mm(map_wall_from_base_element(element), action); +} + void RedrawPlayfield_MM(void) { DrawLevel_MM(); @@ -1356,6 +1383,6 @@ void RedrawPlayfield_MM(void) void BlitScreenToBitmap_MM(Bitmap *target_bitmap) { - BlitBitmap(drawto_field, target_bitmap, + BlitBitmap(drawto_mm, target_bitmap, REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE, REAL_SX, REAL_SY); } diff --git a/src/game_mm/mm_tools.h b/src/game_mm/mm_tools.h index 97b48f43..28ba5c33 100644 --- a/src/game_mm/mm_tools.h +++ b/src/game_mm/mm_tools.h @@ -50,7 +50,8 @@ void SetDrawtoField_MM(int); -void BackToFront(void); +void BackToFront_MM(void); + void FadeToFront(void); void ClearWindow(void); @@ -63,23 +64,17 @@ void DrawGraphicAnimation_MM(int, int, int, int); void DrawGraphic_MM(int, int, int); void DrawGraphicExt_MM(DrawBuffer *, int, int, int); -void DrawGraphicThruMask_MM(int, int, int); -void DrawGraphicThruMaskExt_MM(DrawBuffer *, int, int, int); +void DrawGraphicThruMask_MM(int, int, int, int); +void DrawGraphicThruMaskExt_MM(DrawBuffer *, int, int, int, int); void DrawMiniGraphic_MM(int, int, int); void getMiniGraphicSource(int, Bitmap **, int *, int *); void DrawMiniGraphicExt_MM(DrawBuffer *, int, int, int); void DrawGraphicShifted_MM(int, int, int, int, int, int, int); -void DrawGraphicShiftedThruMask_MM(int, int, int, int, int, int); void DrawScreenElementExt_MM(int, int, int, int, int, int, int); void DrawLevelElementExt_MM(int, int, int, int, int, int, int); void DrawScreenElementShifted_MM(int, int, int, int, int, int); -void DrawLevelElementShifted_MM(int, int, int, int, int, int); -void DrawScreenElementThruMask_MM(int, int, int); -void DrawLevelElementThruMask_MM(int, int, int); -void DrawLevelFieldThruMask_MM(int, int); void ErdreichAnbroeckeln(int, int); void DrawScreenElement_MM(int, int, int); -void DrawLevelElement_MM(int, int, int); void DrawScreenField_MM(int, int); void DrawLevelField_MM(int, int); void DrawMiniElement_MM(int, int, int); @@ -92,9 +87,8 @@ void DrawElement_MM(int, int, int); void DrawWallsExt_MM(int, int, int, int); void DrawWalls_MM(int, int, int); void DrawWallsAnimation_MM(int, int, int, int, int); -void DrawMiniLevel_MM(int, int, int, int); void DrawMicroLevel_MM(int, int, boolean); -void DrawTileCursor_MM(int, boolean); +void DrawTileCursor_MM(int, int, boolean); boolean Request(char *, unsigned int); unsigned int OpenDoor(unsigned int); @@ -104,7 +98,6 @@ unsigned int MoveDoor(unsigned int); void DrawSpecialEditorDoor_MM(void); void UndrawSpecialEditorDoor(void); Pixel ReadPixel(DrawBuffer *, int, int); -void SetRGB(unsigned int, unsigned short, unsigned short, unsigned short); void CreateToolButtons(void); @@ -113,6 +106,10 @@ int get_element_phase(int); int get_num_elements(int); int get_rotated_element(int, int); +int map_wall_from_base_element(int); +int map_wall_to_base_element(int); + int el2gfx(int); +int el_act2gfx(int, int); #endif diff --git a/src/game_sp/DDSpriteBuffer.c b/src/game_sp/DDSpriteBuffer.c index 9865b930..5d5fc38f 100644 --- a/src/game_sp/DDSpriteBuffer.c +++ b/src/game_sp/DDSpriteBuffer.c @@ -40,7 +40,7 @@ void DDSpriteBuffer_BltImg(int pX, int pY, int graphic, int sync_frame) if (graphic < 0) return; - getGraphicSource_SP(&g, graphic, sync_frame, -1, -1); + getGraphicSource_SP(&g, graphic, sync_frame); Blt(pX, pY, g.bitmap, g.src_x, g.src_y); } diff --git a/src/game_sp/MainGameLoop.c b/src/game_sp/MainGameLoop.c index c6d2d6dc..294e4143 100644 --- a/src/game_sp/MainGameLoop.c +++ b/src/game_sp/MainGameLoop.c @@ -25,7 +25,7 @@ void subMainGameLoop_Init(void) RedDiskReleasePhase = 0; // (re-)enable red disk release } -void subMainGameLoop_Main(byte action, boolean warp_mode) +void subMainGameLoop_Main(byte action) { // --------------------------------------------------------------------------- // --------------------- START OF GAME-BUSY LOOP ----------------------------- diff --git a/src/game_sp/MainGameLoop.h b/src/game_sp/MainGameLoop.h index 0dd629fb..7e7ba1e5 100644 --- a/src/game_sp/MainGameLoop.h +++ b/src/game_sp/MainGameLoop.h @@ -14,7 +14,7 @@ extern int ExitToMenuFlag; extern int LeadOutCounter; void subMainGameLoop_Init(void); -void subMainGameLoop_Main(byte, boolean); +void subMainGameLoop_Main(byte); void subCalculateScreenScrollPos(void); #endif // MAINGAMELOOP_H diff --git a/src/game_sp/export.h b/src/game_sp/export.h index d8b964fd..cd1804dd 100644 --- a/src/game_sp/export.h +++ b/src/game_sp/export.h @@ -172,7 +172,7 @@ void InitPrecedingPlayfieldMemory(void); void InitGfxBuffers_SP(void); void InitGameEngine_SP(void); -void GameActions_SP(byte *, boolean); +void GameActions_SP(byte[MAX_PLAYERS]); unsigned int InitEngineRandom_SP(int); diff --git a/src/game_sp/main.c b/src/game_sp/main.c index 7eed1dd5..206ae571 100644 --- a/src/game_sp/main.c +++ b/src/game_sp/main.c @@ -72,7 +72,7 @@ static void UpdateGameDoorValues_SP(void) game_sp.score = 0; // (currently no score in Supaplex engine) } -void GameActions_SP(byte action[MAX_PLAYERS], boolean warp_mode) +void GameActions_SP(byte action[MAX_PLAYERS]) { byte single_player_action = action[0]; int x, y; @@ -80,7 +80,7 @@ void GameActions_SP(byte action[MAX_PLAYERS], boolean warp_mode) UpdateEngineValues(mScrollX / TILEX, mScrollY / TILEY, MurphyScreenXPos / TILEX, MurphyScreenYPos / TILEY); - subMainGameLoop_Main(single_player_action, warp_mode); + subMainGameLoop_Main(single_player_action); RedrawPlayfield_SP(FALSE); diff --git a/src/init.c b/src/init.c index 210594fc..62a5d0ff 100644 --- a/src/init.c +++ b/src/init.c @@ -34,11 +34,28 @@ #define CONFIG_TOKEN_FONT_INITIAL "font.initial" +#define CONFIG_TOKEN_GLOBAL_BUSY_INITIAL "global.busy_initial" #define CONFIG_TOKEN_GLOBAL_BUSY "global.busy" +#define CONFIG_TOKEN_GLOBAL_BUSY_PLAYFIELD "global.busy_playfield" +#define CONFIG_TOKEN_BACKGROUND "background" +#define CONFIG_TOKEN_BACKGROUND_LOADING_INITIAL "background.LOADING_INITIAL" +#define CONFIG_TOKEN_BACKGROUND_LOADING "background.LOADING" + +#define INITIAL_IMG_GLOBAL_BUSY_INITIAL 0 +#define INITIAL_IMG_GLOBAL_BUSY 1 +#define INITIAL_IMG_GLOBAL_BUSY_PLAYFIELD 2 + +#define NUM_INITIAL_IMAGES_BUSY 3 + +#define INITIAL_IMG_BACKGROUND 3 +#define INITIAL_IMG_BACKGROUND_LOADING_INITIAL 4 +#define INITIAL_IMG_BACKGROUND_LOADING 5 + +#define NUM_INITIAL_IMAGES 6 static struct FontBitmapInfo font_initial[NUM_INITIAL_FONTS]; -static struct GraphicInfo anim_initial; +static struct GraphicInfo image_initial[NUM_INITIAL_IMAGES]; static int copy_properties[][5] = { @@ -93,38 +110,75 @@ static int copy_properties[][5] = static int get_graphic_parameter_value(char *, char *, int); -static void DrawInitAnim(void) +static int getLoadingBackgroundImage(int graphic) +{ + return getImageFromGraphicOrDefault(graphic, INITIAL_IMG_BACKGROUND); +} + +static void SetLoadingWindowBackgroundImage(int graphic) +{ + SetBackgroundImage(getLoadingBackgroundImage(graphic), REDRAW_ALL); +} + +static void SetLoadingBackgroundImage(void) { struct GraphicInfo *graphic_info_last = graphic_info; - int graphic = 0; - static unsigned int action_delay = 0; - unsigned int action_delay_value = GameFrameDelay; + int background_image = (game_status_last_screen == -1 ? + INITIAL_IMG_BACKGROUND_LOADING_INITIAL : + INITIAL_IMG_BACKGROUND_LOADING); + + graphic_info = image_initial; + + SetDrawDeactivationMask(REDRAW_NONE); + SetDrawBackgroundMask(REDRAW_ALL); + + SetLoadingWindowBackgroundImage(background_image); + + graphic_info = graphic_info_last; +} + +static void DrawInitAnim(boolean only_when_loading) +{ + struct GraphicInfo *graphic_info_last = graphic_info; + int graphic = (game_status_last_screen == -1 ? + INITIAL_IMG_GLOBAL_BUSY_INITIAL : + game_status == GAME_MODE_LOADING ? + INITIAL_IMG_GLOBAL_BUSY : + INITIAL_IMG_GLOBAL_BUSY_PLAYFIELD); + struct MenuPosInfo *busy = (game_status_last_screen == -1 ? + &init_last.busy_initial : + game_status == GAME_MODE_LOADING ? + &init_last.busy : + &init_last.busy_playfield); + static DelayCounter action_delay = { 0 }; int sync_frame = FrameCounter; int x, y; + action_delay.value = GameFrameDelay; + // prevent OS (Windows) from complaining about program not responding CheckQuitEvent(); - if (game_status != GAME_MODE_LOADING) + if (game_status != GAME_MODE_LOADING && only_when_loading) return; - if (anim_initial.bitmap == NULL || window == NULL) + if (image_initial[graphic].bitmap == NULL || window == NULL) return; - if (!DelayReached(&action_delay, action_delay_value)) + if (!DelayReached(&action_delay)) return; - if (init_last.busy.x == -1) - init_last.busy.x = WIN_XSIZE / 2; - if (init_last.busy.y == -1) - init_last.busy.y = WIN_YSIZE / 2; + if (busy->x == -1) + busy->x = (game_status == GAME_MODE_LOADING ? WIN_XSIZE / 2 : SXSIZE / 2); + if (busy->y == -1) + busy->y = (game_status == GAME_MODE_LOADING ? WIN_YSIZE / 2 : SYSIZE / 2); - x = ALIGNED_TEXT_XPOS(&init_last.busy); - y = ALIGNED_TEXT_YPOS(&init_last.busy); + x = (game_status == GAME_MODE_LOADING ? 0 : SX) + ALIGNED_TEXT_XPOS(busy); + y = (game_status == GAME_MODE_LOADING ? 0 : SY) + ALIGNED_TEXT_YPOS(busy); - graphic_info = &anim_initial; // graphic == 0 => anim_initial + graphic_info = image_initial; - if (sync_frame % anim_initial.anim_delay == 0) + if (sync_frame % image_initial[graphic].anim_delay == 0) { Bitmap *src_bitmap; int src_x, src_y; @@ -132,8 +186,12 @@ static void DrawInitAnim(void) int height = graphic_info[graphic].height; int frame = getGraphicAnimationFrame(graphic, sync_frame); + ClearRectangleOnBackground(drawto, x, y, width, height); + getFixedGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y); - BlitBitmap(src_bitmap, window, src_x, src_y, width, height, x, y); + BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height, x, y); + + BlitBitmap(drawto, window, x, y, width, height, x, y); } graphic_info = graphic_info_last; @@ -323,7 +381,7 @@ void InitImageTextures(void) CreateImageTextures(texture_graphics[i]); } -static int getFontBitmapID(int font_nr) +static int getFontSpecialSuffix(void) { int special = -1; @@ -336,6 +394,13 @@ static int getFontBitmapID(int font_nr) else if (game_status == GAME_MODE_PSEUDO_TYPENAMES) special = GFX_SPECIAL_ARG_NAMES; + return special; +} + +static int getFontBitmapID(int font_nr) +{ + int special = getFontSpecialSuffix(); + if (special != -1) return font_info[font_nr].special_bitmap_id[special]; else @@ -353,6 +418,22 @@ static int getFontFromToken(char *token) return FONT_INITIAL_1; } +static char *getTokenFromFont(int font_nr) +{ + static char *token = NULL; + int special = getFontSpecialSuffix(); + + checked_free(token); + + if (special != -1) + token = getStringCat2(font_info[font_nr].token_name, + special_suffix_info[special].suffix); + else + token = getStringCopy(font_info[font_nr].token_name); + + return token; +} + static void InitFontGraphicInfo(void) { static struct FontBitmapInfo *font_bitmap_info = NULL; @@ -364,7 +445,7 @@ static void InitFontGraphicInfo(void) if (graphic_info == NULL) // still at startup phase { InitFontInfo(font_initial, NUM_INITIAL_FONTS, - getFontBitmapID, getFontFromToken); + getFontBitmapID, getFontFromToken, getTokenFromFont); return; } @@ -589,7 +670,7 @@ static void InitFontGraphicInfo(void) } InitFontInfo(font_bitmap_info, num_font_bitmaps, - getFontBitmapID, getFontFromToken); + getFontBitmapID, getFontFromToken, getTokenFromFont); } static void InitGlobalAnimGraphicInfo(void) @@ -1256,19 +1337,27 @@ static int get_graphic_parameter_value(char *value_raw, char *suffix, int type) return -1; } -static int get_scaled_graphic_width(int graphic) +static int get_scaled_graphic_width(Bitmap *src_bitmap, int graphic) { int original_width = getOriginalImageWidthFromImageID(graphic); int scale_up_factor = graphic_info[graphic].scale_up_factor; + // only happens when loaded outside artwork system (like "global.busy") + if (graphic_info == image_initial && src_bitmap) + original_width = src_bitmap->width; + return original_width * scale_up_factor; } -static int get_scaled_graphic_height(int graphic) +static int get_scaled_graphic_height(Bitmap *src_bitmap, int graphic) { int original_height = getOriginalImageHeightFromImageID(graphic); int scale_up_factor = graphic_info[graphic].scale_up_factor; + // only happens when loaded outside artwork system (like "global.busy") + if (graphic_info == image_initial && src_bitmap) + original_height = src_bitmap->height; + return original_height * scale_up_factor; } @@ -1323,6 +1412,7 @@ static void set_graphic_parameters_ext(int graphic, int *parameter, g->sort_priority = 0; // default for title screens g->class = 0; g->style = STYLE_DEFAULT; + g->alpha = -1; g->bitmaps = src_bitmaps; g->bitmap = src_bitmap; @@ -1352,8 +1442,8 @@ static void set_graphic_parameters_ext(int graphic, int *parameter, if (g->use_image_size) { // set new default bitmap size (with scaling, but without small images) - g->width = get_scaled_graphic_width(graphic); - g->height = get_scaled_graphic_height(graphic); + g->width = get_scaled_graphic_width(src_bitmap, graphic); + g->height = get_scaled_graphic_height(src_bitmap, graphic); } // optional width and height of each animation frame @@ -1400,15 +1490,8 @@ static void set_graphic_parameters_ext(int graphic, int *parameter, if (src_bitmap) { // get final bitmap size (with scaling, but without small images) - int src_image_width = get_scaled_graphic_width(graphic); - int src_image_height = get_scaled_graphic_height(graphic); - - if (src_image_width == 0 || src_image_height == 0) - { - // only happens when loaded outside artwork system (like "global.busy") - src_image_width = src_bitmap->width; - src_image_height = src_bitmap->height; - } + int src_image_width = get_scaled_graphic_width(src_bitmap, graphic); + int src_image_height = get_scaled_graphic_height(src_bitmap, graphic); if (parameter[GFX_ARG_TILE_SIZE] != ARG_UNDEFINED_VALUE) { @@ -1503,6 +1586,9 @@ static void set_graphic_parameters_ext(int graphic, int *parameter, // animation synchronized with global frame counter, not move position g->anim_global_sync = parameter[GFX_ARG_GLOBAL_SYNC]; + // animation synchronized with global anim frame counter, not move position + g->anim_global_anim_sync = parameter[GFX_ARG_GLOBAL_ANIM_SYNC]; + // optional element for cloning crumble graphics if (parameter[GFX_ARG_CRUMBLED_LIKE] != ARG_UNDEFINED_VALUE) g->crumbled_like = parameter[GFX_ARG_CRUMBLED_LIKE]; @@ -1563,7 +1649,7 @@ static void set_graphic_parameters_ext(int graphic, int *parameter, g->draw_yoffset = parameter[GFX_ARG_DRAW_YOFFSET]; // use a different default value for global animations and toons - if ((graphic >= IMG_GFX_GLOBAL_ANIM_1 && graphic <= IMG_GFX_GLOBAL_ANIM_8) || + if ((graphic >= IMG_GFX_GLOBAL_ANIM_1 && graphic <= IMG_GFX_GLOBAL_ANIM_32) || (graphic >= IMG_TOON_1 && graphic <= IMG_TOON_20)) g->draw_masked = TRUE; @@ -1601,12 +1687,20 @@ static void set_graphic_parameters_ext(int graphic, int *parameter, g->class = parameter[GFX_ARG_CLASS]; if (parameter[GFX_ARG_STYLE] != ARG_UNDEFINED_VALUE) g->style = parameter[GFX_ARG_STYLE]; + if (parameter[GFX_ARG_ALPHA] != ARG_UNDEFINED_VALUE) + g->alpha = parameter[GFX_ARG_ALPHA]; // this is only used for drawing menu buttons and text g->active_xoffset = parameter[GFX_ARG_ACTIVE_XOFFSET]; g->active_yoffset = parameter[GFX_ARG_ACTIVE_YOFFSET]; g->pressed_xoffset = parameter[GFX_ARG_PRESSED_XOFFSET]; g->pressed_yoffset = parameter[GFX_ARG_PRESSED_YOFFSET]; + + // this is only used for drawing stacked global animations + g->stacked_xfactor = parameter[GFX_ARG_STACKED_XFACTOR]; + g->stacked_yfactor = parameter[GFX_ARG_STACKED_YFACTOR]; + g->stacked_xoffset = parameter[GFX_ARG_STACKED_XOFFSET]; + g->stacked_yoffset = parameter[GFX_ARG_STACKED_YOFFSET]; } static void set_graphic_parameters(int graphic) @@ -1694,6 +1788,8 @@ static void InitGraphicInfo(void) IMG_BACKGROUND_REQUEST, IMG_BACKGROUND, + IMG_BACKGROUND_LOADING_INITIAL, + IMG_BACKGROUND_LOADING, IMG_BACKGROUND_TITLE_INITIAL, IMG_BACKGROUND_TITLE, IMG_BACKGROUND_MAIN, @@ -1701,6 +1797,7 @@ static void InitGraphicInfo(void) IMG_BACKGROUND_LEVELS, IMG_BACKGROUND_LEVELNR, IMG_BACKGROUND_SCORES, + IMG_BACKGROUND_SCOREINFO, IMG_BACKGROUND_EDITOR, IMG_BACKGROUND_INFO, IMG_BACKGROUND_INFO_ELEMENTS, @@ -1882,6 +1979,16 @@ static void InitGraphicCompatibilityInfo(void) // process all images which default to same image as "global.door" if (strEqual(fi->default_filename, fi_global_door->default_filename)) { + // skip all images that are cloned from images that default to same + // image as "global.door", but that are redefined to something else + if (graphic_info[i].clone_from != -1) + { + int cloned_graphic = graphic_info[i].clone_from; + + if (getImageListEntryFromImageID(cloned_graphic)->redefined) + continue; + } + #if 0 Debug("init:InitGraphicCompatibilityInfo", "special treatment needed for token '%s'", fi->token); @@ -1894,6 +2001,70 @@ static void InitGraphicCompatibilityInfo(void) } } + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + { + Bitmap *bitmap = graphic_info[IMG_BACKGROUND_SCORES].bitmap; + + BlitBitmap(bitmap, bitmap, 18, 66, 32, 480, 50, 66); + BlitBitmap(bitmap, bitmap, 466, 66, 32, 480, 434, 66); + + ClearRectangle(bitmap, 2, 66, 32, 480); + ClearRectangle(bitmap, 514, 66, 32, 480); + } + + // special compatibility handling for "Jue" graphics sets (2007 and 2019) + boolean supports_score_info = (menu.draw_xoffset[GAME_MODE_SCOREINFO] != 0); + if (strPrefix(artwork.gfx_current_identifier, "jue") && !supports_score_info) + { + int font_title[] = + { + FONT_TITLE_1, + FONT_TITLE_2, + + -1 + }; + int font_text[] = + { + FONT_TEXT_1, + FONT_TEXT_2, + FONT_TEXT_3, + FONT_TEXT_4, + + -1 + }; + int mode_old = GAME_MODE_SCORES; + int mode_new = GAME_MODE_SCOREINFO; + int i, j; + + // adjust title screens on score info page + for (i = 0; font_title[i] != -1; i++) + { + struct FontInfo *fi = &font_info[font_title[i]]; + + fi->special_graphic[mode_new] = fi->special_graphic[mode_old]; + fi->special_bitmap_id[mode_new] = fi->special_bitmap_id[mode_old]; + } + + // adjust vertical text and button positions on scores page + for (i = 0; font_text[i] != -1; i++) + { + for (j = 0; j < 2; j++) + { + boolean jue0 = strEqual(artwork.gfx_current_identifier, "jue0"); + int font_nr = (j == 0 ? font_text[i] : FONT_ACTIVE(font_text[i])); + int font_bitmap_id = font_info[font_nr].special_bitmap_id[mode_old]; + int font_yoffset = (jue0 ? 10 : 5); + + gfx.font_bitmap_info[font_bitmap_id].draw_yoffset = font_yoffset; + } + } + + // adjust page offsets on score info page + menu.draw_xoffset[mode_new] = menu.draw_xoffset[mode_old]; + menu.draw_yoffset[mode_new] = menu.draw_yoffset[mode_old]; + } + InitGraphicCompatibilityInfo_Doors(); } @@ -2112,6 +2283,11 @@ static void InitSoundInfo(void) } set_sound_parameters(i, sound->parameter); + +#if 0 + Debug("init:InitSoundInfo", "loop mode: %d ['%s']", + sound_info[i].loop, sound->token); +#endif } free(sound_effect_properties); @@ -2236,6 +2412,13 @@ static void InitMusicInfo(void) } } + +static void InitGameInfoFromArtworkInfo(void) +{ + // special case: store initial value of custom artwork setting + game.use_masked_elements_initial = game.use_masked_elements; +} + static void ReinitializeGraphics(void) { print_timestamp_init("ReinitializeGraphics"); @@ -2269,16 +2452,13 @@ static void ReinitializeGraphics(void) InitGraphicCompatibilityInfo(); print_timestamp_time("InitGraphicCompatibilityInfo"); - SetMainBackgroundImage(IMG_BACKGROUND); - print_timestamp_time("SetMainBackgroundImage"); - SetDoorBackgroundImage(IMG_BACKGROUND_DOOR); - print_timestamp_time("SetDoorBackgroundImage"); - InitGadgets(); print_timestamp_time("InitGadgets"); InitDoors(); print_timestamp_time("InitDoors"); + InitGameInfoFromArtworkInfo(); + print_timestamp_done("ReinitializeGraphics"); } @@ -3000,6 +3180,22 @@ void InitElementPropertiesStatic(void) static int ep_walkable_over[] = { EL_EMPTY_SPACE, + EL_EMPTY_SPACE_1, + EL_EMPTY_SPACE_2, + EL_EMPTY_SPACE_3, + EL_EMPTY_SPACE_4, + EL_EMPTY_SPACE_5, + EL_EMPTY_SPACE_6, + EL_EMPTY_SPACE_7, + EL_EMPTY_SPACE_8, + EL_EMPTY_SPACE_9, + EL_EMPTY_SPACE_10, + EL_EMPTY_SPACE_11, + EL_EMPTY_SPACE_12, + EL_EMPTY_SPACE_13, + EL_EMPTY_SPACE_14, + EL_EMPTY_SPACE_15, + EL_EMPTY_SPACE_16, EL_SP_EMPTY_SPACE, EL_SOKOBAN_FIELD_EMPTY, EL_EXIT_OPEN, @@ -3297,6 +3493,29 @@ void InitElementPropertiesStatic(void) -1 }; + static int ep_empty_space[] = + { + EL_EMPTY_SPACE, + EL_EMPTY_SPACE_1, + EL_EMPTY_SPACE_2, + EL_EMPTY_SPACE_3, + EL_EMPTY_SPACE_4, + EL_EMPTY_SPACE_5, + EL_EMPTY_SPACE_6, + EL_EMPTY_SPACE_7, + EL_EMPTY_SPACE_8, + EL_EMPTY_SPACE_9, + EL_EMPTY_SPACE_10, + EL_EMPTY_SPACE_11, + EL_EMPTY_SPACE_12, + EL_EMPTY_SPACE_13, + EL_EMPTY_SPACE_14, + EL_EMPTY_SPACE_15, + EL_EMPTY_SPACE_16, + + -1 + }; + static int ep_player[] = { EL_PLAYER_1, @@ -4059,6 +4278,7 @@ void InitElementPropertiesStatic(void) EL_BD_AMOEBA, EL_EMC_MAGIC_BALL, EL_EMC_ANDROID, + EL_MM_GRAY_BALL, -1 }; @@ -4099,6 +4319,22 @@ void InitElementPropertiesStatic(void) static int ep_inactive[] = { EL_EMPTY, + EL_EMPTY_SPACE_1, + EL_EMPTY_SPACE_2, + EL_EMPTY_SPACE_3, + EL_EMPTY_SPACE_4, + EL_EMPTY_SPACE_5, + EL_EMPTY_SPACE_6, + EL_EMPTY_SPACE_7, + EL_EMPTY_SPACE_8, + EL_EMPTY_SPACE_9, + EL_EMPTY_SPACE_10, + EL_EMPTY_SPACE_11, + EL_EMPTY_SPACE_12, + EL_EMPTY_SPACE_13, + EL_EMPTY_SPACE_14, + EL_EMPTY_SPACE_15, + EL_EMPTY_SPACE_16, EL_SAND, EL_WALL, EL_BD_WALL, @@ -4348,6 +4584,7 @@ void InitElementPropertiesStatic(void) EL_INTERNAL_CASCADE_STEEL_CHARS_ACTIVE, EL_INTERNAL_CASCADE_CE_ACTIVE, EL_INTERNAL_CASCADE_GE_ACTIVE, + EL_INTERNAL_CASCADE_ES_ACTIVE, EL_INTERNAL_CASCADE_REF_ACTIVE, EL_INTERNAL_CASCADE_USER_ACTIVE, EL_INTERNAL_CASCADE_DYNAMIC_ACTIVE, @@ -4371,6 +4608,7 @@ void InitElementPropertiesStatic(void) EL_INTERNAL_CASCADE_STEEL_CHARS, EL_INTERNAL_CASCADE_CE, EL_INTERNAL_CASCADE_GE, + EL_INTERNAL_CASCADE_ES, EL_INTERNAL_CASCADE_REF, EL_INTERNAL_CASCADE_USER, EL_INTERNAL_CASCADE_DYNAMIC, @@ -4428,6 +4666,7 @@ void InitElementPropertiesStatic(void) { ep_can_explode, EP_CAN_EXPLODE }, { ep_gravity_reachable, EP_GRAVITY_REACHABLE }, + { ep_empty_space, EP_EMPTY_SPACE }, { ep_player, EP_PLAYER }, { ep_can_pass_magic_wall, EP_CAN_PASS_MAGIC_WALL }, { ep_can_pass_dc_magic_wall, EP_CAN_PASS_DC_MAGIC_WALL }, @@ -4664,7 +4903,7 @@ void InitElementPropertiesEngine(int engine_version) i == EL_BLACK_ORB)); // ---------- COULD_MOVE_INTO_ACID ---------------------------------------- - SET_PROPERTY(i, EP_COULD_MOVE_INTO_ACID, (ELEM_IS_PLAYER(i) || + SET_PROPERTY(i, EP_COULD_MOVE_INTO_ACID, (IS_PLAYER_ELEMENT(i) || CAN_MOVE(i) || IS_CUSTOM_ELEMENT(i))); @@ -4689,7 +4928,7 @@ void InitElementPropertiesEngine(int engine_version) // ---------- CAN_BE_CLONED_BY_ANDROID ------------------------------------ for (j = 0; j < level.num_android_clone_elements; j++) SET_PROPERTY(i, EP_CAN_BE_CLONED_BY_ANDROID, - (i != EL_EMPTY && + (!IS_EMPTY(i) && IS_EQUAL_OR_IN_GROUP(i, level.android_clone_element[j]))); // ---------- CAN_CHANGE -------------------------------------------------- @@ -4802,6 +5041,9 @@ static void InitGlobal(void) global_anim_info[i].token_name = global_anim_name_info[i].token_name; } + // create hash to store URLs for global animations + anim_url_hash = newSetupFileHash(); + // create hash from image config list image_config_hash = newSetupFileHash(); for (i = 0; image_config[i].token != NULL; i++) @@ -4903,7 +5145,10 @@ static void InitGlobal(void) global.autoplay_leveldir = NULL; global.patchtapes_leveldir = NULL; global.convert_leveldir = NULL; - global.create_images_dir = NULL; + global.dumplevel_leveldir = NULL; + global.dumptape_leveldir = NULL; + global.create_sketch_images_dir = NULL; + global.create_collect_images_dir = NULL; global.frames_per_second = 0; global.show_frames_per_second = FALSE; @@ -5015,39 +5260,70 @@ static void Execute_Command(char *command) { char *filename = &command[11]; - if (!fileExists(filename)) + if (fileExists(filename)) + { + LoadLevelFromFilename(&level, filename); + DumpLevel(&level); + + exit(0); + } + + char *leveldir = getStringCopy(filename); // read command parameters + char *level_nr = strchr(leveldir, ' '); + + if (level_nr == NULL) Fail("cannot open file '%s'", filename); - LoadLevelFromFilename(&level, filename); - DumpLevel(&level); + *level_nr++ = '\0'; - exit(0); + global.dumplevel_leveldir = leveldir; + global.dumplevel_level_nr = atoi(level_nr); + + program.headless = TRUE; } else if (strPrefix(command, "dump tape ")) { char *filename = &command[10]; - if (!fileExists(filename)) + if (fileExists(filename)) + { + LoadTapeFromFilename(filename); + DumpTape(&tape); + + exit(0); + } + + char *leveldir = getStringCopy(filename); // read command parameters + char *level_nr = strchr(leveldir, ' '); + + if (level_nr == NULL) Fail("cannot open file '%s'", filename); - LoadTapeFromFilename(filename); - DumpTape(&tape); + *level_nr++ = '\0'; - exit(0); + global.dumptape_leveldir = leveldir; + global.dumptape_level_nr = atoi(level_nr); + + program.headless = TRUE; } else if (strPrefix(command, "autoplay ") || strPrefix(command, "autoffwd ") || strPrefix(command, "autowarp ") || strPrefix(command, "autotest ") || + strPrefix(command, "autosave ") || + strPrefix(command, "autoupload ") || strPrefix(command, "autofix ")) { - char *str_ptr = getStringCopy(&command[8]); // read command parameters + char *arg_ptr = strchr(command, ' '); + char *str_ptr = getStringCopy(arg_ptr); // read command parameters global.autoplay_mode = (strPrefix(command, "autoplay") ? AUTOPLAY_MODE_PLAY : strPrefix(command, "autoffwd") ? AUTOPLAY_MODE_FFWD : strPrefix(command, "autowarp") ? AUTOPLAY_MODE_WARP : strPrefix(command, "autotest") ? AUTOPLAY_MODE_TEST : + strPrefix(command, "autosave") ? AUTOPLAY_MODE_SAVE : + strPrefix(command, "autoupload") ? AUTOPLAY_MODE_UPLOAD : strPrefix(command, "autofix") ? AUTOPLAY_MODE_FIX : AUTOPLAY_MODE_NONE); @@ -5161,13 +5437,21 @@ static void Execute_Command(char *command) program.headless = TRUE; } - else if (strPrefix(command, "create images ")) + else if (strPrefix(command, "create sketch images ")) + { + global.create_sketch_images_dir = getStringCopy(&command[21]); + + if (access(global.create_sketch_images_dir, W_OK) != 0) + Fail("image target directory '%s' not found or not writable", + global.create_sketch_images_dir); + } + else if (strPrefix(command, "create collect image ")) { - global.create_images_dir = getStringCopy(&command[14]); + global.create_collect_images_dir = getStringCopy(&command[21]); - if (access(global.create_images_dir, W_OK) != 0) + if (access(global.create_collect_images_dir, W_OK) != 0) Fail("image target directory '%s' not found or not writable", - global.create_images_dir); + global.create_collect_images_dir); } else if (strPrefix(command, "create CE image ")) { @@ -5190,13 +5474,18 @@ static void InitSetup(void) LoadUserSetup(); // global user number LoadSetup(); // global setup info - LoadSetup_AutoSetup(); // global auto setup info // set some options from setup file if (setup.options.verbose) options.verbose = TRUE; + if (setup.options.debug) + options.debug = TRUE; + + if (!strEqual(setup.options.debug_mode, ARG_UNDEFINED_STRING)) + options.debug_mode = getStringCopy(setup.options.debug_mode); + if (setup.debug.show_frames_per_second) global.show_frames_per_second = TRUE; } @@ -5204,10 +5493,9 @@ static void InitSetup(void) static void InitGameInfo(void) { game.restart_level = FALSE; - game.restart_game_message = NULL; - game.request_active = FALSE; - game.request_active_or_moving = FALSE; + + game.use_masked_elements_initial = FALSE; } static void InitPlayerInfo(void) @@ -5408,7 +5696,6 @@ void InitGfxBuffers(void) } ReCreateBitmap(&bitmap_db_field, FXSIZE, FYSIZE); - ReCreateBitmap(&bitmap_db_panel, DXSIZE, DYSIZE); ReCreateBitmap(&bitmap_db_door_1, 3 * DXSIZE, DYSIZE); ReCreateBitmap(&bitmap_db_door_2, 3 * VXSIZE, VYSIZE); @@ -5428,15 +5715,32 @@ void InitGfxBuffers(void) InitGfxBuffers_EM(); InitGfxBuffers_SP(); + InitGfxBuffers_MM(); } static void InitGfx(void) { struct GraphicInfo *graphic_info_last = graphic_info; char *filename_font_initial = NULL; - char *filename_anim_initial = NULL; + char *filename_image_initial[NUM_INITIAL_IMAGES] = { NULL }; + char *image_token[NUM_INITIAL_IMAGES] = + { + CONFIG_TOKEN_GLOBAL_BUSY_INITIAL, + CONFIG_TOKEN_GLOBAL_BUSY, + CONFIG_TOKEN_GLOBAL_BUSY_PLAYFIELD, + CONFIG_TOKEN_BACKGROUND, + CONFIG_TOKEN_BACKGROUND_LOADING_INITIAL, + CONFIG_TOKEN_BACKGROUND_LOADING + }; + struct MenuPosInfo *init_busy[NUM_INITIAL_IMAGES_BUSY] = + { + &init.busy_initial, + &init.busy, + &init.busy_playfield + }; Bitmap *bitmap_font_initial = NULL; - int i, j; + int parameter[NUM_INITIAL_IMAGES][NUM_GFX_ARGS]; + int i, j, k; // determine settings for initial font (for displaying startup messages) for (i = 0; image_config[i].token != NULL; i++) @@ -5450,7 +5754,9 @@ static void InitGfx(void) len_font_token = strlen(font_token); if (strEqual(image_config[i].token, font_token)) + { filename_font_initial = image_config[i].value; + } else if (strlen(image_config[i].token) > len_font_token && strncmp(image_config[i].token, font_token, len_font_token) == 0) { @@ -5479,6 +5785,8 @@ static void InitGfx(void) InitGfxCustomArtworkInfo(); InitGfxOtherSettings(); + InitGfxTileSizeInfo(TILESIZE, TILESIZE); + bitmap_font_initial = LoadCustomImage(filename_font_initial); for (j = 0; j < NUM_INITIAL_FONTS; j++) @@ -5486,21 +5794,17 @@ static void InitGfx(void) InitFontGraphicInfo(); - DrawProgramInfo(); - - DrawInitText("Loading graphics", 120, FC_GREEN); - - // initialize settings for busy animation with default values - int parameter[NUM_GFX_ARGS]; - for (i = 0; i < NUM_GFX_ARGS; i++) - parameter[i] = get_graphic_parameter_value(image_config_suffix[i].value, - image_config_suffix[i].token, - image_config_suffix[i].type); + InitMenuDesignSettings_Static(); - char *anim_token = CONFIG_TOKEN_GLOBAL_BUSY; - int len_anim_token = strlen(anim_token); + // initialize settings for initial images with default values + for (i = 0; i < NUM_INITIAL_IMAGES; i++) + for (j = 0; j < NUM_GFX_ARGS; j++) + parameter[i][j] = + get_graphic_parameter_value(image_config_suffix[j].value, + image_config_suffix[j].token, + image_config_suffix[j].type); - // read settings for busy animation from default custom artwork config + // read settings for initial images from default custom artwork config char *gfx_config_filename = getPath3(options.graphics_directory, GFX_DEFAULT_SUBDIR, GRAPHICSINFO_FILENAME); @@ -5511,79 +5815,110 @@ static void InitGfx(void) if (setup_file_hash) { - char *filename = getHashEntry(setup_file_hash, anim_token); - - if (filename) + for (i = 0; i < NUM_INITIAL_IMAGES; i++) { - filename_anim_initial = getStringCopy(filename); + char *filename = getHashEntry(setup_file_hash, image_token[i]); - for (j = 0; image_config_suffix[j].token != NULL; j++) + if (filename) { - int type = image_config_suffix[j].type; - char *suffix = image_config_suffix[j].token; - char *token = getStringCat2(anim_token, suffix); - char *value = getHashEntry(setup_file_hash, token); + filename_image_initial[i] = getStringCopy(filename); - checked_free(token); + for (j = 0; image_config_suffix[j].token != NULL; j++) + { + int type = image_config_suffix[j].type; + char *suffix = image_config_suffix[j].token; + char *token = getStringCat2(image_token[i], suffix); + char *value = getHashEntry(setup_file_hash, token); + + checked_free(token); - if (value) - parameter[j] = get_graphic_parameter_value(value, suffix, type); + if (value) + parameter[i][j] = + get_graphic_parameter_value(value, suffix, type); + } } } + // read values from custom graphics config file + InitMenuDesignSettings_FromHash(setup_file_hash, FALSE); + freeSetupFileHash(setup_file_hash); } } - if (filename_anim_initial == NULL) + for (i = 0; i < NUM_INITIAL_IMAGES; i++) { - // read settings for busy animation from static default artwork config - for (i = 0; image_config[i].token != NULL; i++) + if (filename_image_initial[i] == NULL) { - if (strEqual(image_config[i].token, anim_token)) - filename_anim_initial = getStringCopy(image_config[i].value); - else if (strlen(image_config[i].token) > len_anim_token && - strncmp(image_config[i].token, anim_token, len_anim_token) == 0) + int len_token = strlen(image_token[i]); + + // read settings for initial images from static default artwork config + for (j = 0; image_config[j].token != NULL; j++) { - for (j = 0; image_config_suffix[j].token != NULL; j++) + if (strEqual(image_config[j].token, image_token[i])) { - if (strEqual(&image_config[i].token[len_anim_token], - image_config_suffix[j].token)) - parameter[j] = - get_graphic_parameter_value(image_config[i].value, - image_config_suffix[j].token, - image_config_suffix[j].type); + filename_image_initial[i] = getStringCopy(image_config[j].value); + } + else if (strlen(image_config[j].token) > len_token && + strncmp(image_config[j].token, image_token[i], len_token) == 0) + { + for (k = 0; image_config_suffix[k].token != NULL; k++) + { + if (strEqual(&image_config[j].token[len_token], + image_config_suffix[k].token)) + parameter[i][k] = + get_graphic_parameter_value(image_config[j].value, + image_config_suffix[k].token, + image_config_suffix[k].type); + } } } } } - if (filename_anim_initial == NULL) // should not happen - Fail("cannot get filename for '%s'", CONFIG_TOKEN_GLOBAL_BUSY); + for (i = 0; i < NUM_INITIAL_IMAGES; i++) + { + if (filename_image_initial[i] == NULL) // should not happen + Fail("cannot get filename for '%s'", image_token[i]); + + image_initial[i].bitmaps = + checked_calloc(sizeof(Bitmap *) * NUM_IMG_BITMAP_POINTERS); - anim_initial.bitmaps = - checked_calloc(sizeof(Bitmap *) * NUM_IMG_BITMAP_POINTERS); + if (!strEqual(filename_image_initial[i], UNDEFINED_FILENAME)) + image_initial[i].bitmaps[IMG_BITMAP_STANDARD] = + LoadCustomImage(filename_image_initial[i]); - anim_initial.bitmaps[IMG_BITMAP_STANDARD] = - LoadCustomImage(filename_anim_initial); + checked_free(filename_image_initial[i]); + } - checked_free(filename_anim_initial); + for (i = 0; i < NUM_INITIAL_IMAGES; i++) + image_initial[i].use_image_size = TRUE; - graphic_info = &anim_initial; // graphic == 0 => anim_initial + graphic_info = image_initial; // graphic == 0 => image_initial - set_graphic_parameters_ext(0, parameter, anim_initial.bitmaps); + for (i = 0; i < NUM_INITIAL_IMAGES; i++) + set_graphic_parameters_ext(i, parameter[i], image_initial[i].bitmaps); graphic_info = graphic_info_last; - init.busy.width = anim_initial.width; - init.busy.height = anim_initial.height; + for (i = 0; i < NUM_INITIAL_IMAGES_BUSY; i++) + { + // set image size for busy animations + init_busy[i]->width = image_initial[i].width; + init_busy[i]->height = image_initial[i].height; + } + + SetLoadingBackgroundImage(); - InitMenuDesignSettings_Static(); + ClearRectangleOnBackground(window, 0, 0, WIN_XSIZE, WIN_YSIZE); + + DrawProgramInfo(); InitGfxDrawBusyAnimFunction(DrawInitAnim); InitGfxDrawGlobalAnimFunction(DrawGlobalAnimations); InitGfxDrawGlobalBorderFunction(DrawMaskedBorderToTarget); InitGfxDrawTileCursorFunction(DrawTileCursor); + InitGfxDrawEnvelopeRequestFunction(DrawEnvelopeRequestToScreen); gfx.fade_border_source_status = global.border_status; gfx.fade_border_target_status = global.border_status; @@ -5691,17 +6026,14 @@ static void InitImages(void) print_timestamp_done("InitImages"); } -static void InitSound(char *identifier) +static void InitSound(void) { print_timestamp_init("InitSound"); - if (identifier == NULL) - identifier = artwork.snd_current->identifier; - // set artwork path to send it to the sound server process setLevelArtworkDir(artwork.snd_first); - InitReloadCustomSounds(identifier); + InitReloadCustomSounds(); print_timestamp_time("InitReloadCustomSounds"); ReinitializeSounds(); @@ -5710,17 +6042,14 @@ static void InitSound(char *identifier) print_timestamp_done("InitSound"); } -static void InitMusic(char *identifier) +static void InitMusic(void) { print_timestamp_init("InitMusic"); - if (identifier == NULL) - identifier = artwork.mus_current->identifier; - // set artwork path to send it to the sound server process setLevelArtworkDir(artwork.mus_first); - InitReloadCustomMusic(identifier); + InitReloadCustomMusic(); print_timestamp_time("InitReloadCustomMusic"); ReinitializeMusic(); @@ -5909,12 +6238,12 @@ static void InitOverrideArtwork(void) #endif } -static char *getNewArtworkIdentifier(int type) +static char *setNewArtworkIdentifier(int type) { static char *last_leveldir_identifier[3] = { NULL, NULL, NULL }; static char *last_artwork_identifier[3] = { NULL, NULL, NULL }; static boolean last_override_level_artwork[3] = { FALSE, FALSE, FALSE }; - static boolean last_has_level_artwork_set[3] = { FALSE, FALSE, FALSE }; + static boolean last_has_custom_artwork_set[3] = { FALSE, FALSE, FALSE }; static boolean initialized[3] = { FALSE, FALSE, FALSE }; TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type); boolean setup_override_artwork = GFX_OVERRIDE_ARTWORK(type); @@ -5923,6 +6252,9 @@ static char *getNewArtworkIdentifier(int type) // !!! setLevelArtworkDir() should be moved to an earlier stage !!! char *leveldir_artwork_set = setLevelArtworkDir(artwork_first_node); boolean has_level_artwork_set = (leveldir_artwork_set != NULL); + TreeInfo *custom_artwork_set = + getTreeInfoFromIdentifier(artwork_first_node, leveldir_identifier); + boolean has_custom_artwork_set = (custom_artwork_set != NULL); char *artwork_current_identifier; char *artwork_new_identifier = NULL; // default: nothing has changed @@ -5940,9 +6272,9 @@ static char *getNewArtworkIdentifier(int type) if (setup_override_artwork) artwork_current_identifier = setup_artwork_set; - else if (leveldir_artwork_set != NULL) + else if (has_level_artwork_set) artwork_current_identifier = leveldir_artwork_set; - else if (getTreeInfoFromIdentifier(artwork_first_node, leveldir_identifier)) + else if (has_custom_artwork_set) artwork_current_identifier = leveldir_identifier; else artwork_current_identifier = setup_artwork_set; @@ -5952,11 +6284,11 @@ static char *getNewArtworkIdentifier(int type) // ---------- reload if level set and also artwork set has changed ---------- if (last_leveldir_identifier[type] != leveldir_identifier && - (last_has_level_artwork_set[type] || has_level_artwork_set)) + (last_has_custom_artwork_set[type] || has_custom_artwork_set)) artwork_new_identifier = artwork_current_identifier; last_leveldir_identifier[type] = leveldir_identifier; - last_has_level_artwork_set[type] = has_level_artwork_set; + last_has_custom_artwork_set[type] = has_custom_artwork_set; // ---------- reload if "override artwork" setting has changed -------------- if (last_override_level_artwork[type] != setup_override_artwork) @@ -5971,6 +6303,7 @@ static char *getNewArtworkIdentifier(int type) // (we cannot compare string pointers here, so copy string content itself) setString(&last_artwork_identifier[type], artwork_current_identifier); + // ---------- set new artwork identifier ---------- *(ARTWORK_CURRENT_IDENTIFIER_PTR(artwork, type)) = artwork_current_identifier; // ---------- do not reload directly after starting ------------------------- @@ -5982,6 +6315,13 @@ static char *getNewArtworkIdentifier(int type) return artwork_new_identifier; } +static void InitArtworkIdentifier(void) +{ + setNewArtworkIdentifier(ARTWORK_TYPE_GRAPHICS); + setNewArtworkIdentifier(ARTWORK_TYPE_SOUNDS); + setNewArtworkIdentifier(ARTWORK_TYPE_MUSIC); +} + void ReloadCustomArtwork(int force_reload) { int last_game_status = game_status; // save current game status @@ -5998,9 +6338,9 @@ void ReloadCustomArtwork(int force_reload) AdjustGraphicsForEMC(); AdjustSoundsForEMC(); - gfx_new_identifier = getNewArtworkIdentifier(ARTWORK_TYPE_GRAPHICS); - snd_new_identifier = getNewArtworkIdentifier(ARTWORK_TYPE_SOUNDS); - mus_new_identifier = getNewArtworkIdentifier(ARTWORK_TYPE_MUSIC); + gfx_new_identifier = setNewArtworkIdentifier(ARTWORK_TYPE_GRAPHICS); + snd_new_identifier = setNewArtworkIdentifier(ARTWORK_TYPE_SOUNDS); + mus_new_identifier = setNewArtworkIdentifier(ARTWORK_TYPE_MUSIC); reload_needed = (gfx_new_identifier != NULL || force_reload_gfx || snd_new_identifier != NULL || force_reload_snd || @@ -6015,11 +6355,17 @@ void ReloadCustomArtwork(int force_reload) FadeOut(REDRAW_ALL); - ClearRectangle(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE); - print_timestamp_time("ClearRectangle"); + SetLoadingBackgroundImage(); + + ClearRectangleOnBackground(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE); + print_timestamp_time("ClearRectangleOnBackground"); FadeIn(REDRAW_ALL); + UPDATE_BUSY_STATE(); + + InitMissingFileHash(); + if (gfx_new_identifier != NULL || force_reload_gfx) { #if 0 @@ -6037,13 +6383,13 @@ void ReloadCustomArtwork(int force_reload) if (snd_new_identifier != NULL || force_reload_snd) { - InitSound(snd_new_identifier); + InitSound(); print_timestamp_time("InitSound"); } if (mus_new_identifier != NULL || force_reload_mus) { - InitMusic(mus_new_identifier); + InitMusic(); print_timestamp_time("InitMusic"); } @@ -6051,8 +6397,6 @@ void ReloadCustomArtwork(int force_reload) SetGameStatus(last_game_status); // restore current game status - init_last = init; // switch to new busy animation - FadeOut(REDRAW_ALL); RedrawGlobalBorder(); @@ -6120,7 +6464,7 @@ void DisplayExitMessage(char *format, va_list ap) sy += 3 * font_height; num_lines_printed = - DrawTextBuffer(sx, sy, program.log_filename[LOG_ERR_ID], font_2, + DrawTextBuffer(sx, sy, program.log_filename, font_2, line_length, line_length, max_lines, 0, BLIT_ON_BACKGROUND, TRUE, TRUE, FALSE); @@ -6133,8 +6477,8 @@ void DisplayExitMessage(char *format, va_list ap) BackToFront(); - // deactivate toons on error message screen - setup.toons = FALSE; + // deactivate toons and global animations on error message screen + setup.global_animations = FALSE; WaitForEventToContinue(); } @@ -6154,14 +6498,18 @@ void OpenAll(void) InitGlobal(); // initialize some global variables + InitRND(NEW_RANDOMIZE); + InitSimpleRandom(NEW_RANDOMIZE); + InitBetterRandom(NEW_RANDOMIZE); + + InitMissingFileHash(); + print_timestamp_time("[init global stuff]"); InitSetup(); print_timestamp_time("[init setup/config stuff (1)]"); - InitScoresInfo(); - if (options.execute_command) Execute_Command(options.execute_command); @@ -6191,9 +6539,6 @@ void OpenAll(void) InitMixer(); print_timestamp_time("[init setup/config stuff (6)]"); - InitRND(NEW_RANDOMIZE); - InitSimpleRandom(NEW_RANDOMIZE); - InitJoysticks(); print_timestamp_time("[init setup/config stuff]"); @@ -6226,13 +6571,16 @@ void OpenAll(void) InitOverrideArtwork(); // needs to know current level directory print_timestamp_time("InitOverrideArtwork"); + InitArtworkIdentifier(); // needs to know current level directory + print_timestamp_time("InitArtworkIdentifier"); + InitImages(); // needs to know current level directory print_timestamp_time("InitImages"); - InitSound(NULL); // needs to know current level directory + InitSound(); // needs to know current level directory print_timestamp_time("InitSound"); - InitMusic(NULL); // needs to know current level directory + InitMusic(); // needs to know current level directory print_timestamp_time("InitMusic"); InitArtworkDone(); @@ -6258,11 +6606,26 @@ void OpenAll(void) ConvertLevels(); return; } - else if (global.create_images_dir) + else if (global.dumplevel_leveldir) + { + DumpLevels(); + return; + } + else if (global.dumptape_leveldir) + { + DumpTapes(); + return; + } + else if (global.create_sketch_images_dir) { CreateLevelSketchImages(); return; } + else if (global.create_collect_images_dir) + { + CreateCollectElementImages(); + return; + } InitNetworkServer(); @@ -6276,6 +6639,9 @@ void OpenAll(void) print_timestamp_done("OpenAll"); + if (setup.ask_for_remaining_tapes) + setup.ask_for_uploading_tapes = TRUE; + DrawMainMenu(); #if 0 @@ -6297,8 +6663,43 @@ void OpenAll(void) #endif } +static boolean WaitForApiThreads(void) +{ + DelayCounter thread_delay = { 10000 }; + + if (program.api_thread_count == 0) + return TRUE; + + // deactivate global animations (not accessible in game state "loading") + setup.global_animations = FALSE; + + // set game state to "loading" to be able to show busy animation + SetGameStatus(GAME_MODE_LOADING); + + ResetDelayCounter(&thread_delay); + + // wait for threads to finish (and fail on timeout) + while (program.api_thread_count > 0) + { + if (DelayReached(&thread_delay)) + { + Error("failed waiting for threads - TIMEOUT"); + + return FALSE; + } + + UPDATE_BUSY_STATE(); + + Delay(20); + } + + return TRUE; +} + void CloseAllAndExit(int exit_value) { + WaitForApiThreads(); + StopSounds(); FreeAllSounds(); FreeAllMusic(); diff --git a/src/libgame/Makefile b/src/libgame/Makefile index 26f31160..246655f5 100644 --- a/src/libgame/Makefile +++ b/src/libgame/Makefile @@ -22,6 +22,8 @@ SRCS = system.c \ image.c \ random.c \ hash.c \ + http.c \ + base64.c \ setup.c \ misc.c \ sdl.c \ @@ -39,6 +41,8 @@ OBJS = system.o \ image.o \ random.o \ hash.o \ + http.o \ + base64.o \ setup.o \ misc.o \ sdl.o \ diff --git a/src/libgame/base64.c b/src/libgame/base64.c new file mode 100644 index 00000000..c602fa06 --- /dev/null +++ b/src/libgame/base64.c @@ -0,0 +1,194 @@ +// ============================================================================ +// Artsoft Retro-Game Library +// ---------------------------------------------------------------------------- +// (c) 1995-2021 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// https://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// base64.c +// ============================================================================ + +/* + + https://github.com/superwills/NibbleAndAHalf + base64.h -- Fast base64 encoding and decoding. + version 1.0.0, April 17, 2013 143a + + Copyright (C) 2013 William Sherif + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + William Sherif + will.sherif@gmail.com + + YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz + +*/ + +// ---------------------------------------------------------------------------- +// Base64 encoder/decoder code was altered for integration in Rocks'n'Diamonds +// ---------------------------------------------------------------------------- + +#include +#include + +#include "base64.h" + + +static const char *b64encode = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int base64_encoded_size(int unencoded_size) +{ + int mod = unencoded_size % 3; + int pad = (mod > 0 ? 3 - mod : 0); + + return 4 * (unencoded_size + pad) / 3 + 1; +} + +void base64_encode(char *encoded_data, + const void *unencoded_ptr, int unencoded_size) +{ + const byte *unencoded_data = (const byte *)unencoded_ptr; + char *ptr = encoded_data; + int i; + + int mod = unencoded_size % 3; + int pad = (mod > 0 ? 3 - mod : 0); + + for (i = 0; i <= unencoded_size - 3; i += 3) + { + byte byte0 = unencoded_data[i]; + byte byte1 = unencoded_data[i + 1]; + byte byte2 = unencoded_data[i + 2]; + + *ptr++ = b64encode[byte0 >> 2]; + *ptr++ = b64encode[((byte0 & 0x03) << 4) + (byte1 >> 4)]; + *ptr++ = b64encode[((byte1 & 0x0f) << 2) + (byte2 >> 6)]; + *ptr++ = b64encode[byte2 & 0x3f]; + } + + if (pad == 1) + { + byte byte0 = unencoded_data[i]; + byte byte1 = unencoded_data[i + 1]; + + *ptr++ = b64encode[byte0 >> 2]; + *ptr++ = b64encode[((byte0 & 0x03) << 4) + (byte1 >> 4)]; + *ptr++ = b64encode[((byte1 & 0x0f) << 2)]; + *ptr++ = '='; + } + else if (pad == 2) + { + byte byte0 = unencoded_data[i]; + + *ptr++ = b64encode[byte0 >> 2]; + *ptr++ = b64encode[(byte0 & 0x03) << 4]; + *ptr++ = '='; + *ptr++ = '='; + } + + *ptr++= '\0'; +} + +static const byte b64decode[] = +{ + 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, // 16 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, // 32 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, // 48 + + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, // 80 + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, // 112 + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176 + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 240 +}; + +int base64_decoded_size(const char *encoded_data) +{ + int encoded_size = strlen(encoded_data); + + if (encoded_size < 2) + return 0; + + int pad = 0; + + if (encoded_data[encoded_size - 1] == '=') + pad++; + if (encoded_data[encoded_size - 2] == '=') + pad++; + + return 3 * encoded_size / 4 - pad; +} + +void base64_decode(byte *decoded_data, const char *encoded_ptr) +{ + const byte *encoded_data = (const byte *)encoded_ptr; + byte *ptr = decoded_data; + int encoded_size = strlen(encoded_ptr); + int i; + + if (encoded_size < 2) + return; + + int pad = 0; + + if (encoded_data[encoded_size - 1] == '=') + pad++; + if (encoded_data[encoded_size - 2] == '=') + pad++; + + for (i = 0; i <= encoded_size - 4 - pad; i += 4) + { + byte byte0 = b64decode[encoded_data[i]]; + byte byte1 = b64decode[encoded_data[i + 1]]; + byte byte2 = b64decode[encoded_data[i + 2]]; + byte byte3 = b64decode[encoded_data[i + 3]]; + + *ptr++ = (byte0 << 2) | (byte1 >> 4); + *ptr++ = (byte1 << 4) | (byte2 >> 2); + *ptr++ = (byte2 << 6) | (byte3); + } + + if (pad == 1) + { + byte byte0 = b64decode[encoded_data[i]]; + byte byte1 = b64decode[encoded_data[i + 1]]; + byte byte2 = b64decode[encoded_data[i + 2]]; + + *ptr++ = (byte0 << 2) | (byte1 >> 4); + *ptr++ = (byte1 << 4) | (byte2 >> 2); + } + else if (pad == 2) + { + byte byte0 = b64decode[encoded_data[i]]; + byte byte1 = b64decode[encoded_data[i + 1]]; + + *ptr++ = (byte0 << 2) | (byte1 >> 4); + } +} diff --git a/src/libgame/base64.h b/src/libgame/base64.h new file mode 100644 index 00000000..3c76e6a0 --- /dev/null +++ b/src/libgame/base64.h @@ -0,0 +1,24 @@ +// ============================================================================ +// Artsoft Retro-Game Library +// ---------------------------------------------------------------------------- +// (c) 1995-2021 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// https://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// base64.h +// ============================================================================ + +#ifndef BASE64_H +#define BASE64_H + +#include "system.h" + + +int base64_encoded_size(int); +int base64_decoded_size(const char *); + +void base64_encode(char *, const void *, int); +void base64_decode(byte *, const char *); + +#endif diff --git a/src/libgame/gadgets.c b/src/libgame/gadgets.c index 6c394d7a..b917b43d 100644 --- a/src/libgame/gadgets.c +++ b/src/libgame/gadgets.c @@ -38,6 +38,8 @@ static struct GadgetInfo *gadget_list_last_entry = NULL; static struct GadgetInfo *last_info_gi = NULL; static int next_free_gadget_id = 1; static boolean gadget_id_wrapped = FALSE; +static int gadget_screen_border_right = -1; +static int gadget_screen_border_bottom = -1; static void (*PlayGadgetSoundActivating)(void) = NULL; static void (*PlayGadgetSoundSelecting)(void) = NULL; @@ -50,6 +52,30 @@ void InitGadgetsSoundCallback(void (*activating_function)(void), PlayGadgetSoundSelecting = selecting_function; } +void InitGadgetScreenBorders(int border_right, int border_bottom) +{ + gadget_screen_border_right = border_right; + gadget_screen_border_bottom = border_bottom; +} + +static int getGadgetScreenBorderRight(void) +{ + if (gadget_screen_border_right < gfx.sx || + gadget_screen_border_right > gfx.sx + gfx.sxsize) + return gfx.sx + gfx.sxsize; + + return gadget_screen_border_right; +} + +static int getGadgetScreenBorderBottom(void) +{ + if (gadget_screen_border_bottom < gfx.sy || + gadget_screen_border_bottom > gfx.sy + gfx.sysize) + return gfx.sy + gfx.sysize; + + return gadget_screen_border_bottom; +} + static struct GadgetInfo *getGadgetInfoFromGadgetID(int id) { struct GadgetInfo *gi = gadget_list_first_entry; @@ -130,6 +156,16 @@ static struct GadgetInfo *getGadgetInfoFromMousePosition(int mx, int my, return gi; } + // full text areas may overlap other active gadgets, so check them first + for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next) + { + if (gi->mapped && gi->active && + gi->type & GD_TYPE_TEXT_AREA && gi->textarea.full_open && + mx >= gi->textarea.full_x && mx < gi->textarea.full_x + gi->width && + my >= gi->textarea.full_y && my < gi->textarea.full_y + gi->height) + return gi; + } + // check all other gadgets for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next) { @@ -238,6 +274,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) { case GD_TYPE_NORMAL_BUTTON: case GD_TYPE_CHECK_BUTTON: + case GD_TYPE_CHECK_BUTTON_2: case GD_TYPE_RADIO_BUTTON: BlitBitmapOnBackground(gd->bitmap, drawto, @@ -281,7 +318,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) border_x, gi->height, gi->x, gi->y); // middle part of gadget - for (i=0; i < gi->textbutton.size; i++) + for (i = 0; i < gi->textbutton.size; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y, font_width, gi->height, gi->x + border_x + i * font_width, gi->y); @@ -309,15 +346,19 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) char text[MAX_GADGET_TEXTSIZE + 1]; int font_nr = (pressed ? gi->font_active : gi->font); int font_width = getFontWidth(font_nr); + int font_height = getFontHeight(font_nr); + struct FontBitmapInfo *font = getFontBitmapInfo(font_nr); int border_x = gi->border.xsize; int border_y = gi->border.ysize; + int text_x = gi->x + font->draw_xoffset; + int text_y = gi->y + font->draw_yoffset; // left part of gadget BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y, border_x, gi->height, gi->x, gi->y); // middle part of gadget - for (i=0; i < gi->textinput.size + 1; i++) + for (i = 0; i < gi->textinput.size + 1; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y, font_width, gi->height, gi->x + border_x + i * font_width, gi->y); @@ -332,6 +373,15 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) strcpy(text, gi->textinput.value); strcat(text, " "); + // dirty workaround to erase text if input gadget font has draw offset + if (font->draw_xoffset != 0 || font->draw_yoffset != 0) + for (i = 0; i < gi->textinput.size + 1; i++) + BlitBitmapOnBackground(gd->bitmap, drawto, + gd->x + border_x, gd->y + border_y, + font_width, font_height, + text_x + border_x + i * font_width, + text_y + border_y); + // gadget text value DrawTextExt(drawto, gi->x + border_x, gi->y + border_y, text, @@ -362,70 +412,114 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) int border_x = gi->border.xsize; int border_y = gi->border.ysize; int gd_height = 2 * border_y + font_height; + int x = gi->x; + int y = gi->y; + int width = gi->width; + int height = gi->height; + int xsize = gi->textarea.xsize; + int ysize = gi->textarea.ysize; + + if (gi->textarea.cropped) + { + if (pressed) + { + x = gi->textarea.full_x; + y = gi->textarea.full_y; + + if (!gi->textarea.full_open) + { + gi->textarea.full_open = TRUE; + + // save background under fully opened text area + BlitBitmap(drawto, gfx.field_save_buffer, + gi->textarea.full_x, gi->textarea.full_y, + gi->width, gi->height, + gi->textarea.full_x, gi->textarea.full_y); + } + } + else + { + width = gi->textarea.crop_width; + height = gi->textarea.crop_height; + xsize = gi->textarea.crop_xsize; + ysize = gi->textarea.crop_ysize; + + if (gi->textarea.full_open) + { + gi->textarea.full_open = FALSE; + + // restore background under fully opened text area + BlitBitmap(gfx.field_save_buffer, drawto, + gi->textarea.full_x, gi->textarea.full_y, + gi->width, gi->height, + gi->textarea.full_x, gi->textarea.full_y); + } + } + } // top left part of gadget border BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y, - border_x, border_y, gi->x, gi->y); + border_x, border_y, x, y); // top middle part of gadget border - for (i=0; i < gi->textarea.xsize; i++) + for (i = 0; i < xsize; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y, font_width, border_y, - gi->x + border_x + i * font_width, gi->y); + x + border_x + i * font_width, y); // top right part of gadget border BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + gi->border.width - border_x, gd->y, border_x, border_y, - gi->x + gi->width - border_x, gi->y); + x + width - border_x, y); // left and right part of gadget border for each row - for (i=0; i < gi->textarea.ysize; i++) + for (i = 0; i < ysize; i++) { BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y, border_x, font_height, - gi->x, gi->y + border_y + i * font_height); + x, y + border_y + i * font_height); BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + gi->border.width - border_x, gd->y + border_y, border_x, font_height, - gi->x + gi->width - border_x, - gi->y + border_y + i * font_height); + x + width - border_x, + y + border_y + i * font_height); } // bottom left part of gadget border BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + gd_height - border_y, border_x, border_y, - gi->x, gi->y + gi->height - border_y); + x, y + height - border_y); // bottom middle part of gadget border - for (i=0; i < gi->textarea.xsize; i++) + for (i = 0; i < xsize; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y + gd_height - border_y, font_width, border_y, - gi->x + border_x + i * font_width, - gi->y + gi->height - border_y); + x + border_x + i * font_width, + y + height - border_y); // bottom right part of gadget border BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + gi->border.width - border_x, gd->y + gd_height - border_y, border_x, border_y, - gi->x + gi->width - border_x, - gi->y + gi->height - border_y); + x + width - border_x, + y + height - border_y); ClearRectangleOnBackground(drawto, - gi->x + border_x, - gi->y + border_y, - gi->width - 2 * border_x, - gi->height - 2 * border_y); + x + border_x, + y + border_y, + width - 2 * border_x, + height - 2 * border_y); // gadget text value - DrawTextBuffer(gi->x + border_x, gi->y + border_y, gi->textarea.value, - font_nr, gi->textarea.xsize, -1, gi->textarea.ysize, 0, - BLIT_ON_BACKGROUND, FALSE, FALSE, FALSE); + DrawTextArea(x + border_x, y + border_y, gi->textarea.value, + font_nr, xsize, -1, ysize, 0, + BLIT_ON_BACKGROUND, FALSE, FALSE, FALSE); cursor_letter = gi->textarea.value[gi->textarea.cursor_position]; cursor_string[0] = (cursor_letter != '\0' ? cursor_letter : ' '); @@ -434,8 +528,8 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) // draw cursor, if active if (pressed) DrawTextExt(drawto, - gi->x + border_x + gi->textarea.cursor_x * font_width, - gi->y + border_y + gi->textarea.cursor_y * font_height, + x + border_x + gi->textarea.cursor_x * font_width, + y + border_y + gi->textarea.cursor_y * font_height, cursor_string, font_nr, BLIT_INVERSE); } @@ -461,7 +555,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) border_x, gi->height, gi->x, gi->y); // middle part of gadget - for (i=0; i < gi->selectbox.size; i++) + for (i = 0; i < gi->selectbox.size; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y, font_width, gi->height, gi->x + border_x + i * font_width, gi->y); @@ -514,7 +608,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) gi->selectbox.x, gi->selectbox.y); // top middle part of gadget border - for (i=0; i < gi->selectbox.size; i++) + for (i = 0; i < gi->selectbox.size; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y, font_width, border_y, gi->selectbox.x + border_x + i * font_width, @@ -535,7 +629,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) gi->selectbox.y); // left and right part of gadget border for each row - for (i=0; i < gi->selectbox.num_values; i++) + for (i = 0; i < gi->selectbox.num_values; i++) { BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y, border_x, font_height, @@ -557,7 +651,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) gi->selectbox.y + box_height - border_y); // bottom middle part of gadget border - for (i=0; i < gi->selectbox.size; i++) + for (i = 0; i < gi->selectbox.size; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y + gi->height - border_y, @@ -588,7 +682,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) gi->selectbox.height - 2 * border_y); // selectbox text values - for (i=0; i < gi->selectbox.num_values; i++) + for (i = 0; i < gi->selectbox.num_values; i++) { int mask_mode = BLIT_MASKED; @@ -663,7 +757,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) xpos, ypos); // middle part of gadget - for (i=0; i < num_steps; i++) + for (i = 0; i < num_steps; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + gi->border.ysize, gi->width, design_body, @@ -710,7 +804,7 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct) xpos, ypos); // middle part of gadget - for (i=0; i < num_steps; i++) + for (i = 0; i < num_steps; i++) BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + gi->border.xsize, gd->y, design_body, gi->height, @@ -828,6 +922,7 @@ static void DrawGadget_OverlayTouchButton(struct GadgetInfo *gi) { case GD_TYPE_NORMAL_BUTTON: case GD_TYPE_CHECK_BUTTON: + case GD_TYPE_CHECK_BUTTON_2: case GD_TYPE_RADIO_BUTTON: SDL_SetTextureAlphaMod(gd->bitmap->texture_masked, alpha); SDL_SetTextureBlendMode(gd->bitmap->texture_masked, SDL_BLENDMODE_BLEND); @@ -879,7 +974,7 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap) { int tag = first_tag; - if (gi == NULL || gi->deactivated) + if (gi == NULL) return; while (tag != GDI_END) @@ -1260,7 +1355,7 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap) int border_xsize = gi->border.xsize; int border_ysize = gi->border.ysize; int button_size = gi->border.xsize_selectbutton; - int bottom_screen_border = gfx.sy + gfx.sysize - font_height; + int bottom_screen_border = getGadgetScreenBorderBottom(); Bitmap *src_bitmap; int src_x, src_y; @@ -1358,6 +1453,8 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap) int font_height = getFontHeight(font_nr); int border_xsize = gi->border.xsize; int border_ysize = gi->border.ysize; + int right_screen_border = getGadgetScreenBorderRight(); + int bottom_screen_border = getGadgetScreenBorderBottom(); if (gi->width == 0 || gi->height == 0) { @@ -1369,6 +1466,42 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap) gi->textarea.xsize = (gi->width - 2 * border_xsize) / font_width; gi->textarea.ysize = (gi->height - 2 * border_ysize) / font_height; } + + gi->textarea.full_x = gi->x; + gi->textarea.full_y = gi->y; + gi->textarea.crop_width = gi->width; + gi->textarea.crop_height = gi->height; + gi->textarea.crop_xsize = gi->textarea.xsize; + gi->textarea.crop_ysize = gi->textarea.ysize; + + gi->textarea.cropped = FALSE; + + if (gi->x + gi->width > right_screen_border) + { + gi->textarea.full_x = MAX(0, right_screen_border - gi->width); + gi->textarea.crop_width = right_screen_border - gi->x; + gi->textarea.crop_xsize = + (gi->textarea.crop_width - 2 * border_xsize) / font_width; + gi->textarea.crop_width = + 2 * border_xsize + gi->textarea.crop_xsize * font_width; + + gi->textarea.cropped = TRUE; + } + + if (gi->y + gi->height > bottom_screen_border) + { + gi->textarea.full_y = MAX(0, bottom_screen_border - gi->height); + gi->textarea.crop_height = bottom_screen_border - gi->y; + gi->textarea.crop_ysize = + (gi->textarea.crop_height - 2 * border_ysize) / font_height; + gi->textarea.crop_height = + 2 * border_ysize + gi->textarea.crop_ysize * font_height; + + gi->textarea.cropped = TRUE; + } + + // always start with unselected text area (which is potentially cropped) + gi->textarea.full_open = FALSE; } } @@ -1629,8 +1762,7 @@ void ClickOnGadget(struct GadgetInfo *gi, int button) boolean HandleGadgets(int mx, int my, int button) { - static unsigned int pressed_delay = 0; - static unsigned int pressed_delay_value = GADGET_FRAME_DELAY; + static DelayCounter pressed_delay = { GADGET_FRAME_DELAY }; static int last_button = 0; static int last_mx = 0, last_my = 0; static int pressed_mx = 0, pressed_my = 0; @@ -1756,7 +1888,7 @@ boolean HandleGadgets(int mx, int my, int button) (button != 0 && last_gi != NULL && new_gi == last_gi); gadget_pressed_delay_reached = - DelayReached(&pressed_delay, pressed_delay_value); + DelayReached(&pressed_delay); gadget_released = (release_event && last_gi != NULL); gadget_released_inside = (gadget_released && new_gi == last_gi); @@ -1860,8 +1992,10 @@ boolean HandleGadgets(int mx, int my, int button) else if (gi->type & GD_TYPE_TEXT_AREA && button != 0 && !motion_status) { int old_cursor_position = gi->textarea.cursor_position; - int x = (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font); - int y = (my - gi->y - gi->border.ysize) / getFontHeight(gi->font); + int gadget_x = mx - gi->textarea.full_x - gi->border.xsize; + int gadget_y = my - gi->textarea.full_y - gi->border.ysize; + int x = gadget_x / getFontWidth(gi->font); + int y = gadget_y / getFontHeight(gi->font); x = (x < 0 ? 0 : x >= gi->textarea.xsize ? gi->textarea.xsize - 1 : x); y = (y < 0 ? 0 : y >= gi->textarea.ysize ? gi->textarea.ysize - 1 : y); @@ -1934,15 +2068,15 @@ boolean HandleGadgets(int mx, int my, int button) if (gadget_pressed) // gadget pressed the first time { // initialize delay counter - DelayReached(&pressed_delay, 0); + ResetDelayCounter(&pressed_delay); // start gadget delay with longer delay after first click on gadget - pressed_delay_value = GADGET_FRAME_DELAY_FIRST; + pressed_delay.value = GADGET_FRAME_DELAY_FIRST; } else // gadget hold pressed for some time { // after first repeated gadget click, continue with shorter delay value - pressed_delay_value = GADGET_FRAME_DELAY; + pressed_delay.value = GADGET_FRAME_DELAY; } if (gi->type & GD_TYPE_SCROLLBAR && !gadget_dragging) @@ -2159,7 +2293,11 @@ boolean HandleGadgets(int mx, int my, int button) boolean deactivate_gadget = TRUE; boolean gadget_changed = TRUE; - if (gi->type & GD_TYPE_SELECTBOX) + if (gi->type == GD_TYPE_CHECK_BUTTON_2) + { + gi->checked = !gi->checked; + } + else if (gi->type & GD_TYPE_SELECTBOX) { if (keep_selectbox_open || mouse_released_where_pressed || @@ -2354,6 +2492,18 @@ boolean HandleGadgetsKeyInput(Key key) strcpy(text, gi->textinput.value); strcpy(&gi->textinput.value[cursor_pos], &text[cursor_pos + 1]); + DrawGadget(gi, DG_PRESSED, gi->direct_draw); + } + else if (key == KSYM_Home && cursor_pos > 0) + { + gi->textinput.cursor_position = 0; + + DrawGadget(gi, DG_PRESSED, gi->direct_draw); + } + else if (key == KSYM_End && cursor_pos < text_length) + { + gi->textinput.cursor_position = text_length; + DrawGadget(gi, DG_PRESSED, gi->direct_draw); } } @@ -2363,6 +2513,7 @@ boolean HandleGadgetsKeyInput(Key key) int text_length = strlen(gi->textarea.value); int area_ysize = gi->textarea.ysize; int cursor_x_pref = gi->textarea.cursor_x_preferred; + int cursor_x = gi->textarea.cursor_x; int cursor_y = gi->textarea.cursor_y; int cursor_pos = gi->textarea.cursor_position; char letter = getCharFromKey(key); @@ -2414,6 +2565,25 @@ boolean HandleGadgetsKeyInput(Key key) strcpy(text, gi->textarea.value); strcpy(&gi->textarea.value[cursor_pos], &text[cursor_pos + 1]); + DrawGadget(gi, DG_PRESSED, gi->direct_draw); + } + else if (key == KSYM_Home && cursor_x > 0) + { + setTextAreaCursorPosition(gi, gi->textarea.cursor_position - cursor_x); + + DrawGadget(gi, DG_PRESSED, gi->direct_draw); + } + else if (key == KSYM_End && cursor_pos < text_length) + { + int last_cursor_pos = cursor_pos; + + while (gi->textarea.value[cursor_pos] != '\0' && + gi->textarea.value[cursor_pos] != '\n') + cursor_pos++; + + setTextAreaCursorPosition(gi, gi->textarea.cursor_position + cursor_pos - + last_cursor_pos); + DrawGadget(gi, DG_PRESSED, gi->direct_draw); } } diff --git a/src/libgame/gadgets.h b/src/libgame/gadgets.h index aaf4de16..6a27f988 100644 --- a/src/libgame/gadgets.h +++ b/src/libgame/gadgets.h @@ -22,18 +22,20 @@ #define GD_TYPE_NORMAL_BUTTON (1 << 0) #define GD_TYPE_TEXT_BUTTON (1 << 1) #define GD_TYPE_CHECK_BUTTON (1 << 2) -#define GD_TYPE_RADIO_BUTTON (1 << 3) -#define GD_TYPE_DRAWING_AREA (1 << 4) -#define GD_TYPE_TEXT_INPUT_ALPHANUMERIC (1 << 5) -#define GD_TYPE_TEXT_INPUT_NUMERIC (1 << 6) -#define GD_TYPE_TEXT_AREA (1 << 7) -#define GD_TYPE_SELECTBOX (1 << 8) -#define GD_TYPE_SCROLLBAR_VERTICAL (1 << 9) -#define GD_TYPE_SCROLLBAR_HORIZONTAL (1 << 10) +#define GD_TYPE_CHECK_BUTTON_2 (1 << 3) +#define GD_TYPE_RADIO_BUTTON (1 << 4) +#define GD_TYPE_DRAWING_AREA (1 << 5) +#define GD_TYPE_TEXT_INPUT_ALPHANUMERIC (1 << 6) +#define GD_TYPE_TEXT_INPUT_NUMERIC (1 << 7) +#define GD_TYPE_TEXT_AREA (1 << 8) +#define GD_TYPE_SELECTBOX (1 << 9) +#define GD_TYPE_SCROLLBAR_VERTICAL (1 << 10) +#define GD_TYPE_SCROLLBAR_HORIZONTAL (1 << 11) #define GD_TYPE_BUTTON (GD_TYPE_NORMAL_BUTTON | \ GD_TYPE_TEXT_BUTTON | \ GD_TYPE_CHECK_BUTTON | \ + GD_TYPE_CHECK_BUTTON_2 | \ GD_TYPE_RADIO_BUTTON) #define GD_TYPE_SCROLLBAR (GD_TYPE_SCROLLBAR_VERTICAL | \ GD_TYPE_SCROLLBAR_HORIZONTAL) @@ -186,6 +188,15 @@ struct GadgetTextArea int cursor_x_preferred; // "preferred" x cursor position int size; // maximal size of input text int xsize, ysize; // size of text area (in chars) + + // automatically determined values + boolean cropped; // text area cropped to fit viewport + int full_x, full_y; // text area position when not cropped + int crop_width, crop_height; // size of text area when cropped + int crop_xsize, crop_ysize; // size of text area when cropped + + // runtime values + boolean full_open; // opening state of text area }; struct GadgetSelectbox @@ -274,6 +285,7 @@ struct GadgetInfo void InitGadgetsSoundCallback(void (*activating_function)(void), void (*selecting_function)(void)); +void InitGadgetScreenBorders(int, int); struct GadgetInfo *CreateGadget(int, ...); void FreeGadget(struct GadgetInfo *); diff --git a/src/libgame/hash.c b/src/libgame/hash.c index c7f4d739..e021da5c 100644 --- a/src/libgame/hash.c +++ b/src/libgame/hash.c @@ -43,7 +43,7 @@ struct hashtable * create_hashtable(unsigned int minsize, float maxloadfactor, unsigned int (*hashf) (void*), - int (*eqf) (void*,void*)) + int (*eqf) (void*, void*)) { struct hashtable *h; unsigned int i, size = 1u; @@ -70,7 +70,7 @@ create_hashtable(unsigned int minsize, float maxloadfactor, return NULL; } - for (i=0; i < size; i++) + for (i = 0; i < size; i++) h->table[i] = NULL; h->tablelength = size; @@ -134,7 +134,7 @@ hashtable_expand(struct hashtable *h) while ((e = h->table[i]) != NULL) { h->table[i] = e->next; - index = indexFor(newsize,e->h); + index = indexFor(newsize, e->h); e->next = newtable[index]; newtable[index] = e; } @@ -160,7 +160,7 @@ hashtable_expand(struct hashtable *h) { for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) { - index = indexFor(newsize,e->h); + index = indexFor(newsize, e->h); if (index == i) { @@ -216,8 +216,8 @@ hashtable_insert(struct hashtable *h, void *k, void *v) return 0; } - e->h = hash(h,k); - index = indexFor(h->tablelength,e->h); + e->h = hash(h, k); + index = indexFor(h->tablelength, e->h); e->k = k; e->v = v; e->next = h->table[index]; @@ -233,8 +233,8 @@ hashtable_change(struct hashtable *h, void *k, void *v) struct entry *e; unsigned int hashvalue, index; - hashvalue = hash(h,k); - index = indexFor(h->tablelength,hashvalue); + hashvalue = hash(h, k); + index = indexFor(h->tablelength, hashvalue); e = h->table[index]; while (e != NULL) @@ -261,8 +261,8 @@ hashtable_search(struct hashtable *h, void *k) struct entry *e; unsigned int hashvalue, index; - hashvalue = hash(h,k); - index = indexFor(h->tablelength,hashvalue); + hashvalue = hash(h, k); + index = indexFor(h->tablelength, hashvalue); e = h->table[index]; while (e != NULL) @@ -287,7 +287,7 @@ hashtable_remove(struct hashtable *h, void *k) struct entry *e; struct entry **pE; void *v; - unsigned int index = indexFor(h->tablelength,hash(h,k)); + unsigned int index = indexFor(h->tablelength, hash(h, k)); pE = &(h->table[index]); e = *pE; @@ -379,7 +379,7 @@ hashtable_iterator(struct hashtable *h) } /*****************************************************************************/ -/* key - return the key of the (key,value) pair at the current position */ +/* key - return the key of the (key, value) pair at the current position */ void * hashtable_iterator_key(struct hashtable_itr *i) @@ -388,7 +388,7 @@ hashtable_iterator_key(struct hashtable_itr *i) } /*****************************************************************************/ -/* value - return the value of the (key,value) pair at the current position */ +/* value - return the value of the (key, value) pair at the current position */ void * hashtable_iterator_value(struct hashtable_itr *i) @@ -403,7 +403,7 @@ hashtable_iterator_value(struct hashtable_itr *i) int hashtable_iterator_advance(struct hashtable_itr *itr) { - unsigned int j,tablelength; + unsigned int j, tablelength; struct entry **table; struct entry *next; diff --git a/src/libgame/hash.h b/src/libgame/hash.h index 004d9c58..f87f8b90 100644 --- a/src/libgame/hash.h +++ b/src/libgame/hash.h @@ -134,7 +134,7 @@ struct hashtable_itr struct hashtable * create_hashtable(unsigned int minsize, float maxloadfactor, unsigned int (*hashfunction) (void*), - int (*key_eq_fn) (void*,void*)); + int (*key_eq_fn) (void*, void*)); /***************************************************************************** * hashtable_insert @@ -161,7 +161,7 @@ hashtable_insert(struct hashtable *h, void *k, void *v); #define DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \ static int fnname (struct hashtable *h, keytype *k, valuetype *v) \ { \ - return hashtable_insert(h,k,v); \ + return hashtable_insert(h, k, v); \ } /***************************************************************************** @@ -180,7 +180,7 @@ hashtable_change(struct hashtable *h, void *k, void *v); #define DEFINE_HASHTABLE_CHANGE(fnname, keytype, valuetype) \ static int fnname (struct hashtable *h, keytype *k, valuetype *v) \ { \ - return hashtable_change(h,k,v); \ + return hashtable_change(h, k, v); \ } /***************************************************************************** @@ -198,7 +198,7 @@ hashtable_search(struct hashtable *h, void *k); #define DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \ static valuetype * fnname (struct hashtable *h, keytype *k) \ { \ - return (valuetype *) (hashtable_search(h,k)); \ + return (valuetype *) (hashtable_search(h, k)); \ } /***************************************************************************** @@ -216,7 +216,7 @@ hashtable_remove(struct hashtable *h, void *k); #define DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \ static valuetype * fnname (struct hashtable *h, keytype *k) \ { \ - return (valuetype *) (hashtable_remove(h,k)); \ + return (valuetype *) (hashtable_remove(h, k)); \ } @@ -249,13 +249,13 @@ struct hashtable_itr * hashtable_iterator(struct hashtable *h); /*****************************************************************************/ -/* key - return the key of the (key,value) pair at the current position */ +/* key - return the key of the (key, value) pair at the current position */ void * hashtable_iterator_key(struct hashtable_itr *i); /*****************************************************************************/ -/* value - return the value of the (key,value) pair at the current position */ +/* value - return the value of the (key, value) pair at the current position */ void * hashtable_iterator_value(struct hashtable_itr *i); diff --git a/src/libgame/http.c b/src/libgame/http.c new file mode 100644 index 00000000..c83c91f7 --- /dev/null +++ b/src/libgame/http.c @@ -0,0 +1,423 @@ +// ============================================================================ +// Artsoft Retro-Game Library +// ---------------------------------------------------------------------------- +// (c) 1995-2021 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// https://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// http.c +// ============================================================================ + +#include + +#include "platform.h" + +#include "http.h" +#include "misc.h" + + +static char http_error[MAX_HTTP_ERROR_SIZE]; + +static void SetHttpError(char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsnprintf(http_error, MAX_HTTP_ERROR_SIZE, format, ap); + va_end(ap); +} + +char *GetHttpError(void) +{ + return http_error; +} + +void ConvertHttpRequestBodyToServerEncoding(struct HttpRequest *request) +{ + char *body_utf8 = getUTF8FromLatin1(request->body); + + strncpy(request->body, body_utf8, MAX_HTTP_BODY_SIZE); + request->body[MAX_HTTP_BODY_SIZE] = '\0'; + + checked_free(body_utf8); +} + +void ConvertHttpResponseBodyToClientEncoding(struct HttpResponse *response) +{ + char *body_latin1 = getLatin1FromUTF8(response->body); + + strncpy(response->body, body_latin1, MAX_HTTP_BODY_SIZE); + response->body[MAX_HTTP_BODY_SIZE] = '\0'; + + response->body_size = strlen(response->body); + + checked_free(body_latin1); +} + +static void SetHttpResponseToDefaults(struct HttpResponse *response) +{ + response->head[0] = '\0'; + response->body[0] = '\0'; + response->body_size = 0; + + response->status_code = 0; + response->status_text[0] = '\0'; +} + +struct HttpResponse *GetHttpResponseFromBuffer(void *buffer, int body_size) +{ + if (body_size > MAX_HTTP_BODY_SIZE) + return NULL; + + struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse)); + + SetHttpResponseToDefaults(response); + + memcpy(response->body, buffer, body_size); + response->body[body_size] = '\0'; + response->body_size = body_size; + + return response; +} + +static boolean SetHTTPResponseCode(struct HttpResponse *response, char *buffer) +{ + char *prefix = "HTTP/1.1 "; + char *prefix_start = strstr(buffer, prefix); + + if (prefix_start == NULL) + return FALSE; + + char *status_code_start = prefix_start + strlen(prefix); + char *status_code_end = strstr(status_code_start, " "); + + if (status_code_end == NULL) + return FALSE; + + int status_code_size = status_code_end - status_code_start; + + if (status_code_size != 3) // status code must have three digits + return FALSE; + + char status_code[status_code_size + 1]; + + strncpy(status_code, status_code_start, status_code_size); + status_code[status_code_size] = '\0'; + + response->status_code = atoi(status_code); + + char *status_text_start = status_code_end + 1; + char *status_text_end = strstr(status_text_start, "\r\n"); + + if (status_text_end == NULL) + return FALSE; + + int status_text_size = status_text_end - status_text_start; + + if (status_text_size > MAX_HTTP_ERROR_SIZE) + return FALSE; + + strncpy(response->status_text, status_text_start, status_text_size); + response->status_text[status_text_size] = '\0'; + + return TRUE; +} + +static boolean SetHTTPResponseHead(struct HttpResponse *response, char *buffer) +{ + char *separator = "\r\n\r\n"; + char *separator_start = strstr(buffer, separator); + + if (separator_start == NULL) + return FALSE; + + int head_size = separator_start - buffer; + + if (head_size > MAX_HTTP_HEAD_SIZE) + return FALSE; + + strncpy(response->head, buffer, head_size); + response->head[head_size] = '\0'; + + return TRUE; +} + +static boolean SetHTTPResponseBody(struct HttpResponse *response, char *buffer, + int buffer_size) +{ + char *separator = "\r\n\r\n"; + char *separator_start = strstr(buffer, separator); + + if (separator_start == NULL) + return FALSE; + + int separator_size = strlen(separator); + int full_head_size = separator_start + separator_size - buffer; + int body_size = buffer_size - full_head_size; + + if (body_size > MAX_HTTP_BODY_SIZE) + return FALSE; + + memcpy(response->body, buffer + full_head_size, body_size); + response->body[body_size] = '\0'; + response->body_size = body_size; + + return TRUE; +} + +static int GetHttpResponse(TCPsocket socket, char *buffer, int max_buffer_size) +{ + char *buffer_ptr = buffer; + int buffer_left = max_buffer_size; + int buffer_size = 0; + int response_size = 0; + + while (1) + { + // read as many bytes to the buffer as possible + int bytes = SDLNet_TCP_Recv(socket, buffer_ptr, buffer_left); + + if (bytes <= 0) + { + SetHttpError("receiving response from server failed"); + + return -1; + } + + buffer_ptr += bytes; + buffer_size += bytes; + buffer_left -= bytes; + + // check if response size was already determined + if (response_size > 0) + { + // check if response data was completely received + if (buffer_size >= response_size) + break; + + // continue reading response body from server + continue; + } + + char *separator = "\r\n\r\n"; + char *separator_start = strstr(buffer, separator); + int separator_size = strlen(separator); + + // check if response header was completely received + if (separator_start == NULL) + { + // continue reading response header from server + continue; + } + + char *content_length = "Content-Length: "; + char *content_length_start = strstr(buffer, content_length); + int head_size = separator_start - buffer; + + // check if response header contains content length header + if (content_length_start == NULL || + content_length_start >= buffer + head_size) + { + SetHttpError("receiving 'Content-Length' header from server failed"); + + return -1; + } + + char *content_length_value = content_length_start + strlen(content_length); + char *content_length_end = strstr(content_length_value, "\r\n"); + + // check if content length header has line termination + if (content_length_end == NULL) + { + SetHttpError("receiving 'Content-Length' value from server failed"); + + return -1; + } + + int value_len = content_length_end - content_length_value; + int max_value_len = 10; + + // check if content length header has valid size + if (value_len > max_value_len) + { + SetHttpError("received invalid 'Content-Length' value from server"); + + return -1; + } + + char value_str[value_len + 1]; + + strncpy(value_str, content_length_value, value_len); + value_str[value_len] = '\0'; + + int body_size = atoi(value_str); + + response_size = head_size + separator_size + body_size; + + // check if response data was completely received + if (buffer_size >= response_size) + break; + } + + return buffer_size; +} + +static boolean DoHttpRequestExt(struct HttpRequest *request, + struct HttpResponse *response, + char *send_buffer, + char *recv_buffer, + int max_http_buffer_size, + SDLNet_SocketSet *socket_set, + TCPsocket *socket) +{ + IPaddress ip; + int server_host; + + SetHttpResponseToDefaults(response); + + *socket_set = SDLNet_AllocSocketSet(1); + + if (*socket_set == NULL) + { + SetHttpError("cannot allocate socket set"); + + return FALSE; + } + + SDLNet_ResolveHost(&ip, request->hostname, request->port); + + if (ip.host == INADDR_NONE) + { + SetHttpError("cannot resolve hostname '%s'", request->hostname); + + return FALSE; + } + + server_host = SDLNet_Read32(&ip.host); + + Debug("network:http", "trying to connect to server at %d.%d.%d.%d ...", + (server_host >> 24) & 0xff, + (server_host >> 16) & 0xff, + (server_host >> 8) & 0xff, + (server_host >> 0) & 0xff); + + *socket = SDLNet_TCP_Open(&ip); + + if (*socket == NULL) + { + SetHttpError("cannot connect to host '%s': %s", request->hostname, + SDLNet_GetError()); + + return FALSE; + } + + if (SDLNet_TCP_AddSocket(*socket_set, *socket) == -1) + { + SetHttpError("cannot add socket to socket set"); + + return FALSE; + } + + Debug("network:http", "successfully connected to server"); + + snprintf(request->head, MAX_HTTP_HEAD_SIZE, + "%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "X-Requested-With: XMLHttpRequest\r\n" + "Content-Type: application/json\r\n" + "Connection: close\r\n" + "Content-Length: %d\r\n", + request->method, + request->uri, + request->hostname, + (int)strlen(request->body)); + + snprintf(send_buffer, max_http_buffer_size, + "%s\r\n%s", request->head, request->body); + + Debug("network:http", "client request:\n--- snip ---\n%s\n--- snip ---", + send_buffer); + + int send_bytes = SDLNet_TCP_Send(*socket, send_buffer, strlen(send_buffer)); + + if (send_bytes != strlen(send_buffer)) + { + SetHttpError("sending request to server failed"); + + return FALSE; + } + + int recv_bytes = GetHttpResponse(*socket, recv_buffer, max_http_buffer_size); + + if (recv_bytes <= 0) + { + // HTTP error already set in GetHttpResponse() + + return FALSE; + } + + recv_buffer[recv_bytes] = '\0'; + + Debug("network:http", "server response:\n--- snip ---\n%s\n--- snip ---", + recv_buffer); + + if (!SetHTTPResponseCode(response, recv_buffer)) + { + SetHttpError("malformed HTTP response"); + + return FALSE; + } + + if (!SetHTTPResponseHead(response, recv_buffer)) + { + SetHttpError("invalid HTTP response header"); + + return FALSE; + } + + if (!SetHTTPResponseBody(response, recv_buffer, recv_bytes)) + { + SetHttpError("invalid HTTP response body"); + + return FALSE; + } + + Debug("network:http", "server response: %d %s", + response->status_code, + response->status_text); + + return TRUE; +} + +boolean DoHttpRequest(struct HttpRequest *request, + struct HttpResponse *response) +{ + int max_http_buffer_size = MAX_HTTP_HEAD_SIZE + 2 + MAX_HTTP_BODY_SIZE + 1; + char *send_buffer = checked_malloc(max_http_buffer_size); + char *recv_buffer = checked_malloc(max_http_buffer_size); + SDLNet_SocketSet socket_set = NULL; + TCPsocket socket = NULL; + + boolean success = DoHttpRequestExt(request, response, + send_buffer, recv_buffer, + max_http_buffer_size, + &socket_set, &socket); + if (socket_set != NULL) + { + if (socket != NULL) + { + SDLNet_TCP_DelSocket(socket_set, socket); + SDLNet_TCP_Close(socket); + } + + SDLNet_FreeSocketSet(socket_set); + } + + checked_free(send_buffer); + checked_free(recv_buffer); + + runtime.use_api_server = success; + + return success; +} diff --git a/src/libgame/http.h b/src/libgame/http.h new file mode 100644 index 00000000..2523bc72 --- /dev/null +++ b/src/libgame/http.h @@ -0,0 +1,52 @@ +// ============================================================================ +// Artsoft Retro-Game Library +// ---------------------------------------------------------------------------- +// (c) 1995-2021 by Artsoft Entertainment +// Holger Schemel +// info@artsoft.org +// https://www.artsoft.org/ +// ---------------------------------------------------------------------------- +// http.h +// ============================================================================ + +#ifndef HTTP_H +#define HTTP_H + +#include "system.h" + +#define MAX_HTTP_HEAD_SIZE 4096 +#define MAX_HTTP_BODY_SIZE 1048576 +#define MAX_HTTP_ERROR_SIZE 1024 + +#define HTTP_SUCCESS(c) ((c) >= 200 && (c) < 300) + + +struct HttpRequest +{ + char head[MAX_HTTP_HEAD_SIZE + 1]; + char body[MAX_HTTP_BODY_SIZE + 1]; + + char *hostname; + int port; + char *method; + char *uri; +}; + +struct HttpResponse +{ + char head[MAX_HTTP_HEAD_SIZE + 1]; + char body[MAX_HTTP_BODY_SIZE + 1]; + int body_size; + + int status_code; + char status_text[MAX_HTTP_ERROR_SIZE + 1]; +}; + + +char *GetHttpError(void); +void ConvertHttpRequestBodyToServerEncoding(struct HttpRequest *); +void ConvertHttpResponseBodyToClientEncoding(struct HttpResponse *); +struct HttpResponse *GetHttpResponseFromBuffer(void *, int); +boolean DoHttpRequest(struct HttpRequest *, struct HttpResponse *); + +#endif diff --git a/src/libgame/image.c b/src/libgame/image.c index 1a61ff92..4cc613e7 100644 --- a/src/libgame/image.c +++ b/src/libgame/image.c @@ -139,13 +139,6 @@ char *getTokenFromImageID(int graphic) return (file_list != NULL ? file_list->token : NULL); } -char *getFilenameFromImageID(int graphic) -{ - struct FileInfo *file_list = getImageListEntryFromImageID(graphic); - - return (file_list != NULL ? file_list->filename : NULL); -} - int getImageIDFromToken(char *token) { struct FileInfo *file_list = image_info->file_list; @@ -398,9 +391,11 @@ void ScaleImage(int pos, int zoom_factor) return; if (zoom_factor != 1) + { ScaleBitmap(img_info->bitmaps, zoom_factor); - img_info->scaled_up = TRUE; + img_info->scaled_up = TRUE; + } } void FreeAllImages(void) diff --git a/src/libgame/image.h b/src/libgame/image.h index 8827257b..04952e74 100644 --- a/src/libgame/image.h +++ b/src/libgame/image.h @@ -15,7 +15,20 @@ #include "system.h" -// these bitmap pointers either point to allocated bitmaps or are NULL +// bitmap array positions for various element sizes, if available +// +// for any loaded image, the "standard" size (which represents the 32x32 pixel +// size for game elements) is always defined; other bitmap sizes may be NULL +// +// formats from 32x32 down to 1x1 are standard bitmap sizes for game elements +// (used in the game, in the level editor, in the level preview etc.) +// +// "CUSTOM" sizes for game elements (like 64x64) may be additionally created; +// all "OTHER" image sizes are stored if different from all other bitmap sizes, +// which may be used "as is" by global animations (as the "standard" size used +// normally may be wrong due to being scaled up or down to a different size if +// the same image contains game elements in a non-standard size) + #define IMG_BITMAP_32x32 0 #define IMG_BITMAP_16x16 1 #define IMG_BITMAP_8x8 2 @@ -23,17 +36,22 @@ #define IMG_BITMAP_2x2 4 #define IMG_BITMAP_1x1 5 #define IMG_BITMAP_CUSTOM 6 +#define IMG_BITMAP_OTHER 7 -#define NUM_IMG_BITMAPS 7 +#define NUM_IMG_BITMAPS 8 -// this bitmap pointer points to one of the above bitmaps (do not free it) -#define IMG_BITMAP_GAME 7 +// these bitmap pointers point to one of the above bitmaps (do not free them) +#define IMG_BITMAP_PTR_GAME 8 +#define IMG_BITMAP_PTR_ORIGINAL 9 -#define NUM_IMG_BITMAP_POINTERS 8 +#define NUM_IMG_BITMAP_POINTERS 10 // this bitmap pointer points to the bitmap with default image size #define IMG_BITMAP_STANDARD IMG_BITMAP_32x32 +// maximum number of statically and dynamically defined image files +#define MAX_IMAGE_FILES 1000000 + #define GET_BITMAP_ID_FROM_TILESIZE(x) ((x) == 1 ? IMG_BITMAP_1x1 : \ (x) == 2 ? IMG_BITMAP_2x2 : \ @@ -58,7 +76,6 @@ Bitmap **getBitmapsFromImageID(int); int getOriginalImageWidthFromImageID(int); int getOriginalImageHeightFromImageID(int); char *getTokenFromImageID(int); -char *getFilenameFromImageID(int); int getImageIDFromToken(char *); char *getImageConfigFilename(void); int getImageListPropertyMappingSize(void); diff --git a/src/libgame/joystick.c b/src/libgame/joystick.c index 4a1bc7d5..14ba7d2c 100644 --- a/src/libgame/joystick.c +++ b/src/libgame/joystick.c @@ -21,71 +21,6 @@ // platform independent joystick functions // ============================================================================ -#define TRANSLATE_JOYSYMBOL_TO_JOYNAME 0 -#define TRANSLATE_JOYNAME_TO_JOYSYMBOL 1 - -static void translate_joyname(int *joysymbol, char **name, int mode) -{ - static struct - { - int joysymbol; - char *name; - } translate_joy[] = - { - { JOY_LEFT, "joystick_left" }, - { JOY_RIGHT, "joystick_right" }, - { JOY_UP, "joystick_up" }, - { JOY_DOWN, "joystick_down" }, - { JOY_BUTTON_1, "joystick_button_1" }, - { JOY_BUTTON_2, "joystick_button_2" }, - }; - - int i; - - if (mode == TRANSLATE_JOYSYMBOL_TO_JOYNAME) - { - *name = "[undefined]"; - - for (i = 0; i < 6; i++) - { - if (*joysymbol == translate_joy[i].joysymbol) - { - *name = translate_joy[i].name; - break; - } - } - } - else if (mode == TRANSLATE_JOYNAME_TO_JOYSYMBOL) - { - *joysymbol = 0; - - for (i = 0; i < 6; i++) - { - if (strEqual(*name, translate_joy[i].name)) - { - *joysymbol = translate_joy[i].joysymbol; - break; - } - } - } -} - -char *getJoyNameFromJoySymbol(int joysymbol) -{ - char *name; - - translate_joyname(&joysymbol, &name, TRANSLATE_JOYSYMBOL_TO_JOYNAME); - return name; -} - -int getJoySymbolFromJoyName(char *name) -{ - int joysymbol; - - translate_joyname(&joysymbol, &name, TRANSLATE_JOYNAME_TO_JOYSYMBOL); - return joysymbol; -} - int getJoystickNrFromDeviceName(char *device_name) { char c; @@ -173,25 +108,6 @@ static int JoystickPositionPercent(int center, int border, int actual) return percent; } -void CheckJoystickData(void) -{ - int i; - int distance = 100; - - for (i = 0; i < MAX_PLAYERS; i++) - { - if (setup.input[i].joy.xleft >= setup.input[i].joy.xmiddle) - setup.input[i].joy.xleft = setup.input[i].joy.xmiddle - distance; - if (setup.input[i].joy.xright <= setup.input[i].joy.xmiddle) - setup.input[i].joy.xright = setup.input[i].joy.xmiddle + distance; - - if (setup.input[i].joy.yupper >= setup.input[i].joy.ymiddle) - setup.input[i].joy.yupper = setup.input[i].joy.ymiddle - distance; - if (setup.input[i].joy.ylower <= setup.input[i].joy.ymiddle) - setup.input[i].joy.ylower = setup.input[i].joy.ymiddle + distance; - } -} - int JoystickExt(int player_nr, boolean use_as_joystick_nr) { int joystick_nr = joystick.nr[player_nr]; @@ -305,24 +221,3 @@ int AnyJoystickButton(void) return result; } - -void DeactivateJoystick(void) -{ - /* Temporarily deactivate joystick. This is needed for calibration - screens, where the player has to select a joystick device that - should be calibrated. If there is a totally uncalibrated joystick - active, it may be impossible (due to messed up input from joystick) - to select the joystick device to calibrate even when trying to use - the mouse or keyboard to select the device. */ - - if (joystick.status & JOYSTICK_AVAILABLE) - joystick.status &= ~JOYSTICK_ACTIVE; -} - -void ActivateJoystick(void) -{ - // reactivate temporarily deactivated joystick - - if (joystick.status & JOYSTICK_AVAILABLE) - joystick.status |= JOYSTICK_ACTIVE; -} diff --git a/src/libgame/joystick.h b/src/libgame/joystick.h index e43844ed..6da683f3 100644 --- a/src/libgame/joystick.h +++ b/src/libgame/joystick.h @@ -67,20 +67,14 @@ #define JOY_BUTTON_NEW_PRESSED 2 #define JOY_BUTTON_NEW_RELEASED 3 -char *getJoyNameFromJoySymbol(int); -int getJoySymbolFromJoyName(char *); int getJoystickNrFromDeviceName(char *); char *getDeviceNameFromJoystickNr(int); char *getFormattedJoystickName(const char *); -void CheckJoystickData(void); int Joystick(int); int JoystickExt(int, boolean); int JoystickButton(int); int AnyJoystick(void); int AnyJoystickButton(void); -void DeactivateJoystick(void); -void ActivateJoystick(void); - #endif // JOYSTICK_H diff --git a/src/libgame/libgame.h b/src/libgame/libgame.h index 135b3789..2573a635 100644 --- a/src/libgame/libgame.h +++ b/src/libgame/libgame.h @@ -26,6 +26,8 @@ #include "image.h" #include "setup.h" #include "misc.h" +#include "http.h" +#include "base64.h" #include "zip/miniunz.h" #endif // LIBGAME_H diff --git a/src/libgame/misc.c b/src/libgame/misc.c index 7a3afc68..536d90b5 100644 --- a/src/libgame/misc.c +++ b/src/libgame/misc.c @@ -41,8 +41,7 @@ // logging functions // ---------------------------------------------------------------------------- -#define DUPLICATE_LOG_OUT_TO_STDOUT TRUE -#define DUPLICATE_LOG_ERR_TO_STDERR TRUE +#define DUPLICATE_LOGGING_TO_STDOUT TRUE #if defined(PLATFORM_ANDROID) @@ -94,15 +93,15 @@ static void vprintf_log(char *format, va_list ap) static void vprintf_log_nonewline(char *format, va_list ap) { - FILE *file = program.log_file[LOG_ERR_ID]; + FILE *file = program.log_file; -#if DUPLICATE_LOG_ERR_TO_STDERR - if (file != program.log_file_default[LOG_ERR_ID]) +#if DUPLICATE_LOGGING_TO_STDOUT + if (file != program.log_file_default) { va_list ap2; va_copy(ap2, ap); - vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2); + vfprintf(program.log_file_default, format, ap2); va_end(ap2); } @@ -113,17 +112,17 @@ static void vprintf_log_nonewline(char *format, va_list ap) static void vprintf_log(char *format, va_list ap) { - FILE *file = program.log_file[LOG_ERR_ID]; + FILE *file = program.log_file; char *newline = STRING_NEWLINE; -#if DUPLICATE_LOG_ERR_TO_STDERR - if (file != program.log_file_default[LOG_ERR_ID]) +#if DUPLICATE_LOGGING_TO_STDOUT + if (file != program.log_file_default) { va_list ap2; va_copy(ap2, ap); - vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2); - fprintf(program.log_file_default[LOG_ERR_ID], "%s", newline); + vfprintf(program.log_file_default, format, ap2); + fprintf(program.log_file_default, "%s", newline); va_end(ap2); } @@ -196,15 +195,15 @@ void printf_line_with_prefix(char *prefix, char *line_chars, int line_length) static void vPrint(char *format, va_list ap) { - FILE *file = program.log_file[LOG_OUT_ID]; + FILE *file = program.log_file; -#if DUPLICATE_LOG_OUT_TO_STDOUT - if (file != program.log_file_default[LOG_OUT_ID]) +#if DUPLICATE_LOGGING_TO_STDOUT + if (file != program.log_file_default) { va_list ap2; va_copy(ap2, ap); - vfprintf(program.log_file_default[LOG_OUT_ID], format, ap2); + vfprintf(program.log_file_default, format, ap2); va_end(ap2); } @@ -224,7 +223,7 @@ void Print(char *format, ...) void PrintNoLog(char *format, ...) { - FILE *file = program.log_file_default[LOG_OUT_ID]; + FILE *file = program.log_file_default; va_list ap; va_start(ap, format); @@ -549,6 +548,50 @@ boolean getTokenValueFromString(char *string, char **token, char **value) } +// ---------------------------------------------------------------------------- +// UUID functions +// ---------------------------------------------------------------------------- + +#define UUID_BYTES 16 +#define UUID_CHARS (UUID_BYTES * 2) +#define UUID_LENGTH (UUID_CHARS + 4) + +static unsigned int uuid_random_function(int max) +{ + return GetBetterRandom(max); +} + +char *getUUIDExt(unsigned int (*random_function)(int max)) +{ + static char uuid[UUID_LENGTH + 1]; + int data[UUID_BYTES]; + int count = 0; + int i; + + for (i = 0; i < UUID_BYTES; i++) + data[i] = random_function(256); + + data[6] = 0x40 | (data[6] & 0x0f); + data[8] = 0x80 | (data[8] & 0x3f); + + for (i = 0; i < UUID_BYTES; i++) + { + sprintf(&uuid[count], "%02x", data[i]); + count += 2; + + if (i == 3 || i == 5 || i == 7 || i == 9) + strcat(&uuid[count++], "-"); + } + + return uuid; +} + +char *getUUID(void) +{ + return getUUIDExt(uuid_random_function); +} + + // ---------------------------------------------------------------------------- // counter functions // ---------------------------------------------------------------------------- @@ -597,8 +640,8 @@ void Delay(unsigned int delay) // Sleep specified number of milliseconds sleep_milliseconds(delay); } -boolean DelayReachedExt(unsigned int *counter_var, unsigned int delay, - unsigned int actual_counter) +boolean DelayReachedExt2(unsigned int *counter_var, unsigned int delay, + unsigned int actual_counter) { if (actual_counter >= *counter_var && actual_counter < *counter_var + delay) @@ -609,34 +652,40 @@ boolean DelayReachedExt(unsigned int *counter_var, unsigned int delay, return TRUE; } -boolean FrameReached(unsigned int *frame_counter_var, unsigned int frame_delay) +boolean DelayReachedExt(DelayCounter *counter, unsigned int actual_counter) +{ + return DelayReachedExt2(&counter->count, counter->value, actual_counter); +} + +boolean FrameReached(DelayCounter *counter) { - return DelayReachedExt(frame_counter_var, frame_delay, FrameCounter); + return DelayReachedExt(counter, FrameCounter); } -boolean DelayReached(unsigned int *counter_var, unsigned int delay) +boolean DelayReached(DelayCounter *counter) { - return DelayReachedExt(counter_var, delay, Counter()); + return DelayReachedExt(counter, Counter()); } -void ResetDelayCounterExt(unsigned int *counter_var, - unsigned int actual_counter) +void ResetDelayCounterExt(DelayCounter *counter, unsigned int actual_counter) { - DelayReachedExt(counter_var, 0, actual_counter); + DelayReachedExt2(&counter->count, 0, actual_counter); } -void ResetFrameCounter(unsigned int *frame_counter_var) +void ResetFrameCounter(DelayCounter *counter) { - FrameReached(frame_counter_var, 0); + ResetDelayCounterExt(counter, FrameCounter); } -void ResetDelayCounter(unsigned int *counter_var) +void ResetDelayCounter(DelayCounter *counter) { - DelayReached(counter_var, 0); + ResetDelayCounterExt(counter, Counter()); } -int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay) +int WaitUntilDelayReached(DelayCounter *counter) { + unsigned int *counter_var = &counter->count; + unsigned int delay = counter->value; unsigned int actual_counter; int skip_frames = 0; @@ -667,22 +716,22 @@ int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay) return skip_frames; } -void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay, +void SkipUntilDelayReached(DelayCounter *counter, int *loop_var, int last_loop_value) { - int skip_frames = WaitUntilDelayReached(counter_var, delay); + int skip_frames = WaitUntilDelayReached(counter); #if 0 #if DEBUG if (skip_frames) Debug("internal:SkipUntilDelayReached", "%d: %d ms -> SKIP %d FRAME(S) [%d ms]", - *loop_var, delay, - skip_frames, skip_frames * delay); + *loop_var, counter->value, + skip_frames, skip_frames * counter->value); else Debug("internal:SkipUntilDelayReached", "%d: %d ms", - *loop_var, delay); + *loop_var, counter->value); #endif #endif @@ -712,14 +761,14 @@ void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay, // random generator functions // ---------------------------------------------------------------------------- -unsigned int init_random_number(int nr, int seed) +static unsigned int init_random_number_ext(int nr, int seed) { if (seed == NEW_RANDOMIZE) { // default random seed seed = (int)time(NULL); // seconds since the epoch -#if !defined(PLATFORM_WIN32) +#if !defined(PLATFORM_WINDOWS) // add some more randomness struct timeval current_time; @@ -740,9 +789,32 @@ unsigned int init_random_number(int nr, int seed) return (unsigned int) seed; } +static unsigned int prng_seed_gettimeofday(void) +{ + struct timeval current_time; + + gettimeofday(¤t_time, NULL); + + prng_seed_bytes(¤t_time, sizeof(current_time)); + + return 0; +} + +unsigned int init_random_number(int nr, int seed) +{ + return (nr == RANDOM_BETTER ? prng_seed_gettimeofday() : + init_random_number_ext(nr, seed)); +} + +static unsigned int get_random_number_ext(int nr) +{ + return (nr == RANDOM_BETTER ? prng_get_uint() : + random_linux_libc(nr)); +} + unsigned int get_random_number(int nr, int max) { - return (max > 0 ? random_linux_libc(nr) % max : 0); + return (max > 0 ? get_random_number_ext(nr) % max : 0); } @@ -829,7 +901,7 @@ char *getLoginName(void) { static char *login_name = NULL; -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) if (login_name == NULL) { unsigned long buffer_size = MAX_USERNAME_LEN + 1; @@ -860,7 +932,7 @@ char *getRealName(void) { static char *real_name = NULL; -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) if (real_name == NULL) { static char buffer[MAX_USERNAME_LEN + 1]; @@ -1016,7 +1088,9 @@ char *getBasePath(char *filename) // various string functions // ---------------------------------------------------------------------------- -char *getStringCat2WithSeparator(char *s1, char *s2, char *sep) +char *getStringCat2WithSeparator(const char *s1, + const char *s2, + const char *sep) { if (s1 == NULL || s2 == NULL || sep == NULL) return NULL; @@ -1029,7 +1103,10 @@ char *getStringCat2WithSeparator(char *s1, char *s2, char *sep) return complete_string; } -char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep) +char *getStringCat3WithSeparator(const char *s1, + const char *s2, + const char *s3, + const char *sep) { if (s1 == NULL || s2 == NULL || s3 == NULL || sep == NULL) return NULL; @@ -1043,17 +1120,17 @@ char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep) return complete_string; } -char *getStringCat2(char *s1, char *s2) +char *getStringCat2(const char *s1, const char *s2) { return getStringCat2WithSeparator(s1, s2, ""); } -char *getStringCat3(char *s1, char *s2, char *s3) +char *getStringCat3(const char *s1, const char *s2, const char *s3) { return getStringCat3WithSeparator(s1, s2, s3, ""); } -char *getPath2(char *path1, char *path2) +char *getPath2(const char *path1, const char *path2) { #if defined(PLATFORM_ANDROID) // workaround for reading from assets directory -- skip "." subdirs in path @@ -1066,7 +1143,7 @@ char *getPath2(char *path1, char *path2) return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR); } -char *getPath3(char *path1, char *path2, char *path3) +char *getPath3(const char *path1, const char *path2, const char *path3) { #if defined(PLATFORM_ANDROID) // workaround for reading from assets directory -- skip "." subdirs in path @@ -1094,12 +1171,12 @@ static char *getPngOrPcxIfNotExists(char *filename) return filename; } -char *getImg2(char *path1, char *path2) +char *getImg2(const char *path1, const char *path2) { return getPngOrPcxIfNotExists(getPath2(path1, path2)); } -char *getImg3(char *path1, char *path2, char *path3) +char *getImg3(const char *path1, const char *path2, const char *path3) { return getPngOrPcxIfNotExists(getPath3(path1, path2, path3)); } @@ -1155,14 +1232,14 @@ char *getStringToLower(const char *s) return s_copy; } -void setString(char **old_value, char *new_value) +void setString(char **old_value, const char *new_value) { checked_free(*old_value); *old_value = getStringCopy(new_value); } -boolean strEqual(char *s1, char *s2) +boolean strEqual(const char *s1, const char *s2) { return (s1 == NULL && s2 == NULL ? TRUE : s1 == NULL && s2 != NULL ? FALSE : @@ -1170,7 +1247,7 @@ boolean strEqual(char *s1, char *s2) strcmp(s1, s2) == 0); } -boolean strEqualN(char *s1, char *s2, int n) +boolean strEqualN(const char *s1, const char *s2, int n) { return (s1 == NULL && s2 == NULL ? TRUE : s1 == NULL && s2 != NULL ? FALSE : @@ -1178,7 +1255,7 @@ boolean strEqualN(char *s1, char *s2, int n) strncmp(s1, s2, n) == 0); } -boolean strEqualCase(char *s1, char *s2) +boolean strEqualCase(const char *s1, const char *s2) { return (s1 == NULL && s2 == NULL ? TRUE : s1 == NULL && s2 != NULL ? FALSE : @@ -1186,7 +1263,7 @@ boolean strEqualCase(char *s1, char *s2) strcasecmp(s1, s2) == 0); } -boolean strEqualCaseN(char *s1, char *s2, int n) +boolean strEqualCaseN(const char *s1, const char *s2, int n) { return (s1 == NULL && s2 == NULL ? TRUE : s1 == NULL && s2 != NULL ? FALSE : @@ -1194,7 +1271,7 @@ boolean strEqualCaseN(char *s1, char *s2, int n) strncasecmp(s1, s2, n) == 0); } -boolean strPrefix(char *s, char *prefix) +boolean strPrefix(const char *s, const char *prefix) { return (s == NULL && prefix == NULL ? TRUE : s == NULL && prefix != NULL ? FALSE : @@ -1202,7 +1279,7 @@ boolean strPrefix(char *s, char *prefix) strncmp(s, prefix, strlen(prefix)) == 0); } -boolean strSuffix(char *s, char *suffix) +boolean strSuffix(const char *s, const char *suffix) { return (s == NULL && suffix == NULL ? TRUE : s == NULL && suffix != NULL ? FALSE : @@ -1211,7 +1288,7 @@ boolean strSuffix(char *s, char *suffix) strcmp(&s[strlen(s) - strlen(suffix)], suffix) == 0); } -boolean strPrefixLower(char *s, char *prefix) +boolean strPrefixLower(const char *s, const char *prefix) { char *s_lower = getStringToLower(s); boolean match = strPrefix(s_lower, prefix); @@ -1221,7 +1298,7 @@ boolean strPrefixLower(char *s, char *prefix) return match; } -boolean strSuffixLower(char *s, char *suffix) +boolean strSuffixLower(const char *s, const char *suffix) { char *s_lower = getStringToLower(s); boolean match = strSuffix(s_lower, suffix); @@ -1231,6 +1308,14 @@ boolean strSuffixLower(char *s, char *suffix) return match; } +boolean isURL(const char *s) +{ + while (*s && *s >= 'a' && *s <= 'z') + s++; + + return strPrefix(s, "://"); +} + // ---------------------------------------------------------------------------- // command line option handling functions @@ -1240,8 +1325,7 @@ void GetOptions(int argc, char *argv[], void (*print_usage_function)(void), void (*print_version_function)(void)) { - char *ro_base_path = getProgramMainDataPath(argv[0], RO_BASE_PATH); - char *rw_base_path = getProgramMainDataPath(argv[0], RW_BASE_PATH); + char *base_path = getProgramMainDataPath(argv[0], BASE_PATH); char **argvplus = checked_calloc((argc + 1) * sizeof(char **)); char **options_left = &argvplus[1]; @@ -1253,18 +1337,24 @@ void GetOptions(int argc, char *argv[], options.server_host = NULL; options.server_port = 0; - options.ro_base_directory = ro_base_path; - options.rw_base_directory = rw_base_path; - options.level_directory = getPath2(ro_base_path, LEVELS_DIRECTORY); - options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY); - options.sounds_directory = getPath2(ro_base_path, SOUNDS_DIRECTORY); - options.music_directory = getPath2(ro_base_path, MUSIC_DIRECTORY); - options.docs_directory = getPath2(ro_base_path, DOCS_DIRECTORY); - options.conf_directory = getPath2(ro_base_path, CONF_DIRECTORY); + options.base_directory = base_path; + + options.level_directory = getPath2(base_path, LEVELS_DIRECTORY); + options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY); + options.sounds_directory = getPath2(base_path, SOUNDS_DIRECTORY); + options.music_directory = getPath2(base_path, MUSIC_DIRECTORY); + options.docs_directory = getPath2(base_path, DOCS_DIRECTORY); + options.conf_directory = getPath2(base_path, CONF_DIRECTORY); options.execute_command = NULL; + options.tape_log_filename = NULL; options.special_flags = NULL; options.debug_mode = NULL; + options.player_name = NULL; + options.identifier = NULL; + options.level_nr = NULL; + + options.display_nr = 0; options.mytapes = FALSE; options.serveronly = FALSE; @@ -1334,19 +1424,17 @@ void GetOptions(int argc, char *argv[], if (option_arg == NULL) FailWithHelp("option '%s' requires an argument", option_str); - // this should be extended to separate options for ro and rw data - options.ro_base_directory = ro_base_path = getStringCopy(option_arg); - options.rw_base_directory = rw_base_path = getStringCopy(option_arg); + options.base_directory = base_path = getStringCopy(option_arg); if (option_arg == next_option) options_left++; // adjust paths for sub-directories in base directory accordingly - options.level_directory = getPath2(ro_base_path, LEVELS_DIRECTORY); - options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY); - options.sounds_directory = getPath2(ro_base_path, SOUNDS_DIRECTORY); - options.music_directory = getPath2(ro_base_path, MUSIC_DIRECTORY); - options.docs_directory = getPath2(ro_base_path, DOCS_DIRECTORY); - options.conf_directory = getPath2(ro_base_path, CONF_DIRECTORY); + options.level_directory = getPath2(base_path, LEVELS_DIRECTORY); + options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY); + options.sounds_directory = getPath2(base_path, SOUNDS_DIRECTORY); + options.music_directory = getPath2(base_path, MUSIC_DIRECTORY); + options.docs_directory = getPath2(base_path, DOCS_DIRECTORY); + options.conf_directory = getPath2(base_path, CONF_DIRECTORY); } else if (strncmp(option, "-levels", option_len) == 0) { @@ -1404,6 +1492,33 @@ void GetOptions(int argc, char *argv[], if (option_arg != next_option) options.debug_mode = getStringCopy(option_arg); } + else if (strncmp(option, "-player-name", option_len) == 0) + { + if (option_arg == NULL) + FailWithHelp("option '%s' requires an argument", option_str); + + options.player_name = getStringCopy(option_arg); + if (option_arg == next_option) + options_left++; + } + else if (strncmp(option, "-identifier", option_len) == 0) + { + if (option_arg == NULL) + FailWithHelp("option '%s' requires an argument", option_str); + + options.identifier = getStringCopy(option_arg); + if (option_arg == next_option) + options_left++; + } + else if (strncmp(option, "-level-nr", option_len) == 0) + { + if (option_arg == NULL) + FailWithHelp("option '%s' requires an argument", option_str); + + options.level_nr = getStringCopy(option_arg); + if (option_arg == next_option) + options_left++; + } else if (strncmp(option, "-verbose", option_len) == 0) { options.verbose = TRUE; @@ -1431,7 +1546,38 @@ void GetOptions(int argc, char *argv[], // when doing batch processing, always enable verbose mode (warnings) options.verbose = TRUE; } -#if defined(PLATFORM_MACOSX) + else if (strncmp(option, "-tape_logfile", option_len) == 0) + { + if (option_arg == NULL) + FailWithHelp("option '%s' requires an argument", option_str); + + options.tape_log_filename = getStringCopy(option_arg); + if (option_arg == next_option) + options_left++; + } + else if (strncmp(option, "-display", option_len) == 0) + { + if (option_arg == NULL) + FailWithHelp("option '%s' requires an argument", option_str); + + if (option_arg == next_option) + options_left++; + + int display_nr = atoi(option_arg); + +#if 1 + // dirty hack: SDL_GetNumVideoDisplays() seems broken on some systems + options.display_nr = display_nr; +#else + options.display_nr = + MAX(0, MIN(display_nr, SDL_GetNumVideoDisplays() - 1)); + + if (display_nr != options.display_nr) + Warn("invalid display %d -- using display %d", + display_nr, options.display_nr); +#endif + } +#if defined(PLATFORM_MAC) else if (strPrefix(option, "-psn")) { // ignore process serial number when launched via GUI on Mac OS X @@ -1505,7 +1651,7 @@ void checked_free(void *ptr) void clear_mem(void *ptr, unsigned int size) { -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) // for unknown reason, memset() sometimes crashes when compiled with MinGW char *cptr = (char *)ptr; @@ -1541,6 +1687,31 @@ void swap_number_pairs(int *x1, int *y1, int *x2, int *y2) *y2 = help_y; } +int get_number_of_bits(int bits) +{ + /* + Counting bits set, Brian Kernighan's way + + Brian Kernighan's method goes through as many iterations as there are set + bits. So if we have a 32-bit word with only the high bit set, then it will + only go once through the loop. + + Published in 1988, the C Programming Language 2nd Ed. (by Brian W. Kernighan + and Dennis M. Ritchie) mentions this in exercise 2-9. + First published by Peter Wegner in CACM 3 (1960), 322. + */ + + int num_bits = 0; + + while (bits) + { + bits &= bits - 1; // clear the least significant bit set + num_bits++; + } + + return num_bits; +} + /* the "put" variants of the following file access functions check for the file pointer being != NULL and return the number of bytes they have or would have written; this allows for chunk writing functions to first determine the size @@ -1725,6 +1896,225 @@ void WriteUnusedBytesToFile(FILE *file, unsigned int bytes) } +// ---------------------------------------------------------------------------- +// functions to convert between ISO-8859-1 and UTF-8 +// ---------------------------------------------------------------------------- + +char *getUTF8FromLatin1(char *latin1) +{ + int max_utf8_size = 2 * strlen(latin1) + 1; + char *utf8 = checked_calloc(max_utf8_size); + unsigned char *src = (unsigned char *)latin1; + unsigned char *dst = (unsigned char *)utf8; + + while (*src) + { + if (*src < 128) // pure 7-bit ASCII + { + *dst++ = *src; + } + else if (*src >= 160) // non-ASCII characters + { + *dst++ = 194 + (*src >= 192); + *dst++ = 128 + (*src & 63); + } + else // undefined in ISO-8859-1 + { + *dst++ = '?'; + } + + src++; + } + + // only use the smallest possible string buffer size + utf8 = checked_realloc(utf8, strlen(utf8) + 1); + + return utf8; +} + +char *getLatin1FromUTF8(char *utf8) +{ + int max_latin1_size = strlen(utf8) + 1; + char *latin1 = checked_calloc(max_latin1_size); + unsigned char *src = (unsigned char *)utf8; + unsigned char *dst = (unsigned char *)latin1; + + while (*src) + { + if (*src < 128) // pure 7-bit ASCII + { + *dst++ = *src++; + } + else if (src[0] == 194 && + src[1] >= 128 && src[1] < 192) // non-ASCII characters + { + *dst++ = src[1]; + src += 2; + } + else if (src[0] == 195 && + src[1] >= 128 && src[1] < 192) // non-ASCII characters + { + *dst++ = src[1] + 64; + src += 2; + } + + // all other UTF-8 characters are undefined in ISO-8859-1 + + else if (src[0] >= 192 && src[0] < 224 && + src[1] >= 128 && src[1] < 192) + { + *dst++ = '?'; + src += 2; + } + else if (src[0] >= 224 && src[0] < 240 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192) + { + *dst++ = '?'; + src += 3; + } + else if (src[0] >= 240 && src[0] < 248 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192 && + src[3] >= 128 && src[3] < 192) + { + *dst++ = '?'; + src += 4; + } + else if (src[0] >= 248 && src[0] < 252 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192 && + src[3] >= 128 && src[3] < 192 && + src[4] >= 128 && src[4] < 192) + { + *dst++ = '?'; + src += 5; + } + else if (src[0] >= 252 && src[0] < 254 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192 && + src[3] >= 128 && src[3] < 192 && + src[4] >= 128 && src[4] < 192 && + src[5] >= 128 && src[5] < 192) + { + *dst++ = '?'; + src += 6; + } + else + { + *dst++ = '?'; + src++; + } + } + + // only use the smallest possible string buffer size + latin1 = checked_realloc(latin1, strlen(latin1) + 1); + + return latin1; +} + +int getTextEncoding(char *text) +{ + unsigned char *src = (unsigned char *)text; + int encoding = TEXT_ENCODING_ASCII; // default: assume encoding is ASCII + + while (*src) + { + if (*src >= 128) + encoding = TEXT_ENCODING_UTF_8; // non-ASCII character: assume UTF-8 + + if (*src < 128) + { + src++; + } + else if (src[0] >= 192 && src[0] < 224 && + src[1] >= 128 && src[1] < 192) + { + src += 2; + } + else if (src[0] >= 224 && src[0] < 240 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192) + { + src += 3; + } + else if (src[0] >= 240 && src[0] < 248 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192 && + src[3] >= 128 && src[3] < 192) + { + src += 4; + } + else if (src[0] >= 248 && src[0] < 252 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192 && + src[3] >= 128 && src[3] < 192 && + src[4] >= 128 && src[4] < 192) + { + src += 5; + } + else if (src[0] >= 252 && src[0] < 254 && + src[1] >= 128 && src[1] < 192 && + src[2] >= 128 && src[2] < 192 && + src[3] >= 128 && src[3] < 192 && + src[4] >= 128 && src[4] < 192 && + src[5] >= 128 && src[5] < 192) + { + src += 6; + } + else + { + return TEXT_ENCODING_UNKNOWN; // non-UTF-8 character: unknown encoding + } + } + + return encoding; +} + + +// ---------------------------------------------------------------------------- +// functions for JSON handling +// ---------------------------------------------------------------------------- + +char *getEscapedJSON(char *s) +{ + int max_json_size = 2 * strlen(s) + 1; + char *json = checked_calloc(max_json_size); + unsigned char *src = (unsigned char *)s; + unsigned char *dst = (unsigned char *)json; + char *escaped[256] = + { + ['\b'] = "\\b", + ['\f'] = "\\f", + ['\n'] = "\\n", + ['\r'] = "\\r", + ['\t'] = "\\t", + ['\"'] = "\\\"", + ['\\'] = "\\\\", + }; + + while (*src) + { + if (escaped[*src] != NULL) + { + char *esc = escaped[*src++]; + + while (*esc) + *dst++ = *esc++; + } + else + { + *dst++ = *src++; + } + } + + // only use the smallest possible string buffer size + json = checked_realloc(json, strlen(json) + 1); + + return json; +} + + // ---------------------------------------------------------------------------- // functions to translate key identifiers between different format // ---------------------------------------------------------------------------- @@ -2162,47 +2552,23 @@ char getValidConfigValueChar(char c) int get_integer_from_string(char *s) { - static char *number_text[][3] = - { - { "0", "zero", "null", }, - { "1", "one", "first" }, - { "2", "two", "second" }, - { "3", "three", "third" }, - { "4", "four", "fourth" }, - { "5", "five", "fifth" }, - { "6", "six", "sixth" }, - { "7", "seven", "seventh" }, - { "8", "eight", "eighth" }, - { "9", "nine", "ninth" }, - { "10", "ten", "tenth" }, - { "11", "eleven", "eleventh" }, - { "12", "twelve", "twelfth" }, - - { NULL, NULL, NULL }, - }; + // check for the most common case first + if (s[0] >= '0' && s[0] <= '9') + return atoi(s); - int i, j; char *s_lower = getStringToLower(s); int result = -1; - for (i = 0; number_text[i][0] != NULL; i++) - for (j = 0; j < 3; j++) - if (strEqual(s_lower, number_text[i][j])) - result = i; - - if (result == -1) - { - if (strEqual(s_lower, "false") || - strEqual(s_lower, "no") || - strEqual(s_lower, "off")) - result = 0; - else if (strEqual(s_lower, "true") || - strEqual(s_lower, "yes") || - strEqual(s_lower, "on")) - result = 1; - else - result = atoi(s); - } + if (strEqual(s_lower, "false") || + strEqual(s_lower, "no") || + strEqual(s_lower, "off")) + result = 0; + else if (strEqual(s_lower, "true") || + strEqual(s_lower, "yes") || + strEqual(s_lower, "on")) + result = 1; + else + result = atoi(s); free(s_lower); @@ -2554,6 +2920,22 @@ int copyFile(char *filename_from, char *filename_to) return 0; } +boolean touchFile(char *filename) +{ + FILE *file; + + if (!(file = fopen(filename, MODE_WRITE))) + { + Warn("cannot touch file '%s'", filename); + + return FALSE; + } + + fclose(file); + + return TRUE; +} + // ---------------------------------------------------------------------------- // functions for directory handling @@ -2695,7 +3077,7 @@ void freeDirectoryEntry(DirectoryEntry *dir_entry) // functions for checking files and filenames // ---------------------------------------------------------------------------- -boolean directoryExists(char *dir_name) +boolean directoryExists(const char *dir_name) { if (dir_name == NULL) return FALSE; @@ -2723,7 +3105,7 @@ boolean directoryExists(char *dir_name) return success; } -boolean fileExists(char *filename) +boolean fileExists(const char *filename) { if (filename == NULL) return FALSE; @@ -2747,7 +3129,7 @@ boolean fileExists(char *filename) } #if 0 -static boolean fileHasPrefix(char *basename, char *prefix) +static boolean fileHasPrefix(const char *basename, const char *prefix) { static char *basename_lower = NULL; int basename_length, prefix_length; @@ -2770,7 +3152,7 @@ static boolean fileHasPrefix(char *basename, char *prefix) } #endif -static boolean fileHasSuffix(char *basename, char *suffix) +static boolean fileHasSuffix(const char *basename, const char *suffix) { static char *basename_lower = NULL; int basename_length, suffix_length; @@ -3568,8 +3950,8 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info) char *filename_base = UNDEFINED_FILENAME, *filename_local; int i, j; - DrawInitText("Loading artwork config", 120, FC_GREEN); - DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW); + DrawInitTextHead("Loading artwork config"); + DrawInitTextItem(ARTWORKINFO_FILENAME(artwork_info->type)); // always start with reliable default values for (i = 0; i < num_file_list_entries; i++) @@ -3654,6 +4036,11 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info, char *basename = file_list_entry->filename; char *filename = getCustomArtworkFilename(basename, artwork_info->type); + // mark all images from non-default graphics directory as "redefined" + if (artwork_info->type == ARTWORK_TYPE_GRAPHICS && + !strPrefix(filename, options.graphics_directory)) + file_list_entry->redefined = TRUE; + if (filename == NULL) { Warn("cannot find artwork file '%s'", basename); @@ -3722,8 +4109,8 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info, return; } - DrawInitText(init_text[artwork_info->type], 120, FC_GREEN); - DrawInitText(basename, 150, FC_YELLOW); + DrawInitTextHead(init_text[artwork_info->type]); + DrawInitTextItem(basename); if ((*listnode = artwork_info->load_artwork(filename)) != NULL) { @@ -3822,45 +4209,41 @@ void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info) // (now also added for Windows, to create files in user data directory) // ---------------------------------------------------------------------------- +char *getLogBasename(char *basename) +{ + return getStringCat2(basename, ".log"); +} + char *getLogFilename(char *basename) { return getPath2(getMainUserGameDataDir(), basename); } -void OpenLogFiles(void) +void OpenLogFile(void) { - int i; - InitMainUserDataDirectory(); - for (i = 0; i < NUM_LOGS; i++) + if ((program.log_file = fopen(program.log_filename, MODE_WRITE)) == NULL) { - if ((program.log_file[i] = fopen(program.log_filename[i], MODE_WRITE)) - == NULL) - { - program.log_file[i] = program.log_file_default[i]; // reset to default - - Warn("cannot open file '%s' for writing: %s", - program.log_filename[i], strerror(errno)); - } + program.log_file = program.log_file_default; // reset to default - // output should be unbuffered so it is not truncated in a crash - setbuf(program.log_file[i], NULL); + Warn("cannot open file '%s' for writing: %s", + program.log_filename, strerror(errno)); } + + // output should be unbuffered so it is not truncated in a crash + setbuf(program.log_file, NULL); } -void CloseLogFiles(void) +void CloseLogFile(void) { - int i; - - for (i = 0; i < NUM_LOGS; i++) - if (program.log_file[i] != program.log_file_default[i]) - fclose(program.log_file[i]); + if (program.log_file != program.log_file_default) + fclose(program.log_file); } -void DumpLogFile(int nr) +void DumpLogFile(void) { - FILE *log_file = fopen(program.log_filename[nr], MODE_READ); + FILE *log_file = fopen(program.log_filename, MODE_READ); if (log_file == NULL) return; @@ -3873,12 +4256,12 @@ void DumpLogFile(int nr) void NotifyUserAboutErrorFile(void) { -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) char *title_text = getStringCat2(program.program_title, " Error Message"); char *error_text = getStringCat2("The program was aborted due to an error; " "for details, see the following error file:" STRING_NEWLINE, - program.log_filename[LOG_ERR_ID]); + program.log_filename); MessageBox(NULL, error_text, title_text, MB_OK); #endif diff --git a/src/libgame/misc.h b/src/libgame/misc.h index 66b310e7..2893ba0f 100644 --- a/src/libgame/misc.h +++ b/src/libgame/misc.h @@ -28,11 +28,14 @@ #define RANDOM_ENGINE 0 #define RANDOM_SIMPLE 1 +#define RANDOM_BETTER 2 #define InitEngineRandom(seed) init_random_number(RANDOM_ENGINE, seed) #define InitSimpleRandom(seed) init_random_number(RANDOM_SIMPLE, seed) +#define InitBetterRandom(seed) init_random_number(RANDOM_BETTER, seed) #define GetEngineRandom(max) get_random_number(RANDOM_ENGINE, max) #define GetSimpleRandom(max) get_random_number(RANDOM_SIMPLE, max) +#define GetBetterRandom(max) get_random_number(RANDOM_BETTER, max) // values for getFile...() and putFile...() #define BYTE_ORDER_BIG_ENDIAN 0 @@ -42,6 +45,11 @@ #define BIT_ORDER_MSB 0 #define BIT_ORDER_LSB 1 +// values for character encoding +#define TEXT_ENCODING_UNKNOWN 0 +#define TEXT_ENCODING_ASCII 1 +#define TEXT_ENCODING_UTF_8 2 + // values for createDirectory() #define PERMS_PRIVATE 0 #define PERMS_PUBLIC 1 @@ -120,17 +128,21 @@ int log_2(unsigned int); boolean getTokenValueFromString(char *, char **, char **); +char *getUUIDExt(unsigned int (*function)(int)); +char *getUUID(void); + void InitCounter(void); unsigned int Counter(void); void Delay(unsigned int); -boolean DelayReachedExt(unsigned int *, unsigned int, unsigned int); -boolean FrameReached(unsigned int *, unsigned int); -boolean DelayReached(unsigned int *, unsigned int); -void ResetDelayCounterExt(unsigned int *, unsigned int); -void ResetFrameCounter(unsigned int *); -void ResetDelayCounter(unsigned int *); -int WaitUntilDelayReached(unsigned int *, unsigned int); -void SkipUntilDelayReached(unsigned int *, unsigned int, int *, int); +boolean DelayReachedExt2(unsigned int *, unsigned int, unsigned int); +boolean DelayReachedExt(DelayCounter *, unsigned int); +boolean FrameReached(DelayCounter *); +boolean DelayReached(DelayCounter *); +void ResetDelayCounterExt(DelayCounter *, unsigned int); +void ResetFrameCounter(DelayCounter *); +void ResetDelayCounter(DelayCounter *); +int WaitUntilDelayReached(DelayCounter *); +void SkipUntilDelayReached(DelayCounter *, int *, int); unsigned int init_random_number(int, int); unsigned int get_random_number(int, int); @@ -156,27 +168,28 @@ char *getBaseName(char *); char *getBaseNamePtr(char *); char *getBaseNameNoSuffix(char *); -char *getStringCat2WithSeparator(char *, char *, char *); -char *getStringCat3WithSeparator(char *, char *, char *, char *); -char *getStringCat2(char *, char *); -char *getStringCat3(char *, char *, char *); -char *getPath2(char *, char *); -char *getPath3(char *, char *, char*); -char *getImg2(char *, char *); -char *getImg3(char *, char *, char*); +char *getStringCat2WithSeparator(const char *, const char *, const char *); +char *getStringCat3WithSeparator(const char *, const char *, const char *, const char *); +char *getStringCat2(const char *, const char *); +char *getStringCat3(const char *, const char *, const char *); +char *getPath2(const char *, const char *); +char *getPath3(const char *, const char *, const char *); +char *getImg2(const char *, const char *); +char *getImg3(const char *, const char *, const char *); char *getStringCopy(const char *); char *getStringCopyN(const char *, int); char *getStringCopyNStatic(const char *, int); char *getStringToLower(const char *); -void setString(char **, char *); -boolean strEqual(char *, char *); -boolean strEqualN(char *, char *, int); -boolean strEqualCase(char *, char *); -boolean strEqualCaseN(char *, char *, int); -boolean strPrefix(char *, char *); -boolean strSuffix(char *, char *); -boolean strPrefixLower(char *, char *); -boolean strSuffixLower(char *, char *); +void setString(char **, const char *); +boolean strEqual(const char *, const char *); +boolean strEqualN(const char *, const char *, int); +boolean strEqualCase(const char *, const char *); +boolean strEqualCaseN(const char *, const char *, int); +boolean strPrefix(const char *, const char *); +boolean strSuffix(const char *, const char *); +boolean strPrefixLower(const char *, const char *); +boolean strSuffixLower(const char *, const char *); +boolean isURL(const char *); void GetOptions(int, char **, void (*print_usage_function)(void), @@ -190,6 +203,7 @@ void clear_mem(void *, unsigned int); void swap_numbers(int *, int *); void swap_number_pairs(int *, int *, int *, int *); +int get_number_of_bits(int); int getFile8BitInteger(File *); int putFile8BitInteger(FILE *, int); @@ -225,6 +239,12 @@ void WriteUnusedBytesToFile(FILE *, unsigned int); #define putFileChunkBE(f,s,x) putFileChunk(f,s,x,BYTE_ORDER_BIG_ENDIAN) #define putFileChunkLE(f,s,x) putFileChunk(f,s,x,BYTE_ORDER_LITTLE_ENDIAN) +char *getUTF8FromLatin1(char *); +char *getLatin1FromUTF8(char *); +int getTextEncoding(char *); + +char *getEscapedJSON(char *); + char *getKeyNameFromKey(Key); char *getX11KeyNameFromKey(Key); Key getKeyFromKeyName(char *); @@ -252,14 +272,16 @@ int seekFile(File *, long, int); int getByteFromFile(File *); char *getStringFromFile(File *, char *, int); int copyFile(char *, char *); +boolean touchFile(char *); Directory *openDirectory(char *); int closeDirectory(Directory *); DirectoryEntry *readDirectory(Directory *); void freeDirectoryEntry(DirectoryEntry *); -boolean directoryExists(char *); -boolean fileExists(char *); +boolean directoryExists(const char *); +boolean fileExists(const char *); + boolean FileIsGraphic(char *); boolean FileIsSound(char *); boolean FileIsMusic(char *); @@ -274,10 +296,11 @@ void LoadArtworkConfig(struct ArtworkListInfo *); void ReloadCustomArtworkList(struct ArtworkListInfo *); void FreeCustomArtworkLists(struct ArtworkListInfo *); +char *getLogBasename(char *); char *getLogFilename(char *); -void OpenLogFiles(void); -void CloseLogFiles(void); -void DumpLogFile(int); +void OpenLogFile(void); +void CloseLogFile(void); +void DumpLogFile(void); void NotifyUserAboutErrorFile(void); diff --git a/src/libgame/platform.h b/src/libgame/platform.h index 438e8de1..b9c8a411 100644 --- a/src/libgame/platform.h +++ b/src/libgame/platform.h @@ -16,8 +16,8 @@ // define main platform keywords // ============================================================================ -#if defined(WIN32) || defined(_WIN32) -#define PLATFORM_WIN32 +#if defined(WIN32) || defined(_WIN32) || defined(_WIN64) +#define PLATFORM_WINDOWS #define PLATFORM_STRING "Windows" #else #define PLATFORM_UNIX @@ -38,7 +38,7 @@ #if defined(AMIGA) || defined(__AMIGA) || defined(__amigados__) #define PLATFORM_AMIGA #undef PLATFORM_STRING -#define PLATFORM_STRING "AmigaOS" +#define PLATFORM_STRING "Amiga" #endif #if defined(__BEOS__) @@ -86,9 +86,9 @@ #endif #if defined(__APPLE__) && defined(__MACH__) -#define PLATFORM_MACOSX +#define PLATFORM_MAC #undef PLATFORM_STRING -#define PLATFORM_STRING "Mac OS X" +#define PLATFORM_STRING "Mac" #endif #if defined(__NetBSD__) diff --git a/src/libgame/random.c b/src/libgame/random.c index 7877d82d..fd409d06 100644 --- a/src/libgame/random.c +++ b/src/libgame/random.c @@ -9,6 +9,9 @@ // random.c // ============================================================================ +#include "random.h" + + /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. @@ -36,8 +39,6 @@ #include #include -#include "random.h" - /* An improved random number generation package. In addition to the standard rand()/srand() like interface, this package also has a special state info @@ -257,3 +258,274 @@ int random_linux_libc(int nr) return i; } } + + +// ============================================================================ + +/* + * prng.c - Portable, ISO C90 and C99 compliant high-quality + * pseudo-random number generator based on the alleged RC4 + * cipher. This PRNG should be suitable for most general-purpose + * uses. Not recommended for cryptographic or financial + * purposes. Not thread-safe. + */ + +/* + * Copyright (c) 2004 Ben Pfaff . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include + +/* RC4-based pseudo-random state. */ +static unsigned char s[256]; +static int s_i, s_j; + +/* Nonzero if PRNG has been seeded. */ +static int seeded; + +/* Swap bytes that A and B point to. */ +#define SWAP_BYTE(A, B) \ + do { \ + unsigned char swap_temp = *(A); \ + *(A) = *(B); \ + *(B) = swap_temp; \ + } while (0) + +/* Seeds the pseudo-random number generator based on the current + time. + + If the user calls neither this function nor prng_seed_bytes() + before any prng_get*() function, this function is called + automatically to obtain a time-based seed. */ +void +prng_seed_time (void) +{ + static time_t t; + if (t == 0) + t = time (NULL); + else + t++; + + prng_seed_bytes (&t, sizeof t); +} + +/* Retrieves one octet from the array BYTES, which is N_BYTES in + size, starting at an offset of OCTET_IDX octets. BYTES is + treated as a circular array, so that accesses past the first + N_BYTES bytes wrap around to the beginning. */ +static unsigned char +get_octet (const void *bytes_, size_t n_bytes, size_t octet_idx) +{ + const unsigned char *bytes = bytes_; + if (CHAR_BIT == 8) + return bytes[octet_idx % n_bytes]; + else + { + size_t first_byte = octet_idx * 8 / CHAR_BIT % n_bytes; + size_t start_bit = octet_idx * 8 % CHAR_BIT; + unsigned char c = (bytes[first_byte] >> start_bit) & 255; + + size_t bits_filled = CHAR_BIT - start_bit; + if (CHAR_BIT % 8 != 0 && bits_filled < 8) + { + size_t bits_left = 8 - bits_filled; + unsigned char bits_left_mask = (1u << bits_left) - 1; + size_t second_byte = first_byte + 1 < n_bytes ? first_byte + 1 : 0; + + c |= (bytes[second_byte] & bits_left_mask) << bits_filled; + } + + return c; + } +} + +/* Seeds the pseudo-random number based on the SIZE bytes in + KEY. At most the first 2048 bits in KEY are used. */ +void +prng_seed_bytes (const void *key, size_t size) +{ + int i, j; + + assert (key != NULL && size > 0); + + for (i = 0; i < 256; i++) + s[i] = i; + for (i = j = 0; i < 256; i++) + { + j = (j + s[i] + get_octet (key, size, i)) & 255; + SWAP_BYTE (s + i, s + j); + } + + s_i = s_j = 0; + seeded = 1; +} + +/* Returns a pseudo-random integer in the range [0, 255]. */ +unsigned char +prng_get_octet (void) +{ + if (!seeded) + prng_seed_time (); + + s_i = (s_i + 1) & 255; + s_j = (s_j + s[s_i]) & 255; + SWAP_BYTE (s + s_i, s + s_j); + + return s[(s[s_i] + s[s_j]) & 255]; +} + +/* Returns a pseudo-random integer in the range [0, UCHAR_MAX]. */ +unsigned char +prng_get_byte (void) +{ + unsigned byte; + int bits; + + byte = prng_get_octet (); + for (bits = 8; bits < CHAR_BIT; bits += 8) + byte = (byte << 8) | prng_get_octet (); + return byte; +} + +/* Fills BUF with SIZE pseudo-random bytes. */ +void +prng_get_bytes (void *buf_, size_t size) +{ + unsigned char *buf; + + for (buf = buf_; size-- > 0; buf++) + *buf = prng_get_byte (); +} + +/* Returns a pseudo-random unsigned long in the range [0, + ULONG_MAX]. */ +unsigned long +prng_get_ulong (void) +{ + unsigned long ulng; + size_t bits; + + ulng = prng_get_octet (); + for (bits = 8; bits < CHAR_BIT * sizeof ulng; bits += 8) + ulng = (ulng << 8) | prng_get_octet (); + return ulng; +} + +/* Returns a pseudo-random long in the range [0, LONG_MAX]. */ +long +prng_get_long (void) +{ + return prng_get_ulong () & LONG_MAX; +} + +/* Returns a pseudo-random unsigned int in the range [0, + UINT_MAX]. */ +unsigned +prng_get_uint (void) +{ + unsigned uint; + size_t bits; + + uint = prng_get_octet (); + for (bits = 8; bits < CHAR_BIT * sizeof uint; bits += 8) + uint = (uint << 8) | prng_get_octet (); + return uint; +} + +/* Returns a pseudo-random int in the range [0, INT_MAX]. */ +int +prng_get_int (void) +{ + return prng_get_uint () & INT_MAX; +} + +/* Returns a pseudo-random floating-point number from the uniform + distribution with range [0,1). */ +double +prng_get_double (void) +{ + for (;;) + { + double dbl = prng_get_ulong () / (ULONG_MAX + 1.0); + if (dbl >= 0.0 && dbl < 1.0) + return dbl; + } +} + +/* Returns a pseudo-random floating-point number from the + distribution with mean 0 and standard deviation 1. (Multiply + the result by the desired standard deviation, then add the + desired mean.) */ +double +prng_get_double_normal (void) +{ + /* Knuth, _The Art of Computer Programming_, Vol. 2, 3.4.1C, + Algorithm P. */ + static int has_next = 0; + static double next_normal; + double this_normal; + + if (has_next) + { + this_normal = next_normal; + has_next = 0; + } + else + { + static double limit; + double v1, v2, s; + + if (limit == 0.0) + limit = log (DBL_MAX / 2) / (DBL_MAX / 2); + + for (;;) + { + double u1 = prng_get_double (); + double u2 = prng_get_double (); + v1 = 2.0 * u1 - 1.0; + v2 = 2.0 * u2 - 1.0; + s = v1 * v1 + v2 * v2; + if (s > limit && s < 1) + break; + } + + this_normal = v1 * sqrt (-2. * log (s) / s); + next_normal = v2 * sqrt (-2. * log (s) / s); + has_next = 1; + } + + return this_normal; +} diff --git a/src/libgame/random.h b/src/libgame/random.h index 63a54c37..0da74da4 100644 --- a/src/libgame/random.h +++ b/src/libgame/random.h @@ -15,4 +15,21 @@ void srandom_linux_libc(int, unsigned int); int random_linux_libc(int); + +// ============================================================================ + +#include + +void prng_seed_time (void); +void prng_seed_bytes (const void *, size_t); +unsigned char prng_get_octet (void); +unsigned char prng_get_byte (void); +void prng_get_bytes (void *, size_t); +unsigned long prng_get_ulong (void); +long prng_get_long (void); +unsigned prng_get_uint (void); +int prng_get_int (void); +double prng_get_double (void); +double prng_get_double_normal (void); + #endif diff --git a/src/libgame/sdl.c b/src/libgame/sdl.c index 4108b2b5..0b3ec7f6 100644 --- a/src/libgame/sdl.c +++ b/src/libgame/sdl.c @@ -62,19 +62,33 @@ static void FinalizeScreen(int draw_target) if (gfx.draw_global_anim_function != NULL) gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_2); - // copy tile selection cursor to render target buffer, if defined (above all) + // copy tile selection cursor to render target buffer, if defined (part 1) if (gfx.draw_tile_cursor_function != NULL) - gfx.draw_tile_cursor_function(draw_target); + gfx.draw_tile_cursor_function(draw_target, TRUE); + + // copy envelope request to render target buffer, if needed (above all) + if (gfx.draw_envelope_request_function != NULL) + gfx.draw_envelope_request_function(draw_target); + + // copy tile selection cursor to render target buffer, if defined (part 2) + if (gfx.draw_tile_cursor_function != NULL) + gfx.draw_tile_cursor_function(draw_target, FALSE); + + // copy global animations to render target buffer, if defined (mouse pointer) + if (gfx.draw_global_anim_function != NULL) + gfx.draw_global_anim_function(draw_target, DRAW_GLOBAL_ANIM_STAGE_3); } static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay) { - static unsigned int update_screen_delay = 0; - unsigned int update_screen_delay_value = 50; // (milliseconds) + if (program.headless) + return; + + static DelayCounter update_screen_delay = { 50 }; // (milliseconds) SDL_Surface *screen = backbuffer->surface; if (limit_screen_updates && - !DelayReached(&update_screen_delay, update_screen_delay_value)) + !DelayReached(&update_screen_delay)) return; LimitScreenUpdates(FALSE); @@ -151,28 +165,31 @@ static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay) dst_rect1 = &dst_rect_screen; #if defined(HAS_SCREEN_KEYBOARD) - if (video.shifted_up || video.shifted_up_delay) + SDL_Rect src_rect_up = { 0, 0, video.width, video.height }; + SDL_Rect dst_rect_up = dst_rect_screen; + + if (video.shifted_up || video.shifted_up_delay.count) { int time_current = SDL_GetTicks(); int pos = video.shifted_up_pos; int pos_last = video.shifted_up_pos_last; - if (!DelayReachedExt(&video.shifted_up_delay, video.shifted_up_delay_value, - time_current)) + if (!DelayReachedExt(&video.shifted_up_delay, time_current)) { - int delay = time_current - video.shifted_up_delay; - int delay_value = video.shifted_up_delay_value; + int delay_count = time_current - video.shifted_up_delay.count; + int delay_value = video.shifted_up_delay.value; - pos = pos_last + (pos - pos_last) * delay / delay_value; + pos = pos_last + (pos - pos_last) * delay_count / delay_value; } else { video.shifted_up_pos_last = pos; - video.shifted_up_delay = 0; + video.shifted_up_delay.count = 0; } - SDL_Rect src_rect_up = { 0, pos, video.width, video.height - pos }; - SDL_Rect dst_rect_up = { xoff, yoff, video.width, video.height - pos }; + src_rect_up.y = pos; + src_rect_up.h = video.height - pos; + dst_rect_up.h = video.height - pos; if (video.screen_rendering_mode == SPECIAL_RENDERING_TARGET || video.screen_rendering_mode == SPECIAL_RENDERING_DOUBLE) @@ -221,7 +238,7 @@ static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay) // global synchronization point of the game to align video frame delay if (with_frame_delay) - WaitUntilDelayReached(&video.frame_delay, video.frame_delay_value); + WaitUntilDelayReached(&video.frame_delay); video.frame_counter++; @@ -260,7 +277,7 @@ static void SDLSetWindowIcon(char *basename) // (setting the window icon on Mac OS X would replace the high-quality // dock icon with the currently smaller (and uglier) icon from file) -#if !defined(PLATFORM_MACOSX) +#if !defined(PLATFORM_MAC) char *filename = getCustomImageFilename(basename); SDL_Surface *surface; @@ -338,7 +355,7 @@ static boolean SDLHasAlpha(SDL_Surface *surface) return (blend_mode == SDL_BLENDMODE_BLEND); } -void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha) +static void SDLSetSurfaceAlpha(SDL_Surface *surface, boolean set, int alpha) { SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE); @@ -346,6 +363,49 @@ void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha) SDL_SetSurfaceAlphaMod(surface, alpha); } +static void SDLSetTextureAlpha(SDL_Texture *texture, boolean set, int alpha) +{ + SDL_BlendMode blend_mode = (set ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE); + + SDL_SetTextureBlendMode(texture, blend_mode); + SDL_SetTextureAlphaMod(texture, alpha); +} + +static void SDLSetBitmapAlpha(Bitmap *bitmap, boolean is_texture, + boolean is_masked) +{ + int alpha_next_blit = bitmap->alpha_next_blit; + + // alpha value must be requested every time before blitting, if needed + bitmap->alpha_next_blit = -1; + + // nothing to do if requested alpha value is already set + if (bitmap->alpha[is_texture][is_masked] == alpha_next_blit) + return; + + // store requested alpha value for masked/unmasked surface/texture + bitmap->alpha[is_texture][is_masked] = alpha_next_blit; + + // set blend mode if bitmap is masked or if alpha value is defined + boolean set_blend_mode = (is_masked || alpha_next_blit != -1); + + // if alpha value is undefined, use default (opaque) alpha value + if (alpha_next_blit == -1) + alpha_next_blit = SDL_ALPHA_OPAQUE; + + if (is_texture) + SDLSetTextureAlpha(is_masked ? bitmap->texture_masked : bitmap->texture, + set_blend_mode, alpha_next_blit); + else + SDLSetSurfaceAlpha(is_masked ? bitmap->surface_masked : bitmap->surface, + set_blend_mode, alpha_next_blit); +} + +void SDLSetAlpha(SDL_Surface *surface, boolean set, int alpha) +{ + SDLSetSurfaceAlpha(surface, set, alpha); +} + const char *SDLGetRendererName(void) { static SDL_RendererInfo renderer_info; @@ -551,6 +611,7 @@ static boolean SDLCreateScreen(boolean fullscreen) int screen_height = video.screen_height; int surface_flags = (fullscreen ? surface_flags_fullscreen : surface_flags_window); + int display_nr = options.display_nr; // default window size is unscaled video.window_width = screen_width; @@ -586,15 +647,14 @@ static boolean SDLCreateScreen(boolean fullscreen) if (sdl_window) { - SDL_DestroyWindow(sdl_window); - sdl_window = NULL; + SDL_SetWindowSize(sdl_window, video.window_width, video.window_height); } } if (sdl_window == NULL) sdl_window = SDL_CreateWindow(program.window_title, - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED_DISPLAY(display_nr), + SDL_WINDOWPOS_CENTERED_DISPLAY(display_nr), video.window_width, video.window_height, surface_flags); @@ -794,7 +854,8 @@ void SDLSetWindowFullscreen(boolean fullscreen) { SDLSetWindowScaling(setup.window_scaling_percent); SDL_SetWindowPosition(sdl_window, - SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_WINDOWPOS_CENTERED_DISPLAY(options.display_nr), + SDL_WINDOWPOS_CENTERED_DISPLAY(options.display_nr)); video.fullscreen_initial = FALSE; } @@ -955,6 +1016,25 @@ void SDLFreeBitmapPointers(Bitmap *bitmap) bitmap->texture_masked = NULL; } +void SDLBlitSurface(SDL_Surface *src_surface, SDL_Surface *dst_surface, + int src_x, int src_y, int width, int height, + int dst_x, int dst_y) +{ + SDL_Rect src_rect, dst_rect; + + src_rect.x = src_x; + src_rect.y = src_y; + src_rect.w = width; + src_rect.h = height; + + dst_rect.x = dst_x; + dst_rect.y = dst_y; + dst_rect.w = width; + dst_rect.h = height; + + SDL_BlitSurface(src_surface, &src_rect, dst_surface, &dst_rect); +} + void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap, int src_x, int src_y, int width, int height, int dst_x, int dst_y, int mask_mode) @@ -972,6 +1052,8 @@ void SDLCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap, dst_rect.w = width; dst_rect.h = height; + SDLSetBitmapAlpha(src_bitmap, FALSE, mask_mode == BLIT_MASKED); + // if (src_bitmap != backbuffer || dst_bitmap != window) if (!(src_bitmap == backbuffer && dst_bitmap == window)) SDL_BlitSurface((mask_mode == BLIT_MASKED ? @@ -1006,6 +1088,8 @@ void SDLBlitTexture(Bitmap *bitmap, dst_rect.w = width; dst_rect.h = height; + SDLSetBitmapAlpha(bitmap, TRUE, mask_mode == BLIT_MASKED); + SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect); } @@ -1225,6 +1309,9 @@ void SDLFadeRectangle(int x, int y, int width, int height, draw_border_function(); UpdateScreen_WithFrameDelay(&dst_rect2); + + if (PendingEscapeKeyEvent()) + break; } } } @@ -1281,6 +1368,9 @@ void SDLFadeRectangle(int x, int y, int width, int height, // only update the region of the screen that is affected from fading UpdateScreen_WithFrameDelay(&dst_rect2); + + if (PendingEscapeKeyEvent()) + break; } } else // fading in, fading out or cross-fading @@ -1307,6 +1397,9 @@ void SDLFadeRectangle(int x, int y, int width, int height, // only update the region of the screen that is affected from fading UpdateScreen_WithFrameDelay(&dst_rect); + + if (PendingEscapeKeyEvent()) + break; } } @@ -1439,14 +1532,14 @@ static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) case 1: { // Assuming 8-bpp - *((Uint8 *)surface->pixels + y*surface->pitch + x) = color; + *((Uint8 *)surface->pixels + y * surface->pitch + x) = color; } break; case 2: { // Probably 15-bpp or 16-bpp - *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color; + *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x) = color; } break; @@ -1457,20 +1550,20 @@ static void _PutPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) int shift; // Gack - slow, but endian correct - pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3; + pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; shift = surface->format->Rshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; shift = surface->format->Gshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; shift = surface->format->Bshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; } break; case 4: { // Probably 32-bpp - *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color; + *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x) = color; } break; } @@ -1486,12 +1579,12 @@ static void _PutPixelRGB(SDL_Surface *surface, Sint16 x, Sint16 y, static void _PutPixel8(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) { - *((Uint8 *)surface->pixels + y*surface->pitch + x) = color; + *((Uint8 *)surface->pixels + y * surface->pitch + x) = color; } static void _PutPixel16(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) { - *((Uint16 *)surface->pixels + y*surface->pitch/2 + x) = color; + *((Uint16 *)surface->pixels + y * surface->pitch / 2 + x) = color; } static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) @@ -1500,38 +1593,38 @@ static void _PutPixel24(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) int shift; // Gack - slow, but endian correct - pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3; + pix = (Uint8 *)surface->pixels + y * surface->pitch + x * 3; shift = surface->format->Rshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; shift = surface->format->Gshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; shift = surface->format->Bshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; } static void _PutPixel32(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color) { - *((Uint32 *)surface->pixels + y*surface->pitch/4 + x) = color; + *((Uint32 *)surface->pixels + y * surface->pitch / 4 + x) = color; } -static void _PutPixelX(SDL_Surface *dest,Sint16 x,Sint16 y,Uint32 color) +static void _PutPixelX(SDL_Surface *dest, Sint16 x, Sint16 y, Uint32 color) { switch (dest->format->BytesPerPixel) { case 1: - *((Uint8 *)dest->pixels + y*dest->pitch + x) = color; + *((Uint8 *)dest->pixels + y * dest->pitch + x) = color; break; case 2: - *((Uint16 *)dest->pixels + y*dest->pitch/2 + x) = color; + *((Uint16 *)dest->pixels + y * dest->pitch / 2 + x) = color; break; case 3: - _PutPixel24(dest,x,y,color); + _PutPixel24(dest, x, y, color); break; case 4: - *((Uint32 *)dest->pixels + y*dest->pitch/4 + x) = color; + *((Uint32 *)dest->pixels + y * dest->pitch / 4 + x) = color; break; } } @@ -1569,19 +1662,19 @@ static Sint32 sge_CalcYPitch(SDL_Surface *dest, Sint16 y) switch (dest->format->BytesPerPixel) { case 1: - return y*dest->pitch; + return y * dest->pitch; break; case 2: - return y*dest->pitch/2; + return y * dest->pitch / 2; break; case 3: - return y*dest->pitch; + return y * dest->pitch; break; case 4: - return y*dest->pitch/4; + return y * dest->pitch / 4; break; } } @@ -1617,13 +1710,13 @@ static void sge_pPutPixel(SDL_Surface *surface, Sint16 x, Sint32 ypitch, int shift; // Gack - slow, but endian correct - pix = (Uint8 *)surface->pixels + ypitch + x*3; + pix = (Uint8 *)surface->pixels + ypitch + x * 3; shift = surface->format->Rshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; shift = surface->format->Gshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; shift = surface->format->Bshift; - *(pix+shift/8) = color>>shift; + *(pix + shift / 8) = color>>shift; } break; @@ -1891,22 +1984,6 @@ void SDLPutPixel(Bitmap *dst_bitmap, int x, int y, Pixel pixel) // quick (no, it's slow) and dirty hack to "invert" rectangle inside SDL surface // ---------------------------------------------------------------------------- -void SDLInvertArea(Bitmap *bitmap, int src_x, int src_y, - int width, int height, Uint32 color) -{ - int x, y; - - for (y = src_y; y < src_y + height; y++) - { - for (x = src_x; x < src_x + width; x++) - { - Uint32 pixel = SDLGetPixel(bitmap, x, y); - - SDLPutPixel(bitmap, x, y, pixel == BLACK_PIXEL ? color : BLACK_PIXEL); - } - } -} - void SDLCopyInverseMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap, int src_x, int src_y, int width, int height, int dst_x, int dst_y) diff --git a/src/libgame/sdl.h b/src/libgame/sdl.h index a5271c81..05f99982 100644 --- a/src/libgame/sdl.h +++ b/src/libgame/sdl.h @@ -17,7 +17,7 @@ #include #include #include -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) #include #endif @@ -75,6 +75,10 @@ struct SDLSurfaceInfo char *source_filename; int width, height; + + int alpha[2][2]; // [surface|texture][opaque|masked] + int alpha_next_blit; + SDL_Surface *surface; SDL_Surface *surface_masked; SDL_Texture *texture; @@ -418,6 +422,7 @@ void SDLInitVideoBuffer(boolean); boolean SDLSetVideoMode(boolean); void SDLCreateBitmapContent(Bitmap *, int, int, int); void SDLFreeBitmapPointers(Bitmap *); +void SDLBlitSurface(SDL_Surface *, SDL_Surface *, int, int, int, int, int, int); void SDLCopyArea(Bitmap *, Bitmap *, int, int, int, int, int, int, int); void SDLBlitTexture(Bitmap *, int, int, int, int, int, int, int); void SDLFillRectangle(Bitmap *, int, int, int, int, Uint32); @@ -428,7 +433,6 @@ void SDLDrawLine(Bitmap *, int, int, int, int, Uint32); Pixel SDLGetPixel(Bitmap *, int, int); void SDLPutPixel(Bitmap *, int, int, Pixel); -void SDLInvertArea(Bitmap *, int, int, int, int, Uint32); void SDLCopyInverseMasked(Bitmap *, Bitmap *, int, int, int, int, int, int); Bitmap *SDLZoomBitmap(Bitmap *, int, int); diff --git a/src/libgame/setup.c b/src/libgame/setup.c index 32ae1121..b419875a 100644 --- a/src/libgame/setup.c +++ b/src/libgame/setup.c @@ -19,6 +19,7 @@ #include "platform.h" #include "setup.h" +#include "sound.h" #include "joystick.h" #include "text.h" #include "misc.h" @@ -47,8 +48,6 @@ static char *levelclass_desc[NUM_LEVELCLASS_DESC] = #define TOKEN_VALUE_POSITION_DEFAULT 40 #define TOKEN_COMMENT_POSITION_DEFAULT 60 -#define MAX_COOKIE_LEN 256 - #define TREE_NODE_TYPE_DEFAULT 0 #define TREE_NODE_TYPE_PARENT 1 #define TREE_NODE_TYPE_GROUP 2 @@ -70,6 +69,7 @@ static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT; static SetupFileHash *artworkinfo_cache_old = NULL; static SetupFileHash *artworkinfo_cache_new = NULL; static SetupFileHash *optional_tokens_hash = NULL; +static SetupFileHash *missing_file_hash = NULL; static boolean use_artworkinfo_cache = TRUE; static boolean update_artworkinfo_cache = FALSE; @@ -78,6 +78,16 @@ static boolean update_artworkinfo_cache = FALSE; // file functions // ---------------------------------------------------------------------------- +static void WarnUsingFallback(char *filename) +{ + if (getHashEntry(missing_file_hash, filename) == NULL) + { + setHashEntry(missing_file_hash, filename, ""); + + Debug("setup", "cannot find artwork file '%s' (using fallback)", filename); + } +} + static char *getLevelClassDescription(TreeInfo *ti) { int position = ti->sort_priority / 100; @@ -88,6 +98,16 @@ static char *getLevelClassDescription(TreeInfo *ti) return "Unknown Level Class"; } +static char *getCacheDir(void) +{ + static char *cache_dir = NULL; + + if (cache_dir == NULL) + cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY); + + return cache_dir; +} + static char *getScoreDir(char *level_subdir) { static char *score_dir = NULL; @@ -95,13 +115,29 @@ static char *getScoreDir(char *level_subdir) char *score_subdir = SCORES_DIRECTORY; if (score_dir == NULL) + score_dir = getPath2(getMainUserGameDataDir(), score_subdir); + + if (level_subdir != NULL) { - if (program.global_scores) - score_dir = getPath2(getCommonDataDir(), score_subdir); - else - score_dir = getPath2(getMainUserGameDataDir(), score_subdir); + checked_free(score_level_dir); + + score_level_dir = getPath2(score_dir, level_subdir); + + return score_level_dir; } + return score_dir; +} + +static char *getScoreCacheDir(char *level_subdir) +{ + static char *score_dir = NULL; + static char *score_level_dir = NULL; + char *score_subdir = SCORES_DIRECTORY; + + if (score_dir == NULL) + score_dir = getPath2(getCacheDir(), score_subdir); + if (level_subdir != NULL) { checked_free(score_level_dir); @@ -114,6 +150,32 @@ static char *getScoreDir(char *level_subdir) return score_dir; } +static char *getScoreTapeDir(char *level_subdir, int nr) +{ + static char *score_tape_dir = NULL; + char tape_subdir[MAX_FILENAME_LEN]; + + checked_free(score_tape_dir); + + sprintf(tape_subdir, "%03d", nr); + score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir); + + return score_tape_dir; +} + +static char *getScoreCacheTapeDir(char *level_subdir, int nr) +{ + static char *score_cache_tape_dir = NULL; + char tape_subdir[MAX_FILENAME_LEN]; + + checked_free(score_cache_tape_dir); + + sprintf(tape_subdir, "%03d", nr); + score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir); + + return score_cache_tape_dir; +} + static char *getUserSubdir(int nr) { static char user_subdir[16] = { 0 }; @@ -156,16 +218,6 @@ static char *getLevelSetupDir(char *level_subdir) return levelsetup_dir; } -static char *getCacheDir(void) -{ - static char *cache_dir = NULL; - - if (cache_dir == NULL) - cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY); - - return cache_dir; -} - static char *getNetworkDir(void) { static char *network_dir = NULL; @@ -250,7 +302,7 @@ char *getNewUserLevelSubdir(void) return new_level_subdir; } -static char *getTapeDir(char *level_subdir) +char *getTapeDir(char *level_subdir) { static char *tape_dir = NULL; char *data_dir = getUserGameDataDir(); @@ -463,7 +515,7 @@ char *getProgramMainDataPath(char *command_filename, char *base_path) set the current working directory to the program package directory) */ char *main_data_path = getBasePath(command_filename); -#if defined(PLATFORM_MACOSX) +#if defined(PLATFORM_MAC) if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR)) { char *main_data_path_old = main_data_path; @@ -505,8 +557,8 @@ char *getProgramConfigFilename(char *command_filename) if (strSuffix(command_filename_1, ".exe")) command_filename_1[strlen(command_filename_1) - 4] = '\0'; - char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH); - char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY); + char *base_path = getProgramMainDataPath(command_filename, BASE_PATH); + char *conf_directory = getPath2(base_path, CONF_DIRECTORY); char *command_basepath = getBasePath(command_filename); char *command_basename = getBaseNameNoSuffix(command_filename); @@ -516,7 +568,7 @@ char *getProgramConfigFilename(char *command_filename) config_filename_2 = getStringCat2(command_filename_2, ".conf"); config_filename_3 = getPath2(conf_directory, SETUP_FILENAME); - checked_free(ro_base_path); + checked_free(base_path); checked_free(conf_directory); checked_free(command_basepath); @@ -540,6 +592,34 @@ char *getProgramConfigFilename(char *command_filename) return config_filename_3; } +static char *getPlatformConfigFilename(char *config_filename) +{ + static char *platform_config_filename = NULL; + static boolean initialized = FALSE; + + if (!initialized) + { + char *config_basepath = getBasePath(config_filename); + char *config_basename = getBaseNameNoSuffix(config_filename); + char *config_filename_prefix = getPath2(config_basepath, config_basename); + char *platform_string_lower = getStringToLower(PLATFORM_STRING); + char *platform_suffix = getStringCat2("-", platform_string_lower); + + platform_config_filename = getStringCat3(config_filename_prefix, + platform_suffix, ".conf"); + + checked_free(config_basepath); + checked_free(config_basename); + checked_free(config_filename_prefix); + checked_free(platform_string_lower); + checked_free(platform_suffix); + + initialized = TRUE; + } + + return platform_config_filename; +} + char *getTapeFilename(int nr) { static char *filename = NULL; @@ -553,7 +633,20 @@ char *getTapeFilename(int nr) return filename; } -char *getSolutionTapeFilename(int nr) +char *getTemporaryTapeFilename(void) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION); + filename = getPath2(getTapeDir(NULL), basename); + + return filename; +} + +char *getDefaultSolutionTapeFilename(int nr) { static char *filename = NULL; char basename[MAX_FILENAME_LEN]; @@ -563,17 +656,32 @@ char *getSolutionTapeFilename(int nr) sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION); filename = getPath2(getSolutionTapeDir(), basename); - if (!fileExists(filename)) - { - static char *filename_sln = NULL; + return filename; +} - checked_free(filename_sln); +char *getSokobanSolutionTapeFilename(int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; - sprintf(basename, "%03d.sln", nr); - filename_sln = getPath2(getSolutionTapeDir(), basename); + checked_free(filename); + + sprintf(basename, "%03d.sln", nr); + filename = getPath2(getSolutionTapeDir(), basename); + + return filename; +} - if (fileExists(filename_sln)) - return filename_sln; +char *getSolutionTapeFilename(int nr) +{ + char *filename = getDefaultSolutionTapeFilename(nr); + + if (!fileExists(filename)) + { + char *filename2 = getSokobanSolutionTapeFilename(nr); + + if (fileExists(filename2)) + return filename2; } return filename; @@ -594,6 +702,64 @@ char *getScoreFilename(int nr) return filename; } +char *getScoreCacheFilename(int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION); + + // used instead of "leveldir_current->subdir" (for network games) + filename = getPath2(getScoreCacheDir(levelset.identifier), basename); + + return filename; +} + +char *getScoreTapeBasename(char *name) +{ + static char basename[MAX_FILENAME_LEN]; + char basename_raw[MAX_FILENAME_LEN]; + char timestamp[20]; + + sprintf(timestamp, "%s", getCurrentTimestamp()); + sprintf(basename_raw, "%s-%s", timestamp, name); + sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw)); + + return basename; +} + +char *getScoreTapeFilename(char *basename_no_ext, int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION); + + // used instead of "leveldir_current->subdir" (for network games) + filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename); + + return filename; +} + +char *getScoreCacheTapeFilename(char *basename_no_ext, int nr) +{ + static char *filename = NULL; + char basename[MAX_FILENAME_LEN]; + + checked_free(filename); + + sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION); + + // used instead of "leveldir_current->subdir" (for network games) + filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename); + + return filename; +} + char *getSetupFilename(void) { static char *filename = NULL; @@ -610,6 +776,11 @@ char *getDefaultSetupFilename(void) return program.config_filename; } +char *getPlatformSetupFilename(void) +{ + return getPlatformConfigFilename(program.config_filename); +} + char *getEditorSetupFilename(void) { static char *filename = NULL; @@ -648,9 +819,34 @@ char *getHelpTextFilename(void) return filename; } -char *getLevelSetInfoFilename(void) +static char *getLevelSetInfoBasename(int nr) +{ + static char basename[32]; + + sprintf(basename, "levelset_%d.txt", nr + 1); + + return basename; +} + +char *getLevelSetInfoFilename(int nr) { + char *basename = getLevelSetInfoBasename(nr); + static char *info_subdir = NULL; static char *filename = NULL; + + if (info_subdir == NULL) + info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY); + + checked_free(filename); + + // look for level set info file the current level set directory + filename = getPath3(getCurrentLevelDir(), info_subdir, basename); + if (fileExists(filename)) + return filename; + + if (nr > 0) + return NULL; + char *basenames[] = { "README", @@ -753,6 +949,65 @@ char *getLevelSetTitleMessageFilename(int nr, boolean initial) return NULL; // cannot find specified artwork file anywhere } +static char *getCreditsBasename(int nr) +{ + static char basename[32]; + + sprintf(basename, "credits_%d.txt", nr + 1); + + return basename; +} + +char *getCreditsFilename(int nr, boolean global) +{ + char *basename = getCreditsBasename(nr); + char *basepath = NULL; + static char *credits_subdir = NULL; + static char *filename = NULL; + + if (credits_subdir == NULL) + credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY); + + checked_free(filename); + + // look for credits file in the game's base or current level set directory + basepath = (global ? options.base_directory : getCurrentLevelDir()); + + filename = getPath3(basepath, credits_subdir, basename); + if (fileExists(filename)) + return filename; + + return NULL; // cannot find credits file +} + +static char *getProgramInfoBasename(int nr) +{ + static char basename[32]; + + sprintf(basename, "program_%d.txt", nr + 1); + + return basename; +} + +char *getProgramInfoFilename(int nr) +{ + char *basename = getProgramInfoBasename(nr); + static char *info_subdir = NULL; + static char *filename = NULL; + + if (info_subdir == NULL) + info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY); + + checked_free(filename); + + // look for program info file in the game's base directory + filename = getPath3(options.base_directory, info_subdir, basename); + if (fileExists(filename)) + return filename; + + return NULL; // cannot find program info file +} + static char *getCorrectedArtworkBasename(char *basename) { return basename; @@ -817,7 +1072,7 @@ char *getCustomImageFilename(char *basename) { free(filename); - Warn("cannot find artwork file '%s' (using fallback)", basename); + WarnUsingFallback(basename); // 6th try: look for fallback artwork in old default artwork directory // (needed to prevent errors when trying to access unused artwork files) @@ -888,7 +1143,7 @@ char *getCustomSoundFilename(char *basename) { free(filename); - Warn("cannot find artwork file '%s' (using fallback)", basename); + WarnUsingFallback(basename); // 6th try: look for fallback artwork in old default artwork directory // (needed to prevent errors when trying to access unused artwork files) @@ -959,7 +1214,7 @@ char *getCustomMusicFilename(char *basename) { free(filename); - Warn("cannot find artwork file '%s' (using fallback)", basename); + WarnUsingFallback(basename); // 6th try: look for fallback artwork in old default artwork directory // (needed to prevent errors when trying to access unused artwork files) @@ -999,7 +1254,58 @@ char *getCustomArtworkLevelConfigFilename(int type) return filename; } -char *getCustomMusicDirectory(void) +static boolean directoryExists_CheckMusic(char *directory, boolean check_music) +{ + if (!directoryExists(directory)) + return FALSE; + + if (!check_music) + return TRUE; + + Directory *dir; + DirectoryEntry *dir_entry; + int num_music = getMusicListSize(); + boolean music_found = FALSE; + + if ((dir = openDirectory(directory)) == NULL) + return FALSE; + + while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries + { + char *basename = dir_entry->basename; + boolean music_already_used = FALSE; + int i; + + // skip all music files that are configured in music config file + for (i = 0; i < num_music; i++) + { + struct FileInfo *music = getMusicListEntry(i); + + if (strEqual(basename, music->filename)) + { + music_already_used = TRUE; + + break; + } + } + + if (music_already_used) + continue; + + if (FileIsMusic(dir_entry->filename)) + { + music_found = TRUE; + + break; + } + } + + closeDirectory(dir); + + return music_found; +} + +static char *getCustomMusicDirectoryExt(boolean check_music) { static char *directory = NULL; boolean skip_setup_artwork = FALSE; @@ -1010,7 +1316,7 @@ char *getCustomMusicDirectory(void) { // 1st try: look for special artwork in current level series directory directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY); - if (directoryExists(directory)) + if (directoryExists_CheckMusic(directory, check_music)) return directory; free(directory); @@ -1020,7 +1326,9 @@ char *getCustomMusicDirectory(void) { // 2nd try: look for special artwork configured in level series config directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR)); - if (directoryExists(directory)) + + // directory also valid if no unconfigured music found (no game music) + if (directoryExists_CheckMusic(directory, FALSE)) return directory; free(directory); @@ -1034,7 +1342,9 @@ char *getCustomMusicDirectory(void) { // 3rd try: look for special artwork in configured artwork directory directory = getStringCopy(getSetupArtworkDir(artwork.mus_current)); - if (directoryExists(directory)) + + // directory also valid if no unconfigured music found (no game music) + if (directoryExists_CheckMusic(directory, FALSE)) return directory; free(directory); @@ -1042,37 +1352,104 @@ char *getCustomMusicDirectory(void) // 4th try: look for default artwork in new default artwork directory directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR)); - if (directoryExists(directory)) + if (directoryExists_CheckMusic(directory, check_music)) return directory; free(directory); // 5th try: look for default artwork in old default artwork directory directory = getStringCopy(options.music_directory); - if (directoryExists(directory)) + if (directoryExists_CheckMusic(directory, check_music)) return directory; return NULL; // cannot find specified artwork file anywhere } +char *getCustomMusicDirectory(void) +{ + return getCustomMusicDirectoryExt(FALSE); +} + +char *getCustomMusicDirectory_NoConf(void) +{ + return getCustomMusicDirectoryExt(TRUE); +} + +void MarkTapeDirectoryUploadsAsComplete(char *level_subdir) +{ + char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME); + + touchFile(filename); + + checked_free(filename); +} + +void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir) +{ + char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME); + + unlink(filename); + + checked_free(filename); +} + +boolean CheckTapeDirectoryUploadsComplete(char *level_subdir) +{ + char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME); + boolean success = fileExists(filename); + + checked_free(filename); + + return success; +} + +void InitMissingFileHash(void) +{ + if (missing_file_hash == NULL) + freeSetupFileHash(missing_file_hash); + + missing_file_hash = newSetupFileHash(); +} + void InitTapeDirectory(char *level_subdir) { - createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE); - createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE); + boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir)); + + createDirectory(getUserGameDataDir(), "user data"); + createDirectory(getTapeDir(NULL), "main tape"); + createDirectory(getTapeDir(level_subdir), "level tape"); + + if (new_tape_dir) + MarkTapeDirectoryUploadsAsComplete(level_subdir); } void InitScoreDirectory(char *level_subdir) { - int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getScoreDir(NULL), "main score"); + createDirectory(getScoreDir(level_subdir), "level score"); +} - if (program.global_scores) - createDirectory(getCommonDataDir(), "common data", permissions); - else - createDirectory(getMainUserGameDataDir(), "main user data", permissions); +void InitScoreCacheDirectory(char *level_subdir) +{ + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getCacheDir(), "cache data"); + createDirectory(getScoreCacheDir(NULL), "main score"); + createDirectory(getScoreCacheDir(level_subdir), "level score"); +} + +void InitScoreTapeDirectory(char *level_subdir, int nr) +{ + InitScoreDirectory(level_subdir); - createDirectory(getScoreDir(NULL), "main score", permissions); - createDirectory(getScoreDir(level_subdir), "level score", permissions); + createDirectory(getScoreTapeDir(level_subdir, nr), "score tape"); +} + +void InitScoreCacheTapeDirectory(char *level_subdir, int nr) +{ + InitScoreCacheDirectory(level_subdir); + + createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape"); } static void SaveUserLevelInfo(void); @@ -1081,12 +1458,15 @@ void InitUserLevelDirectory(char *level_subdir) { if (!directoryExists(getUserLevelDir(level_subdir))) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); - createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE); - createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getUserLevelDir(NULL), "main user level"); if (setup.internal.create_user_levelset) + { + createDirectory(getUserLevelDir(level_subdir), "user level"); + SaveUserLevelInfo(); + } } } @@ -1094,24 +1474,24 @@ void InitNetworkLevelDirectory(char *level_subdir) { if (!directoryExists(getNetworkLevelDir(level_subdir))) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); - createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE); - createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE); - createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getNetworkDir(), "network data"); + createDirectory(getNetworkLevelDir(NULL), "main network level"); + createDirectory(getNetworkLevelDir(level_subdir), "network level"); } } void InitLevelSetupDirectory(char *level_subdir) { - createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE); - createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE); + createDirectory(getUserGameDataDir(), "user data"); + createDirectory(getLevelSetupDir(NULL), "main level setup"); + createDirectory(getLevelSetupDir(level_subdir), "level setup"); } static void InitCacheDirectory(void) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); - createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); + createDirectory(getCacheDir(), "cache data"); } @@ -1181,22 +1561,35 @@ TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node) return getFirstValidTreeInfoEntry(default_node); } -TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node) +static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node) { if (node == NULL) return NULL; - if (node->node_group) // enter level group (step down into tree) + if (node->node_group) // enter node group (step down into tree) return getFirstValidTreeInfoEntry(node->node_group); - else if (node->parent_link) // skip start entry of level group - { - if (node->next) // get first real level series entry - return getFirstValidTreeInfoEntry(node->next); - else // leave empty level group and go on - return getFirstValidTreeInfoEntry(node->node_parent->next); - } - else // this seems to be a regular level series + + if (node->parent_link) // skip first node (back link) of node group + get_next_node = TRUE; + + if (!get_next_node) // get current regular tree node return node; + + // get next regular tree node, or step up until one is found + while (node->next == NULL && node->node_parent != NULL) + node = node->node_parent; + + return getFirstValidTreeInfoEntry(node->next); +} + +TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node) +{ + return getValidTreeInfoEntryExt(node, FALSE); +} + +TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node) +{ + return getValidTreeInfoEntryExt(node, TRUE); } TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node) @@ -1316,30 +1709,56 @@ static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets) *ti_new = ti_cloned; } -static boolean adjustTreeGraphicsForEMC(TreeInfo *node) +static boolean adjustTreeArtworkForEMC(char **artwork_set_1, + char **artwork_set_2, + char **artwork_set, boolean prefer_2) { - boolean settings_changed = FALSE; + // do nothing if neither special artwork set 1 nor 2 are defined + if (!*artwork_set_1 && !*artwork_set_2) + return FALSE; - while (node) + boolean want_1 = (prefer_2 == FALSE); + boolean want_2 = (prefer_2 == TRUE); + boolean has_only_1 = (!*artwork_set && !*artwork_set_2); + boolean has_only_2 = (!*artwork_set && !*artwork_set_1); + char *artwork_set_new = NULL; + + // replace missing special artwork 1 or 2 with (optional) standard artwork + + if (!*artwork_set_1) + setString(artwork_set_1, *artwork_set); + + if (!*artwork_set_2) + setString(artwork_set_2, *artwork_set); + + // set standard artwork to either special artwork 1 or 2, as requested + + if (*artwork_set_1 && (want_1 || has_only_1)) + artwork_set_new = *artwork_set_1; + + if (*artwork_set_2 && (want_2 || has_only_2)) + artwork_set_new = *artwork_set_2; + + if (artwork_set_new && !strEqual(*artwork_set, artwork_set_new)) { - boolean want_ecs = (setup.prefer_aga_graphics == FALSE); - boolean want_aga = (setup.prefer_aga_graphics == TRUE); - boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga); - boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs); - char *graphics_set = NULL; + setString(artwork_set, artwork_set_new); - if (node->graphics_set_ecs && (want_ecs || has_only_ecs)) - graphics_set = node->graphics_set_ecs; + return TRUE; + } - if (node->graphics_set_aga && (want_aga || has_only_aga)) - graphics_set = node->graphics_set_aga; + return FALSE; +} - if (graphics_set && !strEqual(node->graphics_set, graphics_set)) - { - setString(&node->graphics_set, graphics_set); - settings_changed = TRUE; - } +static boolean adjustTreeGraphicsForEMC(TreeInfo *node) +{ + boolean settings_changed = FALSE; + while (node) + { + settings_changed |= adjustTreeArtworkForEMC(&node->graphics_set_ecs, + &node->graphics_set_aga, + &node->graphics_set, + setup.prefer_aga_graphics); if (node->node_group != NULL) settings_changed |= adjustTreeGraphicsForEMC(node->node_group); @@ -1355,24 +1774,10 @@ static boolean adjustTreeSoundsForEMC(TreeInfo *node) while (node) { - boolean want_default = (setup.prefer_lowpass_sounds == FALSE); - boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE); - boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass); - boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default); - char *sounds_set = NULL; - - if (node->sounds_set_default && (want_default || has_only_default)) - sounds_set = node->sounds_set_default; - - if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass)) - sounds_set = node->sounds_set_lowpass; - - if (sounds_set && !strEqual(node->sounds_set, sounds_set)) - { - setString(&node->sounds_set, sounds_set); - settings_changed = TRUE; - } - + settings_changed |= adjustTreeArtworkForEMC(&node->sounds_set_default, + &node->sounds_set_lowpass, + &node->sounds_set, + setup.prefer_lowpass_sounds); if (node->node_group != NULL) settings_changed |= adjustTreeSoundsForEMC(node->node_group); @@ -1382,9 +1787,10 @@ static boolean adjustTreeSoundsForEMC(TreeInfo *node) return settings_changed; } -void dumpTreeInfo(TreeInfo *node, int depth) +int dumpTreeInfo(TreeInfo *node, int depth) { char bullet_list[] = { '-', '*', 'o' }; + int num_leaf_nodes = 0; int i; if (depth == 0) @@ -1400,7 +1806,11 @@ void dumpTreeInfo(TreeInfo *node, int depth) DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n", bullet, node->name, node->identifier, (node->node_parent ? node->node_parent->identifier : "-"), - (node->node_group ? "[GROUP]" : "")); + (node->node_group ? "[GROUP]" : + node->is_copy ? "[COPY]" : "")); + + if (!node->node_group && !node->parent_link) + num_leaf_nodes++; /* // use for dumping artwork info tree @@ -1409,10 +1819,15 @@ void dumpTreeInfo(TreeInfo *node, int depth) */ if (node->node_group != NULL) - dumpTreeInfo(node->node_group, depth + 1); + num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1); node = node->next; } + + if (depth == 0) + Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes); + + return num_leaf_nodes; } void sortTreeInfoBySortFunction(TreeInfo **node_first, @@ -1474,7 +1889,7 @@ void sortTreeInfo(TreeInfo **node_first) // some stuff from "files.c" // ============================================================================ -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) #ifndef S_IRGRP #define S_IRGRP S_IRUSR #endif @@ -1499,7 +1914,7 @@ void sortTreeInfo(TreeInfo **node_first) #ifndef S_ISGID #define S_ISGID 0 #endif -#endif // PLATFORM_WIN32 +#endif // PLATFORM_WINDOWS // file permissions for newly written files #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH) @@ -1523,7 +1938,7 @@ char *getHomeDir(void) { static char *dir = NULL; -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) if (dir == NULL) { dir = checked_malloc(MAX_PATH + 1); @@ -1532,7 +1947,7 @@ char *getHomeDir(void) strcpy(dir, "."); } #elif defined(PLATFORM_EMSCRIPTEN) - dir = "/persistent"; + dir = PERSISTENT_DIRECTORY; #elif defined(PLATFORM_UNIX) if (dir == NULL) { @@ -1553,34 +1968,11 @@ char *getHomeDir(void) return dir; } -char *getCommonDataDir(void) -{ - static char *common_data_dir = NULL; - -#if defined(PLATFORM_WIN32) - if (common_data_dir == NULL) - { - char *dir = checked_malloc(MAX_PATH + 1); - - if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir)) - && !strEqual(dir, "")) // empty for Windows 95/98 - common_data_dir = getPath2(dir, program.userdata_subdir); - else - common_data_dir = options.rw_base_directory; - } -#else - if (common_data_dir == NULL) - common_data_dir = options.rw_base_directory; -#endif - - return common_data_dir; -} - char *getPersonalDataDir(void) { static char *personal_data_dir = NULL; -#if defined(PLATFORM_MACOSX) +#if defined(PLATFORM_MAC) if (personal_data_dir == NULL) personal_data_dir = getPath2(getHomeDir(), "Documents"); #else @@ -1634,7 +2026,7 @@ static mode_t posix_umask(mode_t mask) static int posix_mkdir(const char *pathname, mode_t mode) { -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) return mkdir(pathname); #else return mkdir(pathname, mode); @@ -1650,13 +2042,14 @@ static boolean posix_process_running_setgid(void) #endif } -void createDirectory(char *dir, char *text, int permission_class) +void createDirectory(char *dir, char *text) { if (directoryExists(dir)) return; // leave "other" permissions in umask untouched, but ensure group parts // of USERDATA_DIR_MODE are not masked + int permission_class = PERMS_PRIVATE; mode_t dir_mode = (permission_class == PERMS_PRIVATE ? DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC); mode_t last_umask = posix_umask(0); @@ -1685,17 +2078,17 @@ void createDirectory(char *dir, char *text, int permission_class) void InitMainUserDataDirectory(void) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); } void InitUserDataDirectory(void) { - createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE); + createDirectory(getMainUserGameDataDir(), "main user data"); if (user.nr != 0) { - createDirectory(getUserDir(-1), "users", PERMS_PRIVATE); - createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE); + createDirectory(getUserDir(-1), "users"); + createDirectory(getUserDir(user.nr), "user data"); } } @@ -1711,21 +2104,6 @@ void SetFilePermissions(char *filename, int permission_class) chmod(filename, perms); } -char *getCookie(char *file_type) -{ - static char cookie[MAX_COOKIE_LEN + 1]; - - if (strlen(program.cookie_prefix) + 1 + - strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN) - return "[COOKIE ERROR]"; // should never happen - - sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d", - program.cookie_prefix, file_type, - program.version_super, program.version_major); - - return cookie; -} - void fprintFileHeader(FILE *file, char *basename) { char *prefix = "# "; @@ -1933,7 +2311,7 @@ unsigned int get_hash_from_key(void *key) return hash; } -static int keys_are_equal(void *key1, void *key2) +int hash_keys_are_equal(void *key1, void *key2) { return (strEqual((char *)key1, (char *)key2)); } @@ -1941,7 +2319,7 @@ static int keys_are_equal(void *key1, void *key2) SetupFileHash *newSetupFileHash(void) { SetupFileHash *new_hash = - create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal); + create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal); if (new_hash == NULL) Fail("create_hashtable() failed -- out of memory"); @@ -2403,6 +2781,7 @@ SetupFileHash *loadSetupFileHash(char *filename) // ============================================================================ #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series" +#define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used" #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level" #define TOKEN_STR_HANDICAP_LEVEL "handicap_level" #define TOKEN_STR_LAST_USER "last_user" @@ -2435,11 +2814,15 @@ SetupFileHash *loadSetupFileHash(char *filename) #define LEVELINFO_TOKEN_FILENAME 24 #define LEVELINFO_TOKEN_FILETYPE 25 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26 -#define LEVELINFO_TOKEN_HANDICAP 27 -#define LEVELINFO_TOKEN_SKIP_LEVELS 28 -#define LEVELINFO_TOKEN_USE_EMC_TILES 29 +#define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27 +#define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28 +#define LEVELINFO_TOKEN_HANDICAP 29 +#define LEVELINFO_TOKEN_TIME_LIMIT 30 +#define LEVELINFO_TOKEN_SKIP_LEVELS 31 +#define LEVELINFO_TOKEN_USE_EMC_TILES 32 +#define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33 -#define NUM_LEVELINFO_TOKENS 30 +#define NUM_LEVELINFO_TOKENS 34 static LevelDirTree ldi; @@ -2473,9 +2856,13 @@ static struct TokenInfo levelinfo_tokens[] = { TYPE_STRING, &ldi.level_filename, "filename" }, { TYPE_STRING, &ldi.level_filetype, "filetype" }, { TYPE_STRING, &ldi.special_flags, "special_flags" }, + { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" }, + { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" }, { TYPE_BOOLEAN, &ldi.handicap, "handicap" }, + { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" }, { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }, - { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" } + { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }, + { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" } }; static struct TokenInfo artworkinfo_tokens[] = @@ -2570,6 +2957,9 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type) ti->special_flags = NULL; + ti->empty_level_name = NULL; + ti->force_level_name = FALSE; + ti->levels = 0; ti->first_level = 0; ti->last_level = 0; @@ -2577,9 +2967,11 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type) ti->handicap_level = 0; ti->readonly = TRUE; ti->handicap = TRUE; + ti->time_limit = TRUE; ti->skip_levels = FALSE; ti->use_emc_tiles = FALSE; + ti->info_screens_from_main = FALSE; } } @@ -2652,6 +3044,9 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent) ti->special_flags = getStringCopy(parent->special_flags); + ti->empty_level_name = getStringCopy(parent->empty_level_name); + ti->force_level_name = parent->force_level_name; + ti->levels = parent->levels; ti->first_level = parent->first_level; ti->last_level = parent->last_level; @@ -2659,9 +3054,11 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent) ti->handicap_level = parent->handicap_level; ti->readonly = parent->readonly; ti->handicap = parent->handicap; + ti->time_limit = parent->time_limit; ti->skip_levels = parent->skip_levels; ti->use_emc_tiles = parent->use_emc_tiles; + ti->info_screens_from_main = parent->info_screens_from_main; } } @@ -2714,6 +3111,9 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti) ti_copy->special_flags = getStringCopy(ti->special_flags); + ti_copy->empty_level_name = getStringCopy(ti->empty_level_name); + ti_copy->force_level_name = ti->force_level_name; + ti_copy->levels = ti->levels; ti_copy->first_level = ti->first_level; ti_copy->last_level = ti->last_level; @@ -2728,9 +3128,11 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti) ti_copy->user_defined = ti->user_defined; ti_copy->readonly = ti->readonly; ti_copy->handicap = ti->handicap; + ti_copy->time_limit = ti->time_limit; ti_copy->skip_levels = ti->skip_levels; ti_copy->use_emc_tiles = ti->use_emc_tiles; + ti_copy->info_screens_from_main = ti->info_screens_from_main; ti_copy->color = ti->color; ti_copy->class_desc = getStringCopy(ti->class_desc); @@ -2938,6 +3340,17 @@ static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent) } } +TreeInfo *addTopTreeInfoNode(TreeInfo *node_first) +{ + // add top tree node with back link node in previous tree + node_first = createTopTreeInfoNode(node_first); + + // set all parent links (back links) in complete tree + setTreeInfoParentNodes(node_first, NULL); + + return node_first; +} + // ---------------------------------------------------------------------------- // functions for handling level and custom artwork info cache @@ -3490,7 +3903,7 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first, (leveldir_new->user_defined || !leveldir_new->handicap ? leveldir_new->last_level : leveldir_new->first_level); - DrawInitText(leveldir_new->name, 150, FC_YELLOW); + DrawInitTextItem(leveldir_new->name); pushTreeInfo(node_first, leveldir_new); @@ -3577,9 +3990,13 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first, level_directory, "."); } - if (!valid_entry_found) + boolean valid_entry_expected = + (strEqual(level_directory, options.level_directory) || + setup.internal.create_user_levelset); + + if (valid_entry_expected && !valid_entry_found) Warn("cannot find any valid level series in directory '%s'", - level_directory); + level_directory); } boolean AdjustGraphicsForEMC(void) @@ -3606,7 +4023,7 @@ void LoadLevelInfo(void) { InitUserLevelDirectory(getLoginName()); - DrawInitText("Loading level series", 120, FC_GREEN); + DrawInitTextHead("Loading level series"); LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory); LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL)); @@ -3879,7 +4296,7 @@ void LoadArtworkInfo(void) { LoadArtworkInfoCache(); - DrawInitText("Looking for custom artwork", 120, FC_GREEN); + DrawInitTextHead("Looking for custom artwork"); LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL, options.graphics_directory, @@ -4014,7 +4431,7 @@ static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node, setArtworkInfoCacheEntry(artwork_new, level_node, type); } - DrawInitText(level_node->name, 150, FC_YELLOW); + DrawInitTextItem(level_node->name); if (level_node->node_group != NULL) { @@ -4072,18 +4489,15 @@ static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node) LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE); LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE); - // add top tree node over all three separate sub-trees - *artwork_node = createTopTreeInfoNode(*artwork_node); - - // set all parent links (back links) in complete artwork tree - setTreeInfoParentNodes(*artwork_node, NULL); + // add top tree node over all sub-trees and set parent links + *artwork_node = addTopTreeInfoNode(*artwork_node); } void LoadLevelArtworkInfo(void) { print_timestamp_init("LoadLevelArtworkInfo"); - DrawInitText("Looking for custom level artwork", 120, FC_GREEN); + DrawInitTextHead("Looking for custom level artwork"); print_timestamp_time("DrawTimeText"); @@ -4180,6 +4594,12 @@ static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir, TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first, tree_subdir_new); + // if not found, check if added node is level group or artwork group + if (tree_node_new == NULL) + tree_node_new = getTreeInfoFromIdentifierExt(*tree_node_first, + tree_subdir_new, + TREE_NODE_TYPE_GROUP); + if (tree_node_new == NULL) // should not happen return FALSE; @@ -4333,7 +4753,7 @@ boolean CreateUserLevelSet(char *level_subdir, char *level_name, int i; // create user level sub-directory, if needed - createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE); + createDirectory(getUserLevelDir(level_subdir), "user level"); filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME); @@ -4609,33 +5029,51 @@ static void UpdateLastPlayedLevels_List(void) setString(&last_level_series[0], leveldir_current->identifier); } -static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store) +#define LAST_PLAYED_MODE_SET 1 +#define LAST_PLAYED_MODE_SET_FORCED 2 +#define LAST_PLAYED_MODE_GET 3 + +static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode) { static char *identifier = NULL; - if (store) + if (mode == LAST_PLAYED_MODE_SET) { setString(&identifier, (node && node->is_copy ? node->identifier : NULL)); - - return NULL; // not used } - else + else if (mode == LAST_PLAYED_MODE_SET_FORCED) + { + setString(&identifier, (node ? node->identifier : NULL)); + } + else if (mode == LAST_PLAYED_MODE_GET) { TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first, identifier, TREE_NODE_TYPE_COPY); return (node_new != NULL ? node_new : node); } + + return NULL; // not used } void StoreLastPlayedLevels(TreeInfo *node) { - StoreOrRestoreLastPlayedLevels(node, TRUE); + StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET); +} + +void ForcedStoreLastPlayedLevels(TreeInfo *node) +{ + StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED); } void RestoreLastPlayedLevels(TreeInfo **node) { - *node = StoreOrRestoreLastPlayedLevels(*node, FALSE); + *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET); +} + +boolean CheckLastPlayedLevels(void) +{ + return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL); } void LoadLevelSetup_LastSeries(void) @@ -4673,6 +5111,13 @@ void LoadLevelSetup_LastSeries(void) if (leveldir_current == NULL) leveldir_current = getFirstValidTreeInfoEntry(leveldir_first); + char *last_played_menu_used = + getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED); + + // store if last level set was selected from "last played" menu + if (strEqual(last_played_menu_used, "true")) + ForcedStoreLastPlayedLevels(leveldir_current); + for (i = 0; i < MAX_LEVELDIR_HISTORY; i++) { char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10]; @@ -4735,11 +5180,18 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series) fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated"); fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES, - leveldir_current->identifier)); + leveldir_current->identifier)); + + // store if last level set was selected from "last played" menu + boolean last_played_menu_used = CheckLastPlayedLevels(); + char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used); + + fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED, + setup_value)); for (i = 0; last_level_series[i] != NULL; i++) { - char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10]; + char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1]; sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i); diff --git a/src/libgame/setup.h b/src/libgame/setup.h index c8c5fecc..59700cf9 100644 --- a/src/libgame/setup.h +++ b/src/libgame/setup.h @@ -264,15 +264,25 @@ char *setLevelArtworkDir(TreeInfo *); char *getProgramMainDataPath(char *, char *); char *getProgramConfigFilename(char *); char *getTapeFilename(int); +char *getTemporaryTapeFilename(void); +char *getDefaultSolutionTapeFilename(int); +char *getSokobanSolutionTapeFilename(int); char *getSolutionTapeFilename(int); char *getScoreFilename(int); +char *getScoreCacheFilename(int); +char *getScoreTapeBasename(char *); +char *getScoreTapeFilename(char *, int); +char *getScoreCacheTapeFilename(char *, int); char *getSetupFilename(void); char *getDefaultSetupFilename(void); +char *getPlatformSetupFilename(void); char *getEditorSetupFilename(void); char *getHelpAnimFilename(void); char *getHelpTextFilename(void); -char *getLevelSetInfoFilename(void); +char *getLevelSetInfoFilename(int); char *getLevelSetTitleMessageFilename(int, boolean); +char *getCreditsFilename(int, boolean); +char *getProgramInfoFilename(int); char *getImageFilename(char *); char *getCustomImageFilename(char *); char *getCustomSoundFilename(char *); @@ -281,9 +291,18 @@ char *getCustomArtworkFilename(char *, int); char *getCustomArtworkConfigFilename(int); char *getCustomArtworkLevelConfigFilename(int); char *getCustomMusicDirectory(void); +char *getCustomMusicDirectory_NoConf(void); +void MarkTapeDirectoryUploadsAsComplete(char *); +void MarkTapeDirectoryUploadsAsIncomplete(char *); +boolean CheckTapeDirectoryUploadsComplete(char *); + +void InitMissingFileHash(void); void InitTapeDirectory(char *); void InitScoreDirectory(char *); +void InitScoreCacheDirectory(char *); +void InitScoreTapeDirectory(char *, int); +void InitScoreCacheTapeDirectory(char *, int); void InitUserLevelDirectory(char *); void InitNetworkLevelDirectory(char *); void InitLevelSetupDirectory(char *); @@ -296,20 +315,21 @@ int numTreeInfo(TreeInfo *); boolean validLevelSeries(TreeInfo *); TreeInfo *getValidLevelSeries(TreeInfo *, TreeInfo *); TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *); +TreeInfo *getNextValidTreeInfoEntry(TreeInfo *); TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *); int numTreeInfoInGroup(TreeInfo *); int getPosFromTreeInfo(TreeInfo *); TreeInfo *getTreeInfoFromPos(TreeInfo *, int); TreeInfo *getTreeInfoFromIdentifier(TreeInfo *, char *); -void dumpTreeInfo(TreeInfo *, int); +int dumpTreeInfo(TreeInfo *, int); void sortTreeInfoBySortFunction(TreeInfo **, int (*compare_function)(const void *, const void *)); void sortTreeInfo(TreeInfo **); void freeTreeInfo(TreeInfo *); +TreeInfo *addTopTreeInfoNode(TreeInfo *); char *getHomeDir(void); -char *getCommonDataDir(void); char *getPersonalDataDir(void); char *getMainUserGameDataDir(void); char *getUserGameDataDir(void); @@ -319,13 +339,13 @@ char *getUserLevelDir(char *); char *getNetworkLevelDir(char *); char *getCurrentLevelDir(void); char *getNewUserLevelSubdir(void); +char *getTapeDir(char *); -void createDirectory(char *, char *, int); +void createDirectory(char *, char *); void InitMainUserDataDirectory(void); void InitUserDataDirectory(void); void SetFilePermissions(char *, int); -char *getCookie(char *); void fprintFileHeader(FILE *, char *); int getFileVersionFromCookieString(const char *); boolean checkCookieString(const char *, const char *); @@ -352,6 +372,7 @@ char *getSetupValue(int, void *); char *getSetupLine(struct TokenInfo *, char *, int); unsigned int get_hash_from_key(void *); +int hash_keys_are_equal(void *, void *); int GetZipFileTreeType(char *); char *ExtractZipFileIntoDirectory(char *, char *, int); @@ -376,7 +397,9 @@ boolean CreateUserLevelSet(char *, char *, char *, int, boolean); void UpdateLastPlayedLevels_TreeInfo(void); void StoreLastPlayedLevels(TreeInfo *); +void ForcedStoreLastPlayedLevels(TreeInfo *); void RestoreLastPlayedLevels(TreeInfo **); +boolean CheckLastPlayedLevels(void); void LoadLevelSetup_LastSeries(void); void SaveLevelSetup_LastSeries(void); diff --git a/src/libgame/sound.c b/src/libgame/sound.c index 2375c06f..473850dd 100644 --- a/src/libgame/sound.c +++ b/src/libgame/sound.c @@ -48,10 +48,10 @@ #define SOUND_VOLUME_LEFT(x) (stereo_volume[x]) #define SOUND_VOLUME_RIGHT(x) (stereo_volume[SOUND_MAX_LEFT2RIGHT-x]) -#define SAME_SOUND_NR(x,y) ((x).nr == (y).nr) -#define SAME_SOUND_DATA(x,y) ((x).data_ptr == (y).data_ptr) +#define SAME_SOUND_NR(x, y) ((x).nr == (y).nr) +#define SAME_SOUND_DATA(x, y) ((x).data_ptr == (y).data_ptr) -#define SOUND_VOLUME_FROM_PERCENT(v,p) ((p) < 0 ? SOUND_MIN_VOLUME : \ +#define SOUND_VOLUME_FROM_PERCENT(v, p) ((p) < 0 ? SOUND_MIN_VOLUME : \ (p) > 100 ? (v) : \ (p) * (v) / 100) @@ -59,7 +59,7 @@ #define SOUND_VOLUME_LOOPS(v) SOUND_VOLUME_FROM_PERCENT(v, setup.volume_loops) #define SOUND_VOLUME_MUSIC(v) SOUND_VOLUME_FROM_PERCENT(v, setup.volume_music) -#define SETUP_SOUND_VOLUME(v,s) ((s) & SND_CTRL_MUSIC ? \ +#define SETUP_SOUND_VOLUME(v, s) ((s) & SND_CTRL_MUSIC ? \ SOUND_VOLUME_MUSIC(v) : \ (s) & SND_CTRL_LOOP ? \ SOUND_VOLUME_LOOPS(v) : \ @@ -166,8 +166,8 @@ static boolean Mixer_ChannelExpired(int channel) if (expire_loop_sounds && IS_LOOP(mixer[channel]) && !IS_MUSIC(mixer[channel]) && - DelayReached(&mixer[channel].playing_starttime, - SOUND_LOOP_EXPIRATION_TIME)) + DelayReachedExt2(&mixer[channel].playing_starttime, + SOUND_LOOP_EXPIRATION_TIME, Counter())) return TRUE; if (!Mix_Playing(channel)) @@ -233,7 +233,7 @@ static void Mixer_PlayMusicChannel(void) Mix_VolumeMusic(mixer[audio.music_channel].volume); Mix_FadeInMusic(mixer[audio.music_channel].data_ptr, loops, 100); -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) // playing MIDI music is broken since Windows Vista, as it sets the volume // for MIDI music also for all other sounds and music, which cannot be set // back to normal unless playing MIDI music again with that desired volume @@ -281,7 +281,7 @@ static void Mixer_FadeMusicChannel(void) Mix_FadeOutMusic(SOUND_FADING_INTERVAL); -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) // playing MIDI music is broken since Windows Vista, as it sets the volume // for MIDI music also for all other sounds and music, which cannot be set // back to normal unless playing MIDI music again with that desired volume @@ -587,11 +587,19 @@ static void *Load_WAV_or_MOD(char *filename) return NULL; } +static int compareMusicInfo(const void *object1, const void *object2) +{ + const MusicInfo *mi1 = *((MusicInfo **)object1); + const MusicInfo *mi2 = *((MusicInfo **)object2); + + return strcmp(mi1->source_filename, mi2->source_filename); +} + static void LoadCustomMusic_NoConf(void) { static boolean draw_init_text = TRUE; // only draw at startup static char *last_music_directory = NULL; - char *music_directory = getCustomMusicDirectory(); + char *music_directory = getCustomMusicDirectory_NoConf(); Directory *dir; DirectoryEntry *dir_entry; int num_music = getMusicListSize(); @@ -609,17 +617,21 @@ static void LoadCustomMusic_NoConf(void) FreeAllMusic_NoConf(); - if ((dir = openDirectory(music_directory)) == NULL) + if (music_directory == NULL) { - Warn("cannot read music directory '%s'", music_directory); + Warn("cannot find music directory with unconfigured music"); - audio.music_available = FALSE; + return; + } + else if ((dir = openDirectory(music_directory)) == NULL) + { + Warn("cannot read music directory '%s'", music_directory); return; } if (draw_init_text) - DrawInitText("Loading music", 120, FC_GREEN); + DrawInitTextHead("Loading music"); while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries { @@ -644,7 +656,7 @@ static void LoadCustomMusic_NoConf(void) continue; if (draw_init_text) - DrawInitText(basename, 150, FC_YELLOW); + DrawInitTextItem(basename); if (FileIsMusic(dir_entry->filename)) mus_info = Load_WAV_or_MOD(dir_entry->filename); @@ -660,6 +672,9 @@ static void LoadCustomMusic_NoConf(void) closeDirectory(dir); + // sort music files by filename + qsort(Music_NoConf, num_music_noconf, sizeof(MusicInfo *), compareMusicInfo); + draw_init_text = FALSE; } @@ -675,6 +690,11 @@ int getMusicListSize(void) music_info->num_dynamic_file_list_entries); } +int getMusicListSize_NoConf(void) +{ + return num_music_noconf; +} + struct FileInfo *getSoundListEntry(int pos) { int num_sounds = getSoundListSize(); @@ -741,6 +761,16 @@ static MusicInfo *getMusicInfoEntryFromMusicID(int pos) return mus_info[list_pos]; } +char *getSoundInfoEntryFilename(int pos) +{ + SoundInfo *snd_info = getSoundInfoEntryFromSoundID(pos); + + if (snd_info == NULL) + return NULL; + + return getBaseNamePtr(snd_info->source_filename); +} + char *getMusicInfoEntryFilename(int pos) { MusicInfo *mus_info = getMusicInfoEntryFromMusicID(pos); @@ -1103,7 +1133,7 @@ static void ReloadCustomMusic(void) LoadCustomMusic_NoConf(); } -void InitReloadCustomSounds(char *set_identifier) +void InitReloadCustomSounds(void) { if (!audio.sound_available) return; @@ -1111,7 +1141,7 @@ void InitReloadCustomSounds(char *set_identifier) ReloadCustomSounds(); } -void InitReloadCustomMusic(char *set_identifier) +void InitReloadCustomMusic(void) { if (!audio.music_available) return; diff --git a/src/libgame/sound.h b/src/libgame/sound.h index a2a65999..20852138 100644 --- a/src/libgame/sound.h +++ b/src/libgame/sound.h @@ -35,7 +35,7 @@ #define DEFAULT_AUDIO_SAMPLE_RATE AUDIO_SAMPLE_RATE_22050 -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) #define DEFAULT_AUDIO_FRAGMENT_SIZE AUDIO_FRAGMENT_SIZE_1024 #else #define DEFAULT_AUDIO_FRAGMENT_SIZE AUDIO_FRAGMENT_SIZE_512 @@ -122,8 +122,10 @@ void ExpireSoundLoops(boolean); int getSoundListSize(void); int getMusicListSize(void); +int getMusicListSize_NoConf(void); struct FileInfo *getSoundListEntry(int); struct FileInfo *getMusicListEntry(int); +char *getSoundInfoEntryFilename(int); char *getMusicInfoEntryFilename(int); char *getCurrentlyPlayingMusicFilename(void); int getSoundListPropertyMappingSize(void); @@ -134,8 +136,8 @@ void InitSoundList(struct ConfigInfo *, int, struct ConfigTypeInfo *, char **, char **, char **, char **, char **); void InitMusicList(struct ConfigInfo *, int, struct ConfigTypeInfo *, char **, char **, char **, char **, char **); -void InitReloadCustomSounds(char *); -void InitReloadCustomMusic(char *); +void InitReloadCustomSounds(void); +void InitReloadCustomMusic(void); void FreeAllSounds(void); void FreeAllMusic(void); diff --git a/src/libgame/system.c b/src/libgame/system.c index a2f7ebec..c1abb8a4 100644 --- a/src/libgame/system.c +++ b/src/libgame/system.c @@ -69,22 +69,23 @@ int FrameCounter = 0; // init/close functions // ============================================================================ -void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir, - char *program_title, char *icon_title, +void InitProgramInfo(char *command_filename, + char *config_filename, char *userdata_subdir, + char *program_basename, char *program_title, char *icon_filename, char *cookie_prefix, char *program_version_string, int program_version) { - program.command_basepath = getBasePath(argv0); - program.command_basename = getBaseName(argv0); + program.command_basepath = getBasePath(command_filename); + program.command_basename = getBaseName(command_filename); program.config_filename = config_filename; program.userdata_subdir = userdata_subdir; program.userdata_path = getMainUserGameDataDir(); + program.program_basename = program_basename; program.program_title = program_title; program.window_title = "(undefined)"; - program.icon_title = icon_title; program.icon_filename = icon_filename; @@ -98,31 +99,12 @@ void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir, program.version_string = program_version_string; - program.log_filename[LOG_OUT_ID] = getLogFilename(LOG_OUT_BASENAME); - program.log_filename[LOG_ERR_ID] = getLogFilename(LOG_ERR_BASENAME); - program.log_file[LOG_OUT_ID] = program.log_file_default[LOG_OUT_ID] = stdout; - program.log_file[LOG_ERR_ID] = program.log_file_default[LOG_ERR_ID] = stderr; + program.log_filename = getLogFilename(getLogBasename(program_basename)); + program.log_file = program.log_file_default = stdout; - program.headless = FALSE; + program.api_thread_count = 0; -#if defined(PLATFORM_EMSCRIPTEN) - EM_ASM - ( - Module.sync_done = 0; - - FS.mkdir('/persistent'); // create persistent data directory - FS.mount(IDBFS, {}, '/persistent'); // mount with IDBFS filesystem type - FS.syncfs(true, function(err) // sync persistent data into memory - { - assert(!err); - Module.sync_done = 1; - }); - ); - - // wait for persistent data to be synchronized to memory - while (emscripten_run_script_int("Module.sync_done") == 0) - Delay(20); -#endif + program.headless = FALSE; } void InitNetworkInfo(boolean enabled, boolean connected, boolean serveronly, @@ -146,33 +128,8 @@ void InitRuntimeInfo() #else runtime.uses_touch_device = FALSE; #endif -} - -void InitScoresInfo(void) -{ - char *global_scores_dir = getPath2(getCommonDataDir(), SCORES_DIRECTORY); - - program.global_scores = directoryExists(global_scores_dir); - program.many_scores_per_name = !program.global_scores; - -#if 0 - if (options.debug) - { - if (program.global_scores) - { - Debug("internal:path", "Using global, multi-user scores directory '%s'.", - global_scores_dir); - Debug("internal:path", "Remove to enable single-user scores directory."); - Debug("internal:path", "(This enables multipe score entries per user.)"); - } - else - { - Debug("internal:path", "Using private, single-user scores directory."); - } - } -#endif - free(global_scores_dir); + runtime.use_api_server = setup.use_api_server; } void SetWindowTitle(void) @@ -206,10 +163,12 @@ void InitExitFunction(void (*exit_function)(int)) void InitPlatformDependentStuff(void) { + InitEmscriptenFilesystem(); + // this is initialized in GetOptions(), but may already be used before options.verbose = TRUE; - OpenLogFiles(); + OpenLogFile(); int sdl_init_flags = SDL_INIT_EVENTS | SDL_INIT_NOPARACHUTE; @@ -221,7 +180,7 @@ void InitPlatformDependentStuff(void) void ClosePlatformDependentStuff(void) { - CloseLogFiles(); + CloseLogFile(); } void InitGfxFieldInfo(int sx, int sy, int sxsize, int sysize, @@ -313,7 +272,7 @@ void InitGfxClipRegion(boolean enabled, int x, int y, int width, int height) gfx.clip_height = height; } -void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(void)) +void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(boolean)) { gfx.draw_busy_anim_function = draw_busy_anim_function; } @@ -328,11 +287,16 @@ void InitGfxDrawGlobalBorderFunction(void (*draw_global_border_function)(int)) gfx.draw_global_border_function = draw_global_border_function; } -void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int)) +void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int, int)) { gfx.draw_tile_cursor_function = draw_tile_cursor_function; } +void InitGfxDrawEnvelopeRequestFunction(void (*draw_envelope_request_function)(int)) +{ + gfx.draw_envelope_request_function = draw_envelope_request_function; +} + void InitGfxCustomArtworkInfo(void) { gfx.override_level_graphics = FALSE; @@ -489,7 +453,8 @@ void SetDrawBackgroundMask(int draw_background_mask) gfx.draw_background_mask = draw_background_mask; } -static void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask) +void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask, + int x, int y, int width, int height) { if (background_bitmap_tile != NULL) gfx.background_bitmap_mask |= mask; @@ -500,40 +465,19 @@ static void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask) return; if (mask == REDRAW_ALL) - BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0, + BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, + x, y, width, height, 0, 0, video.width, video.height); else if (mask == REDRAW_FIELD) - BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0, + BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, + x, y, width, height, gfx.real_sx, gfx.real_sy, gfx.full_sxsize, gfx.full_sysize); else if (mask == REDRAW_DOOR_1) - BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0, + BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, + x, y, width, height, gfx.dx, gfx.dy, gfx.dxsize, gfx.dysize); } -void SetWindowBackgroundBitmap(Bitmap *background_bitmap_tile) -{ - // remove every mask before setting mask for window - // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) - SetBackgroundBitmap(NULL, 0xffff); // !!! FIX THIS !!! - SetBackgroundBitmap(background_bitmap_tile, REDRAW_ALL); -} - -void SetMainBackgroundBitmap(Bitmap *background_bitmap_tile) -{ - // remove window area mask before setting mask for main area - // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) - SetBackgroundBitmap(NULL, REDRAW_ALL); // !!! FIX THIS !!! - SetBackgroundBitmap(background_bitmap_tile, REDRAW_FIELD); -} - -void SetDoorBackgroundBitmap(Bitmap *background_bitmap_tile) -{ - // remove window area mask before setting mask for door area - // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) - SetBackgroundBitmap(NULL, REDRAW_ALL); // !!! FIX THIS !!! - SetBackgroundBitmap(background_bitmap_tile, REDRAW_DOOR_1); -} - // ============================================================================ // video functions @@ -607,14 +551,14 @@ void InitVideoBuffer(int width, int height, int depth, boolean fullscreen) video.window_scaling_available = WINDOW_SCALING_STATUS; video.frame_counter = 0; - video.frame_delay = 0; - video.frame_delay_value = GAME_FRAME_DELAY; + video.frame_delay.count = 0; + video.frame_delay.value = GAME_FRAME_DELAY; video.shifted_up = FALSE; video.shifted_up_pos = 0; video.shifted_up_pos_last = 0; - video.shifted_up_delay = 0; - video.shifted_up_delay_value = ONE_SECOND_DELAY / 4; + video.shifted_up_delay.count = 0; + video.shifted_up_delay.value = ONE_SECOND_DELAY / 4; SDLInitVideoBuffer(fullscreen); @@ -655,9 +599,22 @@ void FreeBitmap(Bitmap *bitmap) free(bitmap); } +void ResetBitmapAlpha(Bitmap *bitmap) +{ + bitmap->alpha[0][0] = -1; + bitmap->alpha[0][1] = -1; + bitmap->alpha[1][0] = -1; + bitmap->alpha[1][1] = -1; + bitmap->alpha_next_blit = -1; +} + Bitmap *CreateBitmapStruct(void) { - return checked_calloc(sizeof(Bitmap)); + Bitmap *new_bitmap = checked_calloc(sizeof(Bitmap)); + + ResetBitmapAlpha(new_bitmap); + + return new_bitmap; } Bitmap *CreateBitmap(int width, int height, int depth) @@ -730,8 +687,7 @@ void SetRedrawMaskFromArea(int x, int y, int width, int height) redraw_mask = REDRAW_ALL; } -static boolean CheckDrawingArea(int x, int y, int width, int height, - int draw_mask) +static boolean CheckDrawingArea(int x, int y, int draw_mask) { if (draw_mask == REDRAW_NONE) return FALSE; @@ -765,15 +721,15 @@ boolean DrawingDeactivatedField(void) return FALSE; } -boolean DrawingDeactivated(int x, int y, int width, int height) +boolean DrawingDeactivated(int x, int y) { - return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask); + return CheckDrawingArea(x, y, gfx.draw_deactivation_mask); } boolean DrawingOnBackground(int x, int y) { - return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) && - CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask)); + return (CheckDrawingArea(x, y, gfx.background_bitmap_mask) && + CheckDrawingArea(x, y, gfx.draw_background_mask)); } static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y, @@ -829,6 +785,12 @@ static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y, return TRUE; } +void SetBitmapAlphaNextBlit(Bitmap *bitmap, int alpha) +{ + // set alpha value for next blitting of bitmap + bitmap->alpha_next_blit = alpha; +} + void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap, int src_x, int src_y, int width, int height, int dst_x, int dst_y) @@ -842,7 +804,7 @@ void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap, if (src_bitmap == NULL || dst_bitmap == NULL) return; - if (DrawingDeactivated(dst_x, dst_y, width, height)) + if (DrawingDeactivated(dst_x, dst_y)) return; if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) || @@ -946,7 +908,10 @@ void FadeRectangle(int x, int y, int width, int height, void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height, Pixel color) { - if (DrawingDeactivated(x, y, width, height)) + if (program.headless) + return; + + if (DrawingDeactivated(x, y)) return; if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE)) @@ -973,7 +938,7 @@ void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap, int src_x, int src_y, int width, int height, int dst_x, int dst_y) { - if (DrawingDeactivated(dst_x, dst_y, width, height)) + if (DrawingDeactivated(dst_x, dst_y)) return; sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height, @@ -1049,12 +1014,6 @@ void BlitToScreenMasked(Bitmap *bitmap, BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y); } -void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y, - int to_x, int to_y) -{ - SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL); -} - void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y, int to_x, int to_y) { @@ -1123,15 +1082,6 @@ Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r, return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b); } -Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color) -{ - unsigned int color_r = (color >> 16) & 0xff; - unsigned int color_g = (color >> 8) & 0xff; - unsigned int color_b = (color >> 0) & 0xff; - - return GetPixelFromRGB(bitmap, color_r, color_g, color_b); -} - void KeyboardAutoRepeatOn(void) { keyrepeat_status = TRUE; @@ -1149,12 +1099,12 @@ boolean SetVideoMode(boolean fullscreen) void SetVideoFrameDelay(unsigned int frame_delay_value) { - video.frame_delay_value = frame_delay_value; + video.frame_delay.value = frame_delay_value; } unsigned int GetVideoFrameDelay(void) { - return video.frame_delay_value; + return video.frame_delay.value; } boolean ChangeVideoModeIfNeeded(boolean fullscreen) @@ -1235,7 +1185,7 @@ void ReloadCustomImage(Bitmap *bitmap, char *basename) free(new_bitmap); } -static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height) +Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height) { return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height); } @@ -1244,14 +1194,31 @@ void ReCreateGameTileSizeBitmap(Bitmap **bitmaps) { if (bitmaps[IMG_BITMAP_CUSTOM]) { - FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]); + // check if original sized bitmap points to custom sized bitmap + if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] == bitmaps[IMG_BITMAP_CUSTOM]) + { + SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]); + + // keep pointer of previous custom size bitmap + bitmaps[IMG_BITMAP_OTHER] = bitmaps[IMG_BITMAP_CUSTOM]; + + // set original bitmap pointer to scaled original bitmap of other size + bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER]; + + SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]); + } + else + { + FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]); + } bitmaps[IMG_BITMAP_CUSTOM] = NULL; } if (gfx.game_tile_size == gfx.standard_tile_size) { - bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD]; + // set game bitmap pointer to standard sized bitmap (already existing) + bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD]; return; } @@ -1260,10 +1227,10 @@ void ReCreateGameTileSizeBitmap(Bitmap **bitmaps) int width = bitmap->width * gfx.game_tile_size / gfx.standard_tile_size;; int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;; - Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height); + bitmaps[IMG_BITMAP_CUSTOM] = ZoomBitmap(bitmap, width, height); - bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new; - bitmaps[IMG_BITMAP_GAME] = bitmap_new; + // set game bitmap pointer to custom sized bitmap (newly created) + bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM]; } static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor, @@ -1413,9 +1380,33 @@ static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor, bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0; if (bitmaps[IMG_BITMAP_CUSTOM]) - bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM]; + bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM]; else - bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD]; + bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD]; + + // store the "final" (up-scaled) original bitmap, if not already stored + + int tmp_bitmap_final_nr = -1; + + for (i = 0; i < NUM_IMG_BITMAPS; i++) + if (bitmaps[i] == tmp_bitmap_final) + tmp_bitmap_final_nr = i; + + if (tmp_bitmap_final_nr == -1) // scaled original bitmap not stored + { + // store pointer of scaled original bitmap (not used for any other size) + bitmaps[IMG_BITMAP_OTHER] = tmp_bitmap_final; + + // set original bitmap pointer to scaled original bitmap of other size + bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER]; + } + else + { + // set original bitmap pointer to corresponding sized bitmap + bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[tmp_bitmap_final_nr]; + } + + // free the "old" (unscaled) original bitmap, if not already stored boolean free_old_bitmap = TRUE; @@ -1435,6 +1426,12 @@ static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor, else { bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1; + + // set original bitmap pointer to corresponding sized bitmap + bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_32x32]; + + if (old_bitmap != tmp_bitmap_1) + FreeBitmap(old_bitmap); } UPDATE_BUSY_STATE(); @@ -1450,12 +1447,18 @@ void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor, void CreateBitmapTextures(Bitmap **bitmaps) { - SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]); + if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL) + SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]); + else + SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]); } void FreeBitmapTextures(Bitmap **bitmaps) { - SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]); + if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL) + SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]); + else + SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]); } void ScaleBitmap(Bitmap **bitmaps, int zoom_factor) @@ -1717,7 +1720,7 @@ void CheckQuitEvent(void) program.exit_function(0); } -Key GetEventKey(KeyEvent *event, boolean with_modifiers) +Key GetEventKey(KeyEvent *event) { // key up/down events in SDL2 do not return text characters anymore return event->keysym.sym; @@ -1796,7 +1799,7 @@ void StartTextInput(int x, int y, int width, int height) if (y + height > SCREEN_KEYBOARD_POS(video.height)) { video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height); - video.shifted_up_delay = SDL_GetTicks(); + video.shifted_up_delay.count = SDL_GetTicks(); video.shifted_up = TRUE; } #endif @@ -1812,7 +1815,7 @@ void StopTextInput(void) if (video.shifted_up) { video.shifted_up_pos = 0; - video.shifted_up_delay = SDL_GetTicks(); + video.shifted_up_delay.count = SDL_GetTicks(); video.shifted_up = FALSE; } #endif @@ -1832,6 +1835,24 @@ void PushUserEvent(int code, int value1, int value2) SDL_PushEvent((SDL_Event *)&event); } +boolean PendingEscapeKeyEvent(void) +{ + if (PendingEvent()) + { + Event event; + + // check if any key press event is pending + if (SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN) != 1) + return FALSE; + + // check if pressed key is "Escape" key + if (event.key.keysym.sym == KSYM_Escape) + return TRUE; + } + + return FALSE; +} + // ============================================================================ // joystick functions @@ -1867,3 +1888,45 @@ void ClearJoystickState(void) { SDLClearJoystickState(); } + + +// ============================================================================ +// Emscripten functions +// ============================================================================ + +void InitEmscriptenFilesystem(void) +{ +#if defined(PLATFORM_EMSCRIPTEN) + EM_ASM + ({ + dir = UTF8ToString($0); + + Module.sync_done = 0; + + FS.mkdir(dir); // create persistent data directory + FS.mount(IDBFS, {}, dir); // mount with IDBFS filesystem type + FS.syncfs(true, function(err) // sync persistent data into memory + { + assert(!err); + Module.sync_done = 1; + }); + }, PERSISTENT_DIRECTORY); + + // wait for persistent data to be synchronized to memory + while (emscripten_run_script_int("Module.sync_done") == 0) + Delay(20); +#endif +} + +void SyncEmscriptenFilesystem(void) +{ +#if defined(PLATFORM_EMSCRIPTEN) + EM_ASM + ( + FS.syncfs(function(err) + { + assert(!err); + }); + ); +#endif +} diff --git a/src/libgame/system.h b/src/libgame/system.h index 2db4961c..73d50ddc 100644 --- a/src/libgame/system.h +++ b/src/libgame/system.h @@ -16,9 +16,9 @@ #include "types.h" -#if defined(PLATFORM_MACOSX) +#if defined(PLATFORM_MAC) #include "macosx.h" -#elif defined(PLATFORM_WIN32) +#elif defined(PLATFORM_WINDOWS) #include "windows.h" #elif defined(PLATFORM_ANDROID) #include "android.h" @@ -105,6 +105,24 @@ #define STR_NETWORK_AUTO_DETECT "auto_detect_network_server" #define STR_NETWORK_AUTO_DETECT_SETUP "(auto detect network server)" +// values for API server settings +#define API_SERVER_HOSTNAME "api.artsoft.org" +#define API_SERVER_PORT 80 +#define API_SERVER_METHOD "POST" +#define API_SERVER_URI_ADD "/api/scores/add" +#define API_SERVER_URI_GET "/api/scores/get" +#define API_SERVER_URI_GETTAPE "/api/scores/gettape" +#define API_SERVER_URI_RENAME "/api/players/rename" +#define API_SERVER_URI_RESETUUID "/api/players/resetuuid" + +#if defined(TESTING) +#undef API_SERVER_HOSTNAME +#define API_SERVER_HOSTNAME "api-test.artsoft.org" +#define TEST_PREFIX "test." +#else +#define TEST_PREFIX "" +#endif + // values for touch control #define TOUCH_CONTROL_OFF "off" #define TOUCH_CONTROL_VIRTUAL_BUTTONS "virtual_buttons" @@ -130,7 +148,7 @@ #define USE_TOUCH_INPUT_OVERLAY #define USE_COMPLETE_DISPLAY #define HAS_SCREEN_KEYBOARD -#define SCREEN_KEYBOARD_POS(h) ((h) / 2) +#define SCREEN_KEYBOARD_POS(h) ((h) * 40 / 100) #endif // values for drag-and-drop support (some parts not added before SDL 2.0.5) @@ -145,7 +163,7 @@ #define DEFAULT_KEY_RIGHT KSYM_Right #define DEFAULT_KEY_UP KSYM_Up #define DEFAULT_KEY_DOWN KSYM_Down -#if defined(PLATFORM_MACOSX) +#if defined(PLATFORM_MAC) #define DEFAULT_KEY_SNAP KSYM_Control_L #define DEFAULT_KEY_DROP KSYM_KP_Enter #else @@ -158,6 +176,8 @@ // default shortcut keys #define DEFAULT_KEY_SAVE_GAME KSYM_F1 #define DEFAULT_KEY_LOAD_GAME KSYM_F2 +#define DEFAULT_KEY_RESTART_GAME KSYM_F3 +#define DEFAULT_KEY_PAUSE_BEFORE_END KSYM_F4 #define DEFAULT_KEY_TOGGLE_PAUSE KSYM_space #define DEFAULT_KEY_FOCUS_PLAYER_1 KSYM_F5 #define DEFAULT_KEY_FOCUS_PLAYER_2 KSYM_F6 @@ -220,6 +240,7 @@ #define MB_MENU_MARK TRUE #define MB_MENU_INITIALIZE (-1) #define MB_MENU_LEAVE (-2) +#define MB_MENU_CONTINUE (-3) #define MB_LEFTBUTTON 1 #define MB_MIDDLEBUTTON 2 #define MB_RIGHTBUTTON 3 @@ -267,6 +288,8 @@ // values for drawing stages for global animations #define DRAW_GLOBAL_ANIM_STAGE_1 1 #define DRAW_GLOBAL_ANIM_STAGE_2 2 +#define DRAW_GLOBAL_ANIM_STAGE_3 3 +#define DRAW_GLOBAL_ANIM_STAGE_RESTART 4 // values for drawing target (various functions) #define DRAW_TO_BACKBUFFER 0 @@ -343,18 +366,64 @@ #define ANIM_CE_DELAY (1 << 7) #define ANIM_REVERSE (1 << 8) #define ANIM_OPAQUE_PLAYER (1 << 9) +#define ANIM_LEVEL_NR (1 << 10) // values for special (non game element) animation modes // (not stored in level files -- can be changed, if needed) -#define ANIM_HORIZONTAL (1 << 10) -#define ANIM_VERTICAL (1 << 11) -#define ANIM_CENTERED (1 << 12) -#define ANIM_STATIC_PANEL (1 << 13) -#define ANIM_ALL (1 << 14) -#define ANIM_ONCE (1 << 15) +#define ANIM_HORIZONTAL (1 << 11) +#define ANIM_VERTICAL (1 << 12) +#define ANIM_CENTERED (1 << 13) +#define ANIM_STATIC_PANEL (1 << 14) +#define ANIM_ALL (1 << 15) +#define ANIM_ONCE (1 << 16) +#define ANIM_TILED (1 << 17) +#define ANIM_RANDOM_STATIC (1 << 18) #define ANIM_DEFAULT ANIM_LOOP +// values for special global animation events +#define ANIM_EVENT_UNDEFINED -1 +#define ANIM_EVENT_NONE 0 +#define ANIM_EVENT_SELF (1 << 0) +#define ANIM_EVENT_ANY (1 << 1) +#define ANIM_EVENT_CLICK (1 << 2) +#define ANIM_EVENT_INIT (1 << 3) +#define ANIM_EVENT_START (1 << 4) +#define ANIM_EVENT_END (1 << 5) +#define ANIM_EVENT_POST (1 << 6) +#define ANIM_EVENT_UNCLICK_ANY (1 << 7) +#define ANIM_EVENT_CE_CHANGE (1 << 8) + +// event mask: bits 0-15 +// CE number: bits 16-23 +// anim number: bits 16-23 +// page number: bits 24-31 +// part number: bits 24-31 +#define ANIM_EVENT_CE_BIT 16 +#define ANIM_EVENT_ANIM_BIT 16 +#define ANIM_EVENT_PAGE_BIT 24 +#define ANIM_EVENT_PART_BIT 24 + +#define ANIM_EVENT_CE_MASK (0xff << ANIM_EVENT_CE_BIT) +#define ANIM_EVENT_ANIM_MASK (0xff << ANIM_EVENT_ANIM_BIT) +#define ANIM_EVENT_PAGE_MASK (0xff << ANIM_EVENT_PAGE_BIT) +#define ANIM_EVENT_PART_MASK (0xff << ANIM_EVENT_PART_BIT) + +#define ANIM_EVENT_DEFAULT ANIM_EVENT_NONE + +// values for special global animation event actions +#define ANIM_EVENT_ACTION_NONE -1 + +// values for special global animation delay types +#define ANIM_DELAY_UNDEFINED -1 +#define ANIM_DELAY_NONE 0 +#define ANIM_DELAY_INIT 1 +#define ANIM_DELAY_ANIM 2 +#define ANIM_DELAY_POST 3 + +// values for special global animation delay actions +#define ANIM_DELAY_ACTION_NONE -1 + // values for special drawing styles and event handling #define STYLE_NONE 0 @@ -370,44 +439,10 @@ #define STYLE_BLOCK (1 << 4) #define STYLE_PASSTHROUGH (1 << 5) #define STYLE_MULTIPLE_ACTIONS (1 << 6) +#define STYLE_CONSUME_CE_EVENT (1 << 7) #define STYLE_DEFAULT STYLE_NONE -// values for special global animation delay types -#define ANIM_DELAY_UNDEFINED -1 -#define ANIM_DELAY_NONE 0 -#define ANIM_DELAY_INIT 1 -#define ANIM_DELAY_ANIM 2 -#define ANIM_DELAY_POST 3 - -// values for special global animation delay actions -#define ANIM_DELAY_ACTION_NONE -1 - -// values for special global animation events -#define ANIM_EVENT_UNDEFINED -1 -#define ANIM_EVENT_NONE 0 -#define ANIM_EVENT_SELF (1 << 16) -#define ANIM_EVENT_ANY (1 << 17) -#define ANIM_EVENT_CLICK (1 << 18) -#define ANIM_EVENT_INIT (1 << 19) -#define ANIM_EVENT_START (1 << 20) -#define ANIM_EVENT_END (1 << 21) -#define ANIM_EVENT_POST (1 << 22) -#define ANIM_EVENT_UNCLICK_ANY (1 << 23) - -// anim number: bits 0-7 -// part number: bits 8-15 -#define ANIM_EVENT_ANIM_BIT 0 -#define ANIM_EVENT_PART_BIT 8 - -#define ANIM_EVENT_ANIM_MASK (0xff << ANIM_EVENT_ANIM_BIT) -#define ANIM_EVENT_PART_MASK (0xff << ANIM_EVENT_PART_BIT) - -#define ANIM_EVENT_DEFAULT ANIM_EVENT_NONE - -// values for special global animation event actions -#define ANIM_EVENT_ACTION_NONE -1 - // values for fade mode #define FADE_TYPE_NONE 0 #define FADE_TYPE_FADE_IN (1 << 0) @@ -446,7 +481,9 @@ #define POS_LOWER 5 #define POS_BOTTOM 6 #define POS_ANY 7 -#define POS_LAST 8 +#define POS_CE 8 +#define POS_CE_TRIGGER 9 +#define POS_LAST 10 // values for text alignment #define ALIGN_LEFT (1 << 0) @@ -459,9 +496,9 @@ #define VALIGN_MIDDLE (1 << 2) #define VALIGN_DEFAULT VALIGN_TOP -#define ALIGNED_XPOS(x,w,a) ((a) == ALIGN_CENTER ? (x) - (w) / 2 : \ +#define ALIGNED_XPOS(x, w, a) ((a) == ALIGN_CENTER ? (x) - (w) / 2 : \ (a) == ALIGN_RIGHT ? (x) - (w) : (x)) -#define ALIGNED_YPOS(y,h,v) ((v) == VALIGN_MIDDLE ? (y) - (h) / 2 : \ +#define ALIGNED_YPOS(y, h, v) ((v) == VALIGN_MIDDLE ? (y) - (h) / 2 : \ (v) == VALIGN_BOTTOM ? (y) - (h) : (y)) #define ALIGNED_TEXT_XPOS(p) ALIGNED_XPOS((p)->x, (p)->width, (p)->align) #define ALIGNED_TEXT_YPOS(p) ALIGNED_YPOS((p)->y, (p)->height, (p)->valign) @@ -584,7 +621,7 @@ JOY_NO_ACTION) // maximum number of level sets in the level set history -#define MAX_LEVELDIR_HISTORY 12 +#define MAX_LEVELDIR_HISTORY 100 // default name for empty highscore entry #define EMPTY_PLAYER_NAME "no name" @@ -607,28 +644,27 @@ // default value for undefined levelset #define UNDEFINED_LEVELSET "[NONE]" -// default value for undefined parameter +// default value for undefined password +#define UNDEFINED_PASSWORD "[undefined]" + +// default value for undefined string parameter +#define ARG_UNDEFINED_STRING "[undefined]" + +// default value for default string parameter #define ARG_DEFAULT "[DEFAULT]" -// default values for undefined configuration file parameters +// default values for undefined numerical parameter (as string and integer) #define ARG_UNDEFINED "-1000000" #define ARG_UNDEFINED_VALUE (-1000000) // default value for off-screen positions #define POS_OFFSCREEN (-1000000) -// definitions for game sub-directories -#ifndef RO_GAME_DIR -#define RO_GAME_DIR "." -#endif - -#ifndef RW_GAME_DIR -#define RW_GAME_DIR "." +// definitions for game base path and sub-directories +#ifndef BASE_PATH +#define BASE_PATH "." #endif -#define RO_BASE_PATH RO_GAME_DIR -#define RW_BASE_PATH RW_GAME_DIR - // directory names #define GRAPHICS_DIRECTORY "graphics" #define SOUNDS_DIRECTORY "sounds" @@ -637,10 +673,15 @@ #define TAPES_DIRECTORY "tapes" #define SCORES_DIRECTORY "scores" #define DOCS_DIRECTORY "docs" +#define ELEMENTS_DIRECTORY "elements" +#define CREDITS_DIRECTORY "credits" +#define PROGRAM_INFO_DIRECTORY "program" +#define LEVELSET_INFO_DIRECTORY "levelset" #define CACHE_DIRECTORY "cache" #define CONF_DIRECTORY "conf" #define NETWORK_DIRECTORY "network" #define USERS_DIRECTORY "users" +#define PERSISTENT_DIRECTORY "/persistent" #define GFX_CLASSIC_SUBDIR "gfx_classic" #define SND_CLASSIC_SUBDIR "snd_classic" @@ -662,6 +703,7 @@ #define USERSETUP_FILENAME "usersetup.conf" #define AUTOSETUP_FILENAME "autosetup.conf" #define LEVELSETUP_FILENAME "levelsetup.conf" +#define SERVERSETUP_FILENAME "serversetup.conf" #define EDITORSETUP_FILENAME "editorsetup.conf" #define EDITORCASCADE_FILENAME "editorcascade.conf" #define HELPANIM_FILENAME "helpanim.conf" @@ -672,18 +714,14 @@ #define MUSICINFO_FILENAME "musicinfo.conf" #define ARTWORKINFO_CACHE_FILE "artworkinfo.cache" #define LEVELTEMPLATE_FILENAME "template.level" +#define UPLOADED_FILENAME ".uploaded" #define LEVELFILE_EXTENSION "level" #define TAPEFILE_EXTENSION "tape" #define SCOREFILE_EXTENSION "score" #define GAMECONTROLLER_BASENAME "gamecontrollerdb.txt" -#define LOG_OUT_BASENAME "stdout.txt" -#define LOG_ERR_BASENAME "stderr.txt" - -#define LOG_OUT_ID 0 -#define LOG_ERR_ID 1 -#define NUM_LOGS 2 +#define FALLBACK_TEXT_FILENAME "fallback.txt" #define STRING_PARENT_DIRECTORY ".." #define STRING_TOP_DIRECTORY "/" @@ -697,7 +735,7 @@ #define STRING_NEWLINE_UNIX "\n" #define STRING_NEWLINE_DOS "\r\n" -#if defined(PLATFORM_WIN32) +#if defined(PLATFORM_WINDOWS) #define CHAR_PATH_SEPARATOR CHAR_PATH_SEPARATOR_DOS #define STRING_PATH_SEPARATOR STRING_PATH_SEPARATOR_DOS #define STRING_NEWLINE STRING_NEWLINE_DOS @@ -772,9 +810,10 @@ #define TREE_TYPE_LEVEL_DIR 3 #define TREE_TYPE_LEVEL_NR 4 #define TREE_TYPE_PLAYER_NAME 5 +#define TREE_TYPE_SCORE_ENTRY 6 #define NUM_BASE_TREE_TYPES 4 -#define NUM_TREE_TYPES 6 +#define NUM_TREE_TYPES 7 #define TREE_TYPE_IS_DIR(type) ((type) == TREE_TYPE_GRAPHICS_DIR || \ (type) == TREE_TYPE_SOUNDS_DIR || \ @@ -788,12 +827,17 @@ #define INFOTEXT_LEVEL_DIR "Level Sets" #define INFOTEXT_LEVEL_NR "Levels" #define INFOTEXT_PLAYER_NAME "Players & Teams" +#define INFOTEXT_SCORE_ENTRY "Hall of Fame" #define BACKLINK_TEXT_MAIN ".. (main menu)" #define BACKLINK_TEXT_SETUP ".. (setup menu)" #define BACKLINK_TEXT_PARENT ".. (parent directory)" +#define BACKLINK_TEXT_BACK "back" +#define BACKLINK_TEXT_NEXT "next" -#define TREE_INFOTEXT(t) ((t) == TREE_TYPE_PLAYER_NAME ? \ +#define TREE_INFOTEXT(t) ((t) == TREE_TYPE_SCORE_ENTRY ? \ + INFOTEXT_SCORE_ENTRY : \ + (t) == TREE_TYPE_PLAYER_NAME ? \ INFOTEXT_PLAYER_NAME : \ (t) == TREE_TYPE_LEVEL_NR ? \ INFOTEXT_LEVEL_NR : \ @@ -807,7 +851,9 @@ INFOTEXT_MUSIC_DIR : \ INFOTEXT_UNDEFINED) -#define TREE_BACKLINK_TEXT(t) ((t) == TREE_TYPE_LEVEL_DIR ? \ +#define TREE_BACKLINK_TEXT(t) ((t) == TREE_TYPE_SCORE_ENTRY ? \ + BACKLINK_TEXT_BACK : \ + (t) == TREE_TYPE_LEVEL_DIR ? \ BACKLINK_TEXT_MAIN : \ BACKLINK_TEXT_SETUP) @@ -968,7 +1014,12 @@ #define UPDATE_BUSY_STATE() \ { \ if (gfx.draw_busy_anim_function != NULL) \ - gfx.draw_busy_anim_function(); \ + gfx.draw_busy_anim_function(TRUE); \ +} +#define UPDATE_BUSY_STATE_NOT_LOADING() \ +{ \ + if (gfx.draw_busy_anim_function != NULL) \ + gfx.draw_busy_anim_function(FALSE); \ } @@ -986,17 +1037,17 @@ struct ProgramInfo char *userdata_subdir; // personal user game data directory char *userdata_path; // resulting full path to game data directory + char *program_basename; char *program_title; char *window_title; - char *icon_title; char *icon_filename; char *cookie_prefix; - char *log_filename[NUM_LOGS]; // log filenames for out/err messages - FILE *log_file[NUM_LOGS]; // log file handles for out/err files - FILE *log_file_default[NUM_LOGS]; // default log file handles (out/err) + char *log_filename; // filename for log messages + FILE *log_file; // file handle for log files + FILE *log_file_default; // default log file handle int version_super; int version_major; @@ -1010,8 +1061,7 @@ struct ProgramInfo void (*exit_message_function)(char *, va_list); void (*exit_function)(int); - boolean global_scores; - boolean many_scores_per_name; + int api_thread_count; boolean headless; }; @@ -1032,6 +1082,8 @@ struct NetworkInfo struct RuntimeInfo { boolean uses_touch_device; + + boolean use_api_server; }; struct OptionInfo @@ -1039,8 +1091,7 @@ struct OptionInfo char *server_host; int server_port; - char *ro_base_directory; - char *rw_base_directory; + char *base_directory; char *level_directory; char *graphics_directory; char *sounds_directory; @@ -1049,10 +1100,17 @@ struct OptionInfo char *conf_directory; char *execute_command; + char *tape_log_filename; char *special_flags; char *debug_mode; + char *player_name; + char *identifier; + char *level_nr; + + int display_nr; + boolean mytapes; boolean serveronly; boolean network; @@ -1080,14 +1138,12 @@ struct VideoSystemInfo int vsync_mode; unsigned int frame_counter; - unsigned int frame_delay; - unsigned int frame_delay_value; + DelayCounter frame_delay; boolean shifted_up; int shifted_up_pos; int shifted_up_pos_last; - unsigned int shifted_up_delay; - unsigned int shifted_up_delay_value; + DelayCounter shifted_up_delay; boolean initialized; }; @@ -1182,13 +1238,16 @@ struct GfxInfo struct FontBitmapInfo *font_bitmap_info; int (*select_font_function)(int); int (*get_font_from_token_function)(char *); + char * (*get_token_from_font_function)(int); int anim_random_frame; + int anim_first_level; - void (*draw_busy_anim_function)(void); + void (*draw_busy_anim_function)(boolean); void (*draw_global_anim_function)(int, int); void (*draw_global_border_function)(int); - void (*draw_tile_cursor_function)(int); + void (*draw_tile_cursor_function)(int, int); + void (*draw_envelope_request_function)(int); int cursor_mode; int cursor_mode_override; @@ -1264,6 +1323,8 @@ struct SetupTouchInfo boolean draw_pressed; boolean grid_initialized; + + boolean overlay_buttons; }; struct SetupInputInfo @@ -1333,6 +1394,7 @@ struct SetupEditorCascadeInfo boolean el_steel_chars; boolean el_ce; boolean el_ge; + boolean el_es; boolean el_ref; boolean el_user; boolean el_dynamic; @@ -1342,6 +1404,8 @@ struct SetupShortcutInfo { Key save_game; Key load_game; + Key restart_game; + Key pause_before_end; Key toggle_pause; Key focus_player[MAX_PLAYERS]; @@ -1400,6 +1464,7 @@ struct SetupInternalInfo boolean choose_from_top_leveldir; boolean show_scaling_in_title; boolean create_user_levelset; + boolean info_screens_from_main; boolean menu_game; boolean menu_engines; @@ -1412,6 +1477,21 @@ struct SetupInternalInfo boolean menu_shortcuts; boolean menu_exit; boolean menu_save_and_exit; + + boolean menu_shortcuts_various; + boolean menu_shortcuts_focus; + boolean menu_shortcuts_tape; + boolean menu_shortcuts_sound; + boolean menu_shortcuts_snap; + + boolean info_title; + boolean info_elements; + boolean info_music; + boolean info_credits; + boolean info_program; + boolean info_version; + boolean info_levelset; + boolean info_exit; }; struct SetupDebugInfo @@ -1428,6 +1508,8 @@ struct SetupDebugInfo struct SetupInfo { char *player_name; + char *player_uuid; + int player_version; boolean multiple_users; @@ -1436,6 +1518,7 @@ struct SetupInfo boolean sound_music; boolean sound_simple; boolean toons; + boolean global_animations; boolean scroll_delay; boolean forced_scroll_delay; int scroll_delay_value; @@ -1443,6 +1526,8 @@ struct SetupInfo int engine_snapshot_memory; boolean fade_screens; boolean autorecord; + boolean autorecord_after_replay; + boolean auto_pause_on_start; boolean show_titlescreen; boolean quick_doors; boolean team_mode; @@ -1472,7 +1557,9 @@ struct SetupInfo int game_frame_delay; boolean sp_show_border_elements; boolean small_game_graphics; - boolean show_snapshot_buttons; + boolean show_load_save_buttons; + boolean show_undo_redo_buttons; + char *scores_in_highscore_list; char *graphics_set; char *sounds_set; @@ -1489,6 +1576,15 @@ struct SetupInfo int network_player_nr; char *network_server_hostname; + boolean use_api_server; + char *api_server_hostname; + char *api_server_password; + boolean ask_for_uploading_tapes; + boolean ask_for_remaining_tapes; + boolean provide_uploading_tapes; + boolean ask_for_using_api_server; + boolean has_remaining_tapes; + struct SetupAutoSetupInfo auto_setup; struct SetupLevelSetupInfo level_setup; @@ -1556,10 +1652,14 @@ struct TreeInfo char *special_flags; // flags for special actions performed on level file + char *empty_level_name; // name pattern if level title is "nameless level" + boolean force_level_name; // force also renaming non-nameless level titles + int levels; // number of levels in level series int first_level; // first level number (to allow start with 0 or 1) int last_level; // last level number (automatically calculated) int sort_priority; // sort levels by 'sort_priority' and then by name + int pos; // custom position information of node in tree boolean latest_engine;// force level set to use the latest game engine @@ -1570,9 +1670,11 @@ struct TreeInfo boolean user_defined; // levels in user directory and marked as "private" boolean readonly; // readonly levels can not be changed with editor boolean handicap; // level set has no handicap when set to "false" + boolean time_limit; // level set has no time limit when set to "false" boolean skip_levels; // levels can be skipped when set to "true" boolean use_emc_tiles;// use (swapped) V5/V6 EMC tiles when set to "true" + boolean info_screens_from_main; // can invoke info screens from main menu int color; // color to use on selection screen for this level char *class_desc; // description of level series class @@ -1826,7 +1928,6 @@ extern struct AudioSystemInfo audio; extern struct GfxInfo gfx; extern struct TileCursorInfo tile_cursor; extern struct OverlayInfo overlay; -extern struct AnimInfo anim; extern struct ArtworkInfo artwork; extern struct JoystickInfo joystick; extern struct SetupInfo setup; @@ -1862,7 +1963,6 @@ void InitProgramInfo(char *, char *, char *, char *, char *, char *, char *, void InitNetworkInfo(boolean, boolean, boolean, char *, int); void InitRuntimeInfo(void); -void InitScoresInfo(void); void SetWindowTitle(void); void InitWindowTitleFunction(char *(*window_title_function)(void)); @@ -1879,10 +1979,11 @@ void InitGfxDoor3Info(int, int, int, int); void InitGfxWindowInfo(int, int); void InitGfxScrollbufferInfo(int, int); void InitGfxClipRegion(boolean, int, int, int, int); -void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(void)); +void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(boolean)); void InitGfxDrawGlobalAnimFunction(void (*draw_global_anim_function)(int, int)); void InitGfxDrawGlobalBorderFunction(void (*draw_global_border_function)(int)); -void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int)); +void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int, int)); +void InitGfxDrawEnvelopeRequestFunction(void (*draw_envelope_request_function)(int)); void InitGfxCustomArtworkInfo(void); void InitGfxOtherSettings(void); void InitTileCursorInfo(void); @@ -1901,9 +2002,7 @@ boolean GetOverlayActive(void); void SetDrawDeactivationMask(int); int GetDrawDeactivationMask(void); void SetDrawBackgroundMask(int); -void SetWindowBackgroundBitmap(Bitmap *); -void SetMainBackgroundBitmap(Bitmap *); -void SetDoorBackgroundBitmap(Bitmap *); +void SetBackgroundBitmap(Bitmap *, int, int, int, int, int); void SetRedrawMaskFromArea(int, int, int, int); void LimitScreenUpdates(boolean); @@ -1912,12 +2011,14 @@ void InitVideoDefaults(void); void InitVideoDisplay(void); void CloseVideoDisplay(void); void InitVideoBuffer(int, int, int, boolean); +void ResetBitmapAlpha(Bitmap *); Bitmap *CreateBitmapStruct(void); Bitmap *CreateBitmap(int, int, int); void ReCreateBitmap(Bitmap **, int, int); void FreeBitmap(Bitmap *); +void SetBitmapAlphaNextBlit(Bitmap *, int); void BlitBitmap(Bitmap *, Bitmap *, int, int, int, int, int, int); -void BlitBitmapTiled(Bitmap *, Bitmap *, int, int, int, int, int, int, int,int); +void BlitBitmapTiled(Bitmap *, Bitmap *, int, int, int, int, int, int, int, int); void FadeRectangle(int, int, int, int, int, int, int, void (*draw_border_function)(void)); void FillRectangle(Bitmap *, int, int, int, int, Pixel); @@ -1925,7 +2026,7 @@ void ClearRectangle(Bitmap *, int, int, int, int); void ClearRectangleOnBackground(Bitmap *, int, int, int, int); void BlitBitmapMasked(Bitmap *, Bitmap *, int, int, int, int, int, int); boolean DrawingDeactivatedField(void); -boolean DrawingDeactivated(int, int, int, int); +boolean DrawingDeactivated(int, int); boolean DrawingOnBackground(int, int); boolean DrawingAreaChanged(void); void BlitBitmapOnBackground(Bitmap *, Bitmap *, int, int, int, int, int, int); @@ -1933,12 +2034,10 @@ void BlitTexture(Bitmap *, int, int, int, int, int, int); void BlitTextureMasked(Bitmap *, int, int, int, int, int, int); void BlitToScreen(Bitmap *, int, int, int, int, int, int); void BlitToScreenMasked(Bitmap *, int, int, int, int, int, int); -void DrawSimpleBlackLine(Bitmap *, int, int, int, int); void DrawSimpleWhiteLine(Bitmap *, int, int, int, int); void DrawLines(Bitmap *, struct XY *, int, Pixel); Pixel GetPixel(Bitmap *, int, int); -Pixel GetPixelFromRGB(Bitmap *, unsigned int,unsigned int,unsigned int); -Pixel GetPixelFromRGBcompact(Bitmap *, unsigned int); +Pixel GetPixelFromRGB(Bitmap *, unsigned int, unsigned int, unsigned int); void KeyboardAutoRepeatOn(void); void KeyboardAutoRepeatOff(void); @@ -1951,6 +2050,7 @@ Bitmap *LoadImage(char *); Bitmap *LoadCustomImage(char *); void ReloadCustomImage(Bitmap *, char *); +Bitmap *ZoomBitmap(Bitmap *, int, int); void ReCreateGameTileSizeBitmap(Bitmap **); void CreateBitmapWithSmallBitmaps(Bitmap **, int, int); void CreateBitmapTextures(Bitmap **); @@ -1971,17 +2071,21 @@ void WaitEvent(Event *event); void PeekEvent(Event *event); void PumpEvents(void); void CheckQuitEvent(void); -Key GetEventKey(KeyEvent *, boolean); +Key GetEventKey(KeyEvent *); KeyMod HandleKeyModState(Key, int); KeyMod GetKeyModState(void); KeyMod GetKeyModStateFromEvents(void); void StartTextInput(int, int, int, int); void StopTextInput(void); void PushUserEvent(int, int, int); +boolean PendingEscapeKeyEvent(void); void InitJoysticks(void); boolean ReadJoystick(int, int *, int *, boolean *, boolean *); boolean CheckJoystickOpened(int); void ClearJoystickState(void); +void InitEmscriptenFilesystem(void); +void SyncEmscriptenFilesystem(void); + #endif // SYSTEM_H diff --git a/src/libgame/text.c b/src/libgame/text.c index c10583d3..9b9ad87f 100644 --- a/src/libgame/text.c +++ b/src/libgame/text.c @@ -16,18 +16,37 @@ #include "misc.h" +// ============================================================================ +// static font variables +// ============================================================================ + +boolean text_drawing_enabled = TRUE; + + // ============================================================================ // font functions // ============================================================================ +void EnableDrawingText(void) +{ + text_drawing_enabled = TRUE; +} + +void DisableDrawingText(void) +{ + text_drawing_enabled = FALSE; +} + void InitFontInfo(struct FontBitmapInfo *font_bitmap_info, int num_fonts, int (*select_font_function)(int), - int (*get_font_from_token_function)(char *)) + int (*get_font_from_token_function)(char *), + char * (*get_token_from_font_function)(int)) { gfx.num_fonts = num_fonts; gfx.font_bitmap_info = font_bitmap_info; gfx.select_font_function = select_font_function; gfx.get_font_from_token_function = get_font_from_token_function; + gfx.get_token_from_font_function = get_token_from_font_function; } void FreeFontInfo(struct FontBitmapInfo *font_bitmap_info) @@ -136,12 +155,15 @@ int maxWordLengthInRequestString(char *text) // simple text drawing functions // ============================================================================ -void DrawInitText(char *text, int ypos, int font_nr) +static void DrawInitTextExt(char *text, int ypos, int font_nr, boolean update) { LimitScreenUpdates(TRUE); UPDATE_BUSY_STATE(); + if (!text_drawing_enabled) + return; + if (window != NULL && gfx.draw_init_text && gfx.num_fonts > 0 && @@ -152,13 +174,29 @@ void DrawInitText(char *text, int ypos, int font_nr) int width = video.width; int height = getFontHeight(font_nr); - ClearRectangle(drawto, 0, y, width, height); - DrawTextExt(drawto, x, y, text, font_nr, BLIT_OPAQUE); + ClearRectangleOnBackground(drawto, 0, y, width, height); + DrawTextExt(drawto, x, y, text, font_nr, BLIT_MASKED); - BlitBitmap(drawto, window, 0, 0, video.width, video.height, 0, 0); + if (update) + BlitBitmap(drawto, window, 0, 0, video.width, video.height, 0, 0); } } +void DrawInitText(char *text, int ypos, int font_nr) +{ + DrawInitTextExt(text, ypos, font_nr, FALSE); +} + +void DrawInitTextHead(char *text) +{ + DrawInitTextExt(text, 120, FC_GREEN, FALSE); +} + +void DrawInitTextItem(char *text) +{ + DrawInitTextExt(text, 150, FC_YELLOW, TRUE); +} + void DrawTextF(int x, int y, int font_nr, char *format, ...) { char buffer[MAX_OUTPUT_LINESIZE + 1]; @@ -207,12 +245,6 @@ void DrawTextSAligned(int x, int y, char *text, int font_nr, int align) gfx.sy + y, text, font_nr); } -void DrawTextAligned(int x, int y, char *text, int font_nr, int align) -{ - DrawText(ALIGNED_XPOS(x, getTextWidth(text, font_nr), align), - y, text, font_nr); -} - void DrawText(int x, int y, char *text, int font_nr) { int mask_mode = BLIT_OPAQUE; @@ -244,6 +276,14 @@ void DrawTextExt(DrawBuffer *dst_bitmap, int dst_x, int dst_y, char *text, int src_x, src_y; char *text_ptr = text; + if (!text_drawing_enabled) + return; + +#if DEBUG + Debug("font:token", "'%s' / '%s'", + gfx.get_token_from_font_function(font_nr), text); +#endif + if (font->bitmap == NULL) return; @@ -341,11 +381,24 @@ char *GetTextBufferFromFile(char *filename, int max_lines) while (!checkEndOfFile(file) && num_lines < max_lines) { char line[MAX_LINE_LEN]; + char *line_ptr; + int line_len; // read next line of input file if (!getStringFromFile(file, line, MAX_LINE_LEN)) break; + line_len = strlen(line); + + // cut trailing line break (this can be newline and/or carriage return) + for (line_ptr = &line[line_len]; line_ptr >= line; line_ptr--) + if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0') + *line_ptr = '\0'; + + // re-add newline (so the result is terminated by newline, but not CR/LF) + if (strlen(line) != line_len) + strcat(line, "\n"); + buffer = checked_realloc(buffer, strlen(buffer) + strlen(line) + 1); strcat(buffer, line); @@ -355,6 +408,15 @@ char *GetTextBufferFromFile(char *filename, int max_lines) closeFile(file); + if (getTextEncoding(buffer) == TEXT_ENCODING_UTF_8) + { + char *body_latin1 = getLatin1FromUTF8(buffer); + + checked_free(buffer); + + buffer = body_latin1; + } + return buffer; } @@ -372,6 +434,9 @@ static boolean RenderLineToBuffer(char **src_buffer_ptr, char *dst_buffer, char *word_ptr; int word_len; + if (strEqual(text_ptr, " ")) // special case: force line break + buffer_filled = TRUE; + // skip leading whitespaces while (*text_ptr == ' ' || *text_ptr == '\t') text_ptr++; @@ -459,16 +524,18 @@ static boolean getCheckedTokenValueFromString(char *string, char **token, return TRUE; } -static void DrawTextBuffer_Flush(int x, int y, char *buffer, int font_nr, - int line_length, int cut_length, +static void DrawTextBuffer_Flush(int x, int y, char *buffer, int base_font_nr, + int font_nr, int line_length, int cut_length, int mask_mode, boolean centered, int current_ypos) { int buffer_len = strlen(buffer); + int base_font_width = getFontWidth(base_font_nr); int font_width = getFontWidth(font_nr); int offset_chars = (centered ? (line_length - buffer_len) / 2 : 0); - int offset_xsize = - (centered ? font_width * (line_length - buffer_len) / 2 : 0); + int line_width = base_font_width * line_length; + int buffer_width = font_width * buffer_len; + int offset_xsize = (centered ? (line_width - buffer_width) / 2 : 0); int final_cut_length = MAX(0, cut_length - offset_chars); int xx = x + offset_xsize; int yy = y + current_ypos; @@ -481,13 +548,15 @@ static void DrawTextBuffer_Flush(int x, int y, char *buffer, int font_nr, DrawText(xx, yy, buffer, font_nr); } -int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, - int line_length, int cut_length, int max_lines, - int line_spacing, int mask_mode, boolean autowrap, - boolean centered, boolean parse_comments) +static int DrawTextBufferExt(int x, int y, char *text_buffer, int base_font_nr, + int line_length, int cut_length, int max_lines, + int line_spacing, int mask_mode, boolean autowrap, + boolean centered, boolean parse_comments, + boolean is_text_area) { char buffer[line_length + 1]; int buffer_len; + int font_nr = base_font_nr; int font_height = getFontHeight(font_nr); int line_height = font_height + line_spacing; int current_line = 0; @@ -516,8 +585,16 @@ int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, // copy next line from text buffer to line buffer (nearly fgets() style) for (i = 0; i < num_line_chars && *text_buffer; i++) + { if ((line[i] = *text_buffer++) == '\n') + { + // in text areas, 'line_length' sized lines cause additional empty line + if (i == line_length && is_text_area) + text_buffer--; + break; + } + } line[i] = '\0'; // prevent 'num_line_chars' sized lines to cause additional empty line @@ -535,8 +612,8 @@ int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, // if found, flush the current buffer, if non-empty if (buffer_len > 0 && current_ypos < max_ysize) { - DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length, - mask_mode, centered, current_ypos); + DrawTextBuffer_Flush(x, y, buffer, base_font_nr, font_nr, line_length, + cut_length, mask_mode, centered, current_ypos); current_ypos += line_height; current_line++; @@ -606,8 +683,8 @@ int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, if (buffer_filled) { - DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length, - mask_mode, centered, current_ypos); + DrawTextBuffer_Flush(x, y, buffer, base_font_nr, font_nr, line_length, + cut_length, mask_mode, centered, current_ypos); current_ypos += line_height; current_line++; @@ -621,8 +698,8 @@ int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, if (buffer_len > 0 && current_ypos < max_ysize) { - DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length, - mask_mode, centered, current_ypos); + DrawTextBuffer_Flush(x, y, buffer, base_font_nr, font_nr, line_length, + cut_length, mask_mode, centered, current_ypos); current_ypos += line_height; current_line++; } @@ -630,6 +707,39 @@ int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, return current_line; } +int DrawTextArea(int x, int y, char *text_buffer, int font_nr, + int line_length, int cut_length, int max_lines, + int line_spacing, int mask_mode, boolean autowrap, + boolean centered, boolean parse_comments) +{ + return DrawTextBufferExt(x, y, text_buffer, font_nr, + line_length, cut_length, max_lines, + line_spacing, mask_mode, autowrap, + centered, parse_comments, TRUE); +} + +int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr, + int line_length, int cut_length, int max_lines, + int line_spacing, int mask_mode, boolean autowrap, + boolean centered, boolean parse_comments) +{ + return DrawTextBufferExt(x, y, text_buffer, font_nr, + line_length, cut_length, max_lines, + line_spacing, mask_mode, autowrap, + centered, parse_comments, FALSE); +} + +int DrawTextBufferS(int x, int y, char *text_buffer, int font_nr, + int line_length, int cut_length, int max_lines, + int line_spacing, int mask_mode, boolean autowrap, + boolean centered, boolean parse_comments) +{ + return DrawTextBuffer(gfx.sx + x, gfx.sy + y, text_buffer, font_nr, + line_length, cut_length, max_lines, + line_spacing, mask_mode, autowrap, + centered, parse_comments); +} + int DrawTextBufferVA(int x, int y, char *format, va_list ap, int font_nr, int line_length, int cut_length, int max_lines, int line_spacing, int mask_mode, boolean autowrap, diff --git a/src/libgame/text.h b/src/libgame/text.h index 26df7816..57a8d224 100644 --- a/src/libgame/text.h +++ b/src/libgame/text.h @@ -75,8 +75,13 @@ // font structure definitions +void EnableDrawingText(void); +void DisableDrawingText(void); + void InitFontInfo(struct FontBitmapInfo *, int, - int (*function1)(int), int (*function2)(char *)); + int (*function1)(int), + int (*function2)(char *), + char * (*function3)(int)); void FreeFontInfo(struct FontBitmapInfo *); struct FontBitmapInfo *getFontBitmapInfo(int); @@ -92,19 +97,24 @@ void getFontCharSource(int, char, Bitmap **, int *, int *); int maxWordLengthInRequestString(char *); void DrawInitText(char *, int, int); +void DrawInitTextHead(char *); +void DrawInitTextItem(char *); void DrawTextF(int, int, int, char *, ...); void DrawTextFCentered(int, int, char *, ...); void DrawTextS(int, int, int, char *); void DrawTextSCentered(int, int, char *); void DrawTextSAligned(int, int, char *, int, int); -void DrawTextAligned(int, int, char *, int, int); void DrawText(int, int, char *, int); void DrawTextExt(DrawBuffer *, int, int, char *, int, int); char *GetTextBufferFromFile(char *, int); +int DrawTextArea(int, int, char *, int, int, int, int, int, int, + boolean, boolean, boolean); int DrawTextBuffer(int, int, char *, int, int, int, int, int, int, boolean, boolean, boolean); +int DrawTextBufferS(int, int, char *, int, int, int, int, int, int, + boolean, boolean, boolean); int DrawTextBufferVA(int, int, char *, va_list, int, int, int, int, int, int, boolean, boolean, boolean); int DrawTextFile(int, int, char *, int, int, int, int, int, int, diff --git a/src/libgame/types.h b/src/libgame/types.h index 62752f1f..0c935a21 100644 --- a/src/libgame/types.h +++ b/src/libgame/types.h @@ -19,7 +19,7 @@ #include -#if !defined(PLATFORM_WIN32) +#if !defined(PLATFORM_WINDOWS) typedef int boolean; typedef unsigned char byte; #endif @@ -41,11 +41,11 @@ typedef unsigned char byte; #define AUTO -1 #ifndef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif #ifndef ABS @@ -76,4 +76,11 @@ struct ListNode }; typedef struct ListNode ListNode; +struct DelayCounter +{ + unsigned int value; + unsigned int count; +}; +typedef struct DelayCounter DelayCounter; + #endif // TYPES_H diff --git a/src/libgame/zip/ioapi.c b/src/libgame/zip/ioapi.c index 5b6fcc63..0aa6347a 100644 --- a/src/libgame/zip/ioapi.c +++ b/src/libgame/zip/ioapi.c @@ -45,11 +45,11 @@ long call_zseek64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, ui { uint32_t offset_truncated = 0; if (pfilefunc->zfile_func64.zseek64_file != NULL) - return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); + return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque, filestream, offset, origin); offset_truncated = (uint32_t)offset; if (offset_truncated != offset) return -1; - return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream, offset_truncated, origin); + return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque, filestream, offset_truncated, origin); } uint64_t call_ztell64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream) @@ -81,7 +81,6 @@ void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def *p_filef p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; } -static voidpf ZCALLBACK fopen_file_func(ZIP_UNUSED voidpf opaque, const char *filename, int mode); static uint32_t ZCALLBACK fread_file_func(voidpf opaque, voidpf stream, void* buf, uint32_t size); static uint32_t ZCALLBACK fwrite_file_func(voidpf opaque, voidpf stream, const void *buf, uint32_t size); static uint64_t ZCALLBACK ftell64_file_func(voidpf opaque, voidpf stream); @@ -109,25 +108,6 @@ static voidpf file_build_ioposix(FILE *file, const char *filename) return (voidpf)ioposix; } -static voidpf ZCALLBACK fopen_file_func(ZIP_UNUSED voidpf opaque, const char *filename, int mode) -{ - FILE* file = NULL; - const char *mode_fopen = NULL; - if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) - mode_fopen = "rb"; - else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) - mode_fopen = "r+b"; - else if (mode & ZLIB_FILEFUNC_MODE_CREATE) - mode_fopen = "wb"; - - if ((filename != NULL) && (mode_fopen != NULL)) - { - file = fopen(filename, mode_fopen); - return file_build_ioposix(file, filename); - } - return file; -} - static voidpf ZCALLBACK fopen64_file_func(ZIP_UNUSED voidpf opaque, const void *filename, int mode) { FILE* file = NULL; @@ -172,31 +152,6 @@ static voidpf ZCALLBACK fopendisk64_file_func(voidpf opaque, voidpf stream, uint return ret; } -static voidpf ZCALLBACK fopendisk_file_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) -{ - FILE_IOPOSIX *ioposix = NULL; - char *diskFilename = NULL; - voidpf ret = NULL; - int i = 0; - - if (stream == NULL) - return NULL; - ioposix = (FILE_IOPOSIX*)stream; - diskFilename = (char*)malloc(ioposix->filenameLength * sizeof(char)); - strncpy(diskFilename, (const char*)ioposix->filename, ioposix->filenameLength); - for (i = ioposix->filenameLength - 1; i >= 0; i -= 1) - { - if (diskFilename[i] != '.') - continue; - snprintf(&diskFilename[i], ioposix->filenameLength - i, ".z%02u", number_disk + 1); - break; - } - if (i >= 0) - ret = fopen_file_func(opaque, diskFilename, mode); - free(diskFilename); - return ret; -} - static uint32_t ZCALLBACK fread_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, void* buf, uint32_t size) { FILE_IOPOSIX *ioposix = NULL; @@ -219,17 +174,6 @@ static uint32_t ZCALLBACK fwrite_file_func(ZIP_UNUSED voidpf opaque, voidpf stre return written; } -static long ZCALLBACK ftell_file_func(ZIP_UNUSED voidpf opaque, voidpf stream) -{ - FILE_IOPOSIX *ioposix = NULL; - long ret = -1; - if (stream == NULL) - return ret; - ioposix = (FILE_IOPOSIX*)stream; - ret = ftell(ioposix->file); - return ret; -} - static uint64_t ZCALLBACK ftell64_file_func(ZIP_UNUSED voidpf opaque, voidpf stream) { FILE_IOPOSIX *ioposix = NULL; @@ -241,35 +185,6 @@ static uint64_t ZCALLBACK ftell64_file_func(ZIP_UNUSED voidpf opaque, voidpf str return ret; } -static long ZCALLBACK fseek_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, uint32_t offset, int origin) -{ - FILE_IOPOSIX *ioposix = NULL; - int fseek_origin = 0; - long ret = 0; - - if (stream == NULL) - return -1; - ioposix = (FILE_IOPOSIX*)stream; - - switch (origin) - { - case ZLIB_FILEFUNC_SEEK_CUR: - fseek_origin = SEEK_CUR; - break; - case ZLIB_FILEFUNC_SEEK_END: - fseek_origin = SEEK_END; - break; - case ZLIB_FILEFUNC_SEEK_SET: - fseek_origin = SEEK_SET; - break; - default: - return -1; - } - if (fseek(ioposix->file, offset, fseek_origin) != 0) - ret = -1; - return ret; -} - static long ZCALLBACK fseek64_file_func(ZIP_UNUSED voidpf opaque, voidpf stream, uint64_t offset, int origin) { FILE_IOPOSIX *ioposix = NULL; @@ -326,19 +241,6 @@ static int ZCALLBACK ferror_file_func(ZIP_UNUSED voidpf opaque, voidpf stream) return ret; } -void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def) -{ - pzlib_filefunc_def->zopen_file = fopen_file_func; - pzlib_filefunc_def->zopendisk_file = fopendisk_file_func; - pzlib_filefunc_def->zread_file = fread_file_func; - pzlib_filefunc_def->zwrite_file = fwrite_file_func; - pzlib_filefunc_def->ztell_file = ftell_file_func; - pzlib_filefunc_def->zseek_file = fseek_file_func; - pzlib_filefunc_def->zclose_file = fclose_file_func; - pzlib_filefunc_def->zerror_file = ferror_file_func; - pzlib_filefunc_def->opaque = NULL; -} - void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def) { pzlib_filefunc_def->zopen64_file = fopen64_file_func; diff --git a/src/libgame/zip/ioapi.h b/src/libgame/zip/ioapi.h index 4b21ef34..1c70d61a 100644 --- a/src/libgame/zip/ioapi.h +++ b/src/libgame/zip/ioapi.h @@ -115,7 +115,6 @@ typedef struct zlib_filefunc64_def_s voidpf opaque; } zlib_filefunc64_def; -void fill_fopen_filefunc(zlib_filefunc_def *pzlib_filefunc_def); void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def); /* now internal definition, only for zip.c and unzip.h */ @@ -128,24 +127,24 @@ typedef struct zlib_filefunc64_32_def_s seek_file_func zseek32_file; } zlib_filefunc64_32_def; -#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) -#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) -/*#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))*/ -/*#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))*/ -#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) -#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) +#define ZREAD64(filefunc, filestream, buf, size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque, filestream, buf, size)) +#define ZWRITE64(filefunc, filestream, buf, size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque, filestream, buf, size)) +/*#define ZTELL64(filefunc, filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque, filestream))*/ +/*#define ZSEEK64(filefunc, filestream, pos, mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque, filestream, pos, mode))*/ +#define ZCLOSE64(filefunc, filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque, filestream)) +#define ZERROR64(filefunc, filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque, filestream)) -voidpf call_zopen64(const zlib_filefunc64_32_def *pfilefunc,const void*filename, int mode); +voidpf call_zopen64(const zlib_filefunc64_32_def *pfilefunc, const void *filename, int mode); voidpf call_zopendisk64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, uint32_t number_disk, int mode); long call_zseek64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, uint64_t offset, int origin); uint64_t call_ztell64(const zlib_filefunc64_32_def *pfilefunc, voidpf filestream); void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def *p_filefunc64_32, const zlib_filefunc_def *p_filefunc32); -#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) -#define ZOPENDISK64(filefunc,filestream,diskn,mode) (call_zopendisk64((&(filefunc)),(filestream),(diskn),(mode))) -#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) -#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) +#define ZOPEN64(filefunc, filename, mode) (call_zopen64((&(filefunc)), (filename), (mode))) +#define ZOPENDISK64(filefunc, filestream, diskn, mode) (call_zopendisk64((&(filefunc)), (filestream), (diskn), (mode))) +#define ZTELL64(filefunc, filestream) (call_ztell64((&(filefunc)), (filestream))) +#define ZSEEK64(filefunc, filestream, pos, mode) (call_zseek64((&(filefunc)), (filestream), (pos), (mode))) #ifdef __cplusplus } diff --git a/src/libgame/zip/iowin32.c b/src/libgame/zip/iowin32.c index 53a96e65..0681294c 100644 --- a/src/libgame/zip/iowin32.c +++ b/src/libgame/zip/iowin32.c @@ -37,7 +37,6 @@ # endif #endif -voidpf ZCALLBACK win32_open_file_func (voidpf opaque, const char *filename, int mode); uint32_t ZCALLBACK win32_read_file_func (voidpf opaque, voidpf stream, void* buf, uint32_t size); uint32_t ZCALLBACK win32_write_file_func (voidpf opaque, voidpf stream, const void *buf, uint32_t size); uint64_t ZCALLBACK win32_tell64_file_func (voidpf opaque, voidpf stream); @@ -101,38 +100,6 @@ static voidpf win32_build_iowin(HANDLE hFile) return (voidpf)iowin; } -static voidpf ZCALLBACK win32_open64_file_func(voidpf opaque, const void *filename, int mode) -{ - DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes; - HANDLE hFile = NULL; - WIN32FILE_IOWIN *iowin = NULL; - - win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes); - - if ((filename != NULL) && (dwDesiredAccess != 0)) - { -#ifdef IOWIN32_USING_WINRT_API -#ifdef UNICODE - hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); -#else - WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; - MultiByteToWideChar(CP_ACP, 0, (const char*)filename, -1, filenameW, FILENAME_MAX + 0x200); - hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); -#endif -#else - hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); -#endif - } - - iowin = win32_build_iowin(hFile); - if (iowin == NULL) - return NULL; - iowin->filenameLength = _tcslen(filename) + 1; - iowin->filename = (void*)malloc(iowin->filenameLength * sizeof(TCHAR)); - _tcsncpy(iowin->filename, filename, iowin->filenameLength); - return iowin; -} - static voidpf ZCALLBACK win32_open64_file_funcA(voidpf opaque, const void *filename, int mode) { DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes ; @@ -161,117 +128,6 @@ static voidpf ZCALLBACK win32_open64_file_funcA(voidpf opaque, const void *filen return iowin; } -static voidpf ZCALLBACK win32_open64_file_funcW(voidpf opaque, const void *filename, int mode) -{ - DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes; - HANDLE hFile = NULL; - WIN32FILE_IOWIN *iowin = NULL; - - win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes); - - if ((filename != NULL) && (dwDesiredAccess != 0)) - { -#ifdef IOWIN32_USING_WINRT_API - hFile = CreateFile2((LPCWSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); -#else - hFile = CreateFileW((LPCWSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); -#endif - } - - iowin = win32_build_iowin(hFile); - if (iowin == NULL) - return NULL; - if (iowin->filename == NULL) - { - iowin->filenameLength = wcslen(filename) + 1; - iowin->filename = (void*)malloc(iowin->filenameLength * sizeof(WCHAR)); - wcsncpy(iowin->filename, filename, iowin->filenameLength); - } - return iowin; -} - -voidpf ZCALLBACK win32_open_file_func(voidpf opaque, const char *filename, int mode) -{ - DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes ; - HANDLE hFile = NULL; - WIN32FILE_IOWIN *iowin = NULL; - - win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes); - - if ((filename != NULL) && (dwDesiredAccess != 0)) - { -#ifdef IOWIN32_USING_WINRT_API -#ifdef UNICODE - hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); -#else - WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; - MultiByteToWideChar(CP_ACP, 0, (const char*)filename, -1, filenameW, FILENAME_MAX + 0x200); - hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); -#endif -#else - hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); -#endif - } - - iowin = win32_build_iowin(hFile); - if (iowin == NULL) - return NULL; - iowin->filenameLength = _tcslen((TCHAR*)filename) + 1; - iowin->filename = (void*)malloc(iowin->filenameLength * sizeof(TCHAR)); - _tcsncpy(iowin->filename, (TCHAR*)filename, iowin->filenameLength); - return iowin; -} - -static voidpf ZCALLBACK win32_opendisk64_file_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) -{ - WIN32FILE_IOWIN *iowin = NULL; - TCHAR *diskFilename = NULL; - voidpf ret = NULL; - int i = 0; - - if (stream == NULL) - return NULL; - iowin = (WIN32FILE_IOWIN*)stream; - diskFilename = (TCHAR*)malloc(iowin->filenameLength * sizeof(TCHAR)); - _tcsncpy(diskFilename, iowin->filename, iowin->filenameLength); - for (i = iowin->filenameLength - 1; i >= 0; i -= 1) - { - if (diskFilename[i] != _T('.')) - continue; - _sntprintf(&diskFilename[i], iowin->filenameLength - i, _T(".z%02d"), number_disk + 1); - break; - } - if (i >= 0) - ret = win32_open64_file_func(opaque, (char*)diskFilename, mode); - free(diskFilename); - return ret; -} - -static voidpf ZCALLBACK win32_opendisk64_file_funcW(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) -{ - WIN32FILE_IOWIN *iowin = NULL; - WCHAR *diskFilename = NULL; - voidpf ret = NULL; - int i = 0; - - if (stream == NULL) - return NULL; - iowin = (WIN32FILE_IOWIN*)stream; - diskFilename = (WCHAR*)malloc((iowin->filenameLength + 10) * sizeof(WCHAR)); - wcsncpy(diskFilename, iowin->filename, iowin->filenameLength); - for (i = iowin->filenameLength - 1; i >= 0; i -= 1) - { - if (diskFilename[i] != L'.') - continue; - _snwprintf(&diskFilename[i], (iowin->filenameLength + 10) - i, L".z%02d", number_disk + 1); - break; - } - if (i >= 0) - ret = win32_open64_file_funcW(opaque, diskFilename, mode); - free(diskFilename); - return ret; -} - static voidpf ZCALLBACK win32_opendisk64_file_funcA(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) { WIN32FILE_IOWIN *iowin = NULL; @@ -297,31 +153,6 @@ static voidpf ZCALLBACK win32_opendisk64_file_funcA(voidpf opaque, voidpf stream return ret; } -static voidpf ZCALLBACK win32_opendisk_file_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode) -{ - WIN32FILE_IOWIN *iowin = NULL; - TCHAR *diskFilename = NULL; - voidpf ret = NULL; - int i = 0; - - if (stream == NULL) - return NULL; - iowin = (WIN32FILE_IOWIN*)stream; - diskFilename = (TCHAR*)malloc(iowin->filenameLength * sizeof(TCHAR)); - _tcsncpy(diskFilename, iowin->filename, iowin->filenameLength); - for (i = iowin->filenameLength - 1; i >= 0; i -= 1) - { - if (diskFilename[i] != _T('.')) - continue; - _sntprintf(&diskFilename[i], iowin->filenameLength - i, _T(".z%02d"), number_disk + 1); - break; - } - if (i >= 0) - ret = win32_open_file_func(opaque, (char*)diskFilename, mode); - free(diskFilename); - return ret; -} - uint32_t ZCALLBACK win32_read_file_func(voidpf opaque, voidpf stream, void* buf, uint32_t size) { DWORD ret = 0; @@ -383,28 +214,6 @@ static BOOL win32_setfilepointer_internal(HANDLE hFile, LARGE_INTEGER pos, LARGE #endif } -static long ZCALLBACK win32_tell_file_func(voidpf opaque, voidpf stream) -{ - long ret = -1; - HANDLE hFile = NULL; - if (stream != NULL) - hFile = ((WIN32FILE_IOWIN*)stream)->hf; - if (hFile != NULL) - { - LARGE_INTEGER pos; - pos.QuadPart = 0; - if (!win32_setfilepointer_internal(hFile, pos, &pos, FILE_CURRENT)) - { - DWORD dwErr = GetLastError(); - ((WIN32FILE_IOWIN*)stream)->error = (int)dwErr; - ret = -1; - } - else - ret = (long)pos.LowPart; - } - return ret; -} - uint64_t ZCALLBACK win32_tell64_file_func(voidpf opaque, voidpf stream) { uint64_t ret = (uint64_t)-1; @@ -428,46 +237,6 @@ uint64_t ZCALLBACK win32_tell64_file_func(voidpf opaque, voidpf stream) return ret; } -static long ZCALLBACK win32_seek_file_func(voidpf opaque, voidpf stream, uint32_t offset, int origin) -{ - DWORD dwMoveMethod = 0xFFFFFFFF; - HANDLE hFile = NULL; - long ret = -1; - - if (stream != NULL) - hFile = ((WIN32FILE_IOWIN*)stream)->hf; - - switch (origin) - { - case ZLIB_FILEFUNC_SEEK_CUR: - dwMoveMethod = FILE_CURRENT; - break; - case ZLIB_FILEFUNC_SEEK_END: - dwMoveMethod = FILE_END; - break; - case ZLIB_FILEFUNC_SEEK_SET: - dwMoveMethod = FILE_BEGIN; - break; - default: - return -1; - } - - if (hFile != NULL) - { - LARGE_INTEGER pos; - pos.QuadPart = offset; - if (!win32_setfilepointer_internal(hFile, pos, NULL, dwMoveMethod)) - { - DWORD dwErr = GetLastError(); - ((WIN32FILE_IOWIN*)stream)->error = (int)dwErr; - ret = -1; - } - else - ret = 0; - } - return ret; -} - long ZCALLBACK win32_seek64_file_func(voidpf opaque, voidpf stream, uint64_t offset, int origin) { DWORD dwMoveMethod = 0xFFFFFFFF; @@ -536,32 +305,6 @@ int ZCALLBACK win32_error_file_func(voidpf opaque, voidpf stream) return ret; } -void fill_win32_filefunc(zlib_filefunc_def *pzlib_filefunc_def) -{ - pzlib_filefunc_def->zopen_file = win32_open_file_func; - pzlib_filefunc_def->zopendisk_file = win32_opendisk_file_func; - pzlib_filefunc_def->zread_file = win32_read_file_func; - pzlib_filefunc_def->zwrite_file = win32_write_file_func; - pzlib_filefunc_def->ztell_file = win32_tell_file_func; - pzlib_filefunc_def->zseek_file = win32_seek_file_func; - pzlib_filefunc_def->zclose_file = win32_close_file_func; - pzlib_filefunc_def->zerror_file = win32_error_file_func; - pzlib_filefunc_def->opaque = NULL; -} - -void fill_win32_filefunc64(zlib_filefunc64_def *pzlib_filefunc_def) -{ - pzlib_filefunc_def->zopen64_file = win32_open64_file_func; - pzlib_filefunc_def->zopendisk64_file = win32_opendisk64_file_func; - pzlib_filefunc_def->zread_file = win32_read_file_func; - pzlib_filefunc_def->zwrite_file = win32_write_file_func; - pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; - pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; - pzlib_filefunc_def->zclose_file = win32_close_file_func; - pzlib_filefunc_def->zerror_file = win32_error_file_func; - pzlib_filefunc_def->opaque = NULL; -} - void fill_win32_filefunc64A(zlib_filefunc64_def *pzlib_filefunc_def) { pzlib_filefunc_def->zopen64_file = win32_open64_file_funcA; @@ -575,17 +318,4 @@ void fill_win32_filefunc64A(zlib_filefunc64_def *pzlib_filefunc_def) pzlib_filefunc_def->opaque = NULL; } -void fill_win32_filefunc64W(zlib_filefunc64_def *pzlib_filefunc_def) -{ - pzlib_filefunc_def->zopen64_file = win32_open64_file_funcW; - pzlib_filefunc_def->zopendisk64_file = win32_opendisk64_file_funcW; - pzlib_filefunc_def->zread_file = win32_read_file_func; - pzlib_filefunc_def->zwrite_file = win32_write_file_func; - pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; - pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; - pzlib_filefunc_def->zclose_file = win32_close_file_func; - pzlib_filefunc_def->zerror_file = win32_error_file_func; - pzlib_filefunc_def->opaque = NULL; -} - #endif // _WIN32 diff --git a/src/libgame/zip/iowin32.h b/src/libgame/zip/iowin32.h index 7ce4fc9f..a6265ea3 100644 --- a/src/libgame/zip/iowin32.h +++ b/src/libgame/zip/iowin32.h @@ -23,10 +23,7 @@ extern "C" { #endif -void fill_win32_filefunc(zlib_filefunc_def *pzlib_filefunc_def); -void fill_win32_filefunc64(zlib_filefunc64_def *pzlib_filefunc_def); void fill_win32_filefunc64A(zlib_filefunc64_def *pzlib_filefunc_def); -void fill_win32_filefunc64W(zlib_filefunc64_def *pzlib_filefunc_def); #ifdef __cplusplus } diff --git a/src/libgame/zip/miniunz.c b/src/libgame/zip/miniunz.c index cf9839b1..1ca632a9 100644 --- a/src/libgame/zip/miniunz.c +++ b/src/libgame/zip/miniunz.c @@ -389,7 +389,7 @@ static int miniunz_extract_currentfile(unzFile uf, int opt_extract_without_path, err = unzGetCurrentFileInfo64(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); if (err != UNZ_OK) { - debug_printf("error %d with zipfile in unzGetCurrentFileInfo\n",err); + debug_printf("error %d with zipfile in unzGetCurrentFileInfo\n", err); return err; } diff --git a/src/main.c b/src/main.c index 13f1ed86..a1ba5c7c 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,6 @@ #include "config.h" Bitmap *bitmap_db_field; -Bitmap *bitmap_db_panel; Bitmap *bitmap_db_door_1; Bitmap *bitmap_db_door_2; Bitmap *bitmap_db_store_1; @@ -32,6 +31,7 @@ DrawBuffer *drawto_field; int game_status = -1; int game_status_last_screen = -1; boolean level_editor_test_game = FALSE; +boolean score_info_tape_play = FALSE; boolean network_playing = FALSE; int key_joystick_mapping = 0; @@ -67,7 +67,9 @@ int PlayerVisit[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; int GfxFrame[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; int GfxRandom[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; +int GfxRandomStatic[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; int GfxElement[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; +int GfxElementEmpty[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; int GfxAction[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; int GfxDir[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; int GfxRedraw[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; @@ -128,7 +130,7 @@ boolean network_player_action_received = FALSE; struct LevelInfo level, level_template; struct PlayerInfo stored_player[MAX_PLAYERS], *local_player = NULL; -struct HiScore highscore[MAX_SCORE_ENTRIES]; +struct ScoreInfo scores, server_scores; struct TapeInfo tape; struct GameInfo game; struct GlobalInfo global; @@ -175,6 +177,7 @@ SetupFileHash *element_token_hash = NULL; SetupFileHash *graphic_token_hash = NULL; SetupFileHash *font_token_hash = NULL; SetupFileHash *hide_setup_hash = NULL; +SetupFileHash *anim_url_hash = NULL; // ---------------------------------------------------------------------------- @@ -4660,7 +4663,7 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = { "mm_kettle", "mm_kettle", - "magic kettle" + "magic cauldron" }, { "mm_bomb", @@ -5215,17 +5218,17 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = { "mm_mirror_fixed_2", "mm_mirror_fixed", - "fixed mirror (0\xb0)" + "fixed mirror (45\xb0)" }, { "mm_mirror_fixed_3", "mm_mirror_fixed", - "fixed mirror (0\xb0)" + "fixed mirror (90\xb0)" }, { "mm_mirror_fixed_4", "mm_mirror_fixed", - "fixed mirror (0\xb0)" + "fixed mirror (135\xb0)" }, { "mm_steel_lock", @@ -5293,24 +5296,24 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = "extra energy ball (empty)" }, { - "mm_unused_156", - "unused", - "(not used)" + "mm_envelope_1", + "mm_envelope", + "mail envelope 1 (MM style)" }, { - "mm_unused_157", - "unused", - "(not used)" + "mm_envelope_2", + "mm_envelope", + "mail envelope 2 (MM style)" }, { - "mm_unused_158", - "unused", - "(not used)" + "mm_envelope_3", + "mm_envelope", + "mail envelope 3 (MM style)" }, { - "mm_unused_159", - "unused", - "(not used)" + "mm_envelope_4", + "mm_envelope", + "mail envelope 4 (MM style)" }, { "df_mirror_1", @@ -5395,82 +5398,82 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = { "df_wooden_grid_fixed_1", "df_wooden_grid_fixed", - "fixed wooden polarizer (0\xb0)" + "fixed wooden polarizer (DF) (0\xb0)" }, { "df_wooden_grid_fixed_2", "df_wooden_grid_fixed", - "fixed wooden polarizer (22.5\xb0)" + "fixed wooden polarizer (DF) (22.5\xb0)" }, { "df_wooden_grid_fixed_3", "df_wooden_grid_fixed", - "fixed wooden polarizer (45\xb0)" + "fixed wooden polarizer (DF) (45\xb0)" }, { "df_wooden_grid_fixed_4", "df_wooden_grid_fixed", - "fixed wooden polarizer (67.5\xb0)" + "fixed wooden polarizer (DF) (67.5\xb0)" }, { "df_wooden_grid_fixed_5", "df_wooden_grid_fixed", - "fixed wooden polarizer (90\xb0)" + "fixed wooden polarizer (DF) (90\xb0)" }, { "df_wooden_grid_fixed_6", "df_wooden_grid_fixed", - "fixed wooden polarizer (112.5\xb0)" + "fixed wooden polarizer (DF) (112.5\xb0)" }, { "df_wooden_grid_fixed_7", "df_wooden_grid_fixed", - "fixed wooden polarizer (135\xb0)" + "fixed wooden polarizer (DF) (135\xb0)" }, { "df_wooden_grid_fixed_8", "df_wooden_grid_fixed", - "fixed wooden polarizer (157.5\xb0)" + "fixed wooden polarizer (DF) (157.5\xb0)" }, { "df_steel_grid_fixed_1", "df_steel_grid_fixed", - "fixed steel polarizer (0\xb0)" + "fixed steel polarizer (DF) (0\xb0)" }, { "df_steel_grid_fixed_2", "df_steel_grid_fixed", - "fixed steel polarizer (22.5\xb0)" + "fixed steel polarizer (DF) (22.5\xb0)" }, { "df_steel_grid_fixed_3", "df_steel_grid_fixed", - "fixed steel polarizer (45\xb0)" + "fixed steel polarizer (DF) (45\xb0)" }, { "df_steel_grid_fixed_4", "df_steel_grid_fixed", - "fixed steel polarizer (67.5\xb0)" + "fixed steel polarizer (DF) (67.5\xb0)" }, { "df_steel_grid_fixed_5", "df_steel_grid_fixed", - "fixed steel polarizer (90\xb0)" + "fixed steel polarizer (DF) (90\xb0)" }, { "df_steel_grid_fixed_6", "df_steel_grid_fixed", - "fixed steel polarizer (112.5\xb0)" + "fixed steel polarizer (DF) (112.5\xb0)" }, { "df_steel_grid_fixed_7", "df_steel_grid_fixed", - "fixed steel polarizer (135\xb0)" + "fixed steel polarizer (DF) (135\xb0)" }, { "df_steel_grid_fixed_8", "df_steel_grid_fixed", - "fixed steel polarizer (157.5\xb0)" + "fixed steel polarizer (DF) (157.5\xb0)" }, { "df_wooden_wall_1", @@ -6277,6 +6280,186 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = "spring", "spring (starts moving right)" }, + { + "empty_space_1", + "empty_space", + "empty space 1" + }, + { + "empty_space_2", + "empty_space", + "empty space 2" + }, + { + "empty_space_3", + "empty_space", + "empty space 3" + }, + { + "empty_space_4", + "empty_space", + "empty space 4" + }, + { + "empty_space_5", + "empty_space", + "empty space 5" + }, + { + "empty_space_6", + "empty_space", + "empty space 6" + }, + { + "empty_space_7", + "empty_space", + "empty space 7" + }, + { + "empty_space_8", + "empty_space", + "empty space 8" + }, + { + "empty_space_9", + "empty_space", + "empty space 9" + }, + { + "empty_space_10", + "empty_space", + "empty space 10" + }, + { + "empty_space_11", + "empty_space", + "empty space 11" + }, + { + "empty_space_12", + "empty_space", + "empty space 12" + }, + { + "empty_space_13", + "empty_space", + "empty space 13" + }, + { + "empty_space_14", + "empty_space", + "empty space 14" + }, + { + "empty_space_15", + "empty_space", + "empty space 15" + }, + { + "empty_space_16", + "empty_space", + "empty space 16" + }, + { + "df_mirror_fixed_1", + "df_mirror_fixed", + "fixed mirror (DF style) (0\xb0)" + }, + { + "df_mirror_fixed_2", + "df_mirror_fixed", + "fixed mirror (DF style) (11.25\xb0)" + }, + { + "df_mirror_fixed_3", + "df_mirror_fixed", + "fixed mirror (DF style) (22.5\xb0)" + }, + { + "df_mirror_fixed_4", + "df_mirror_fixed", + "fixed mirror (DF style) (33.75\xb0)" + }, + { + "df_mirror_fixed_5", + "df_mirror_fixed", + "fixed mirror (DF style) (45\xb0)" + }, + { + "df_mirror_fixed_6", + "df_mirror_fixed", + "fixed mirror (DF style) (56.25\xb0)" + }, + { + "df_mirror_fixed_7", + "df_mirror_fixed", + "fixed mirror (DF style) (67.5\xb0)" + }, + { + "df_mirror_fixed_8", + "df_mirror_fixed", + "fixed mirror (DF style) (78.75\xb0)" + }, + { + "df_mirror_fixed_9", + "df_mirror_fixed", + "fixed mirror (DF style) (90\xb0)" + }, + { + "df_mirror_fixed_10", + "df_mirror_fixed", + "fixed mirror (DF style) (101.25\xb0)" + }, + { + "df_mirror_fixed_11", + "df_mirror_fixed", + "fixed mirror (DF style) (112.5\xb0)" + }, + { + "df_mirror_fixed_12", + "df_mirror_fixed", + "fixed mirror (DF style) (123.75\xb0)" + }, + { + "df_mirror_fixed_13", + "df_mirror_fixed", + "fixed mirror (DF style) (135\xb0)" + }, + { + "df_mirror_fixed_14", + "df_mirror_fixed", + "fixed mirror (DF style) (146.25\xb0)" + }, + { + "df_mirror_fixed_15", + "df_mirror_fixed", + "fixed mirror (DF style) (157.5\xb0)" + }, + { + "df_mirror_fixed_16", + "df_mirror_fixed", + "fixed mirror (DF style) (168.75\xb0)" + }, + { + "df_slope_1", + "df_slope", + "slope (DF style) (45\xb0)" + }, + { + "df_slope_2", + "df_slope", + "slope (DF style) (135\xb0)" + }, + { + "df_slope_3", + "df_slope", + "slope (DF style) (225\xb0)" + }, + { + "df_slope_4", + "df_slope", + "slope (DF style) (315\xb0)" + }, // -------------------------------------------------------------------------- // "real" (and therefore drawable) runtime elements @@ -6653,6 +6836,11 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = "mm_exit", "-" }, + { + "mm_gray_ball.active", + "mm_gray_ball", + "-", + }, { "mm_gray_ball.opening", "mm_gray_ball", @@ -6688,6 +6876,16 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = "mm_pacman", "pac man (eating down)" }, + { + "mm_bomb.active", + "mm_bomb", + "active bomb (MM style)" + }, + { + "df_mine.active", + "df_mine", + "active mine" + }, // -------------------------------------------------------------------------- // "unreal" (and therefore not drawable) runtime elements @@ -6938,56 +7136,6 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = "-", "-" }, - { - "mm_mask_mcduffin.right", - "-", - "-" - }, - { - "mm_mask_mcduffin.up", - "-", - "-" - }, - { - "mm_mask_mcduffin.left", - "-", - "-" - }, - { - "mm_mask_mcduffin.down", - "-", - "-" - }, - { - "mm_mask_grid_1", - "-", - "-" - }, - { - "mm_mask_grid_2", - "-", - "-" - }, - { - "mm_mask_grid_3", - "-", - "-" - }, - { - "mm_mask_grid_4", - "-", - "-" - }, - { - "mm_mask_rectangle", - "-", - "-" - }, - { - "mm_mask_circle", - "-", - "-" - }, { "[default]", "default", @@ -7213,6 +7361,16 @@ struct ElementNameInfo element_name_info[MAX_NUM_ELEMENTS + 1] = "internal", "hide group elements" }, + { + "internal_cascade_es", + "internal", + "show empty space elements" + }, + { + "internal_cascade_es.active", + "internal", + "hide empty space elements" + }, { "internal_cascade_ref", "internal", @@ -7402,6 +7560,7 @@ struct ElementDirectionInfo element_direction_info[NUM_DIRECTIONS_FULL + 1] = struct SpecialSuffixInfo special_suffix_info[NUM_SPECIAL_GFX_ARGS + 1 + 1] = { { ".[DEFAULT]", GFX_SPECIAL_ARG_DEFAULT, }, + { ".LOADING_INITIAL", GFX_SPECIAL_ARG_LOADING_INITIAL, }, { ".LOADING", GFX_SPECIAL_ARG_LOADING, }, { ".TITLE_INITIAL", GFX_SPECIAL_ARG_TITLE_INITIAL, }, { ".TITLE_INITIAL_1", GFX_SPECIAL_ARG_TITLE_INITIAL_1, }, @@ -7420,6 +7579,7 @@ struct SpecialSuffixInfo special_suffix_info[NUM_SPECIAL_GFX_ARGS + 1 + 1] = { ".LEVELS", GFX_SPECIAL_ARG_LEVELS }, { ".LEVELNR", GFX_SPECIAL_ARG_LEVELNR }, { ".SCORES", GFX_SPECIAL_ARG_SCORES, }, + { ".SCOREINFO", GFX_SPECIAL_ARG_SCOREINFO, }, { ".EDITOR", GFX_SPECIAL_ARG_EDITOR, }, { ".INFO", GFX_SPECIAL_ARG_INFO, }, { ".SETUP", GFX_SPECIAL_ARG_SETUP, }, @@ -7431,6 +7591,7 @@ struct SpecialSuffixInfo special_suffix_info[NUM_SPECIAL_GFX_ARGS + 1 + 1] = { ".CRUMBLED", GFX_SPECIAL_ARG_CRUMBLED, }, { ".MAINONLY", GFX_SPECIAL_ARG_MAINONLY, }, { ".NAMESONLY", GFX_SPECIAL_ARG_NAMESONLY, }, + { ".SCORESONLY", GFX_SPECIAL_ARG_SCORESONLY, }, { ".TYPENAME", GFX_SPECIAL_ARG_TYPENAME, }, { ".TYPENAMES", GFX_SPECIAL_ARG_TYPENAMES, }, { ".SUBMENU", GFX_SPECIAL_ARG_SUBMENU, }, @@ -7483,6 +7644,7 @@ struct FontInfo font_info[NUM_FONTS + 1] = { "font.envelope_2" }, { "font.envelope_3" }, { "font.envelope_4" }, + { "font.request_narrow" }, { "font.request" }, { "font.input_1.active" }, { "font.input_2.active" }, @@ -7613,10 +7775,11 @@ static void print_usage(void) "\n" "Options:\n" " -b, --basepath DIRECTORY alternative base DIRECTORY\n" - " -l, --level DIRECTORY alternative level DIRECTORY\n" + " -l, --levels DIRECTORY alternative levels DIRECTORY\n" " -g, --graphics DIRECTORY alternative graphics DIRECTORY\n" " -s, --sounds DIRECTORY alternative sounds DIRECTORY\n" " -m, --music DIRECTORY alternative music DIRECTORY\n" + " --display NR open program window on display NR\n" " --mytapes use private tapes for tape tests\n" " -n, --network network multiplayer game\n" " --serveronly only start network server\n" @@ -7641,8 +7804,8 @@ static void print_usage(void) " \"autofix LEVELDIR [NR ...]\" test and fix tapes for LEVELDIR\n" " \"patch tapes MODE LEVELDIR [NR]\" patch level tapes for LEVELDIR\n" " \"convert LEVELDIR [NR]\" convert levels in LEVELDIR\n" - " \"create images DIRECTORY\" write BMP images to DIRECTORY\n" - " \"create CE image DIRECTORY\" write BMP image to DIRECTORY\n" + " \"create sketch images DIRECTORY\" write BMP images to DIRECTORY\n" + " \"create collect image DIRECTORY\" write BMP image to DIRECTORY\n" "\n", program.command_basename); } @@ -7699,33 +7862,13 @@ static void InitProgramConfig(char *command_filename) char *program_title = PROGRAM_TITLE_STRING; char *program_icon_file = PROGRAM_ICON_FILENAME; char *program_version = getProgramRealVersionString(); + char *program_basename = getBaseNameNoSuffix(command_filename); char *config_filename = getProgramConfigFilename(command_filename); - char *userdata_basename = getBaseNameNoSuffix(command_filename); char *userdata_subdir; -#if defined(PLATFORM_UNIX) - char *userdata_subdir_unix; -#endif // read default program config, if existing if (fileExists(config_filename)) - { - // if program config file exists, derive Unix user data directory from it - // (but only if the program config file is not generic "setup.conf" file) - if (!strEqual(getBaseNamePtr(config_filename), SETUP_FILENAME)) - { - userdata_basename = getBaseName(config_filename); - - if (strSuffix(userdata_basename, ".conf")) - userdata_basename[strlen(userdata_basename) - 5] = '\0'; - } - LoadSetupFromFilename(config_filename); - } - -#if defined(PLATFORM_UNIX) - // set user data directory for Linux/Unix (but not Mac OS X) - userdata_subdir_unix = getStringCat2(".", userdata_basename); -#endif // set program title from potentially redefined program title if (setup.internal.program_title != NULL && @@ -7742,10 +7885,10 @@ static void InitProgramConfig(char *command_filename) strlen(setup.internal.program_icon_file) > 0) program_icon_file = getStringCopy(setup.internal.program_icon_file); -#if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX) +#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC) || defined(PLATFORM_EMSCRIPTEN) userdata_subdir = program_title; #elif defined(PLATFORM_UNIX) - userdata_subdir = userdata_subdir_unix; + userdata_subdir = getStringCat2(".", program_basename); #else userdata_subdir = USERDATA_DIRECTORY_OTHER; #endif @@ -7761,7 +7904,7 @@ static void InitProgramConfig(char *command_filename) InitProgramInfo(command_filename, config_filename, userdata_subdir, - program_title, + program_basename, program_title, program_icon_file, COOKIE_PREFIX, diff --git a/src/main.h b/src/main.h index c67fbebb..6f5b29c7 100644 --- a/src/main.h +++ b/src/main.h @@ -118,80 +118,81 @@ // values for pre-defined properties // (from here on, values can be changed by inserting new values) -#define EP_PLAYER 32 -#define EP_CAN_PASS_MAGIC_WALL 33 -#define EP_CAN_PASS_DC_MAGIC_WALL 34 -#define EP_SWITCHABLE 35 -#define EP_BD_ELEMENT 36 -#define EP_SP_ELEMENT 37 -#define EP_SB_ELEMENT 38 -#define EP_GEM 39 -#define EP_FOOD_DARK_YAMYAM 40 -#define EP_FOOD_PENGUIN 41 -#define EP_FOOD_PIG 42 -#define EP_HISTORIC_WALL 43 -#define EP_HISTORIC_SOLID 44 -#define EP_CLASSIC_ENEMY 45 -#define EP_BELT 46 -#define EP_BELT_ACTIVE 47 -#define EP_BELT_SWITCH 48 -#define EP_TUBE 49 -#define EP_ACID_POOL 50 -#define EP_KEYGATE 51 -#define EP_AMOEBOID 52 -#define EP_AMOEBALIVE 53 -#define EP_HAS_EDITOR_CONTENT 54 -#define EP_CAN_TURN_EACH_MOVE 55 -#define EP_CAN_GROW 56 -#define EP_ACTIVE_BOMB 57 -#define EP_INACTIVE 58 +#define EP_EMPTY_SPACE 32 +#define EP_PLAYER 33 +#define EP_CAN_PASS_MAGIC_WALL 34 +#define EP_CAN_PASS_DC_MAGIC_WALL 35 +#define EP_SWITCHABLE 36 +#define EP_BD_ELEMENT 37 +#define EP_SP_ELEMENT 38 +#define EP_SB_ELEMENT 39 +#define EP_GEM 40 +#define EP_FOOD_DARK_YAMYAM 41 +#define EP_FOOD_PENGUIN 42 +#define EP_FOOD_PIG 43 +#define EP_HISTORIC_WALL 44 +#define EP_HISTORIC_SOLID 45 +#define EP_CLASSIC_ENEMY 46 +#define EP_BELT 47 +#define EP_BELT_ACTIVE 48 +#define EP_BELT_SWITCH 49 +#define EP_TUBE 50 +#define EP_ACID_POOL 51 +#define EP_KEYGATE 52 +#define EP_AMOEBOID 53 +#define EP_AMOEBALIVE 54 +#define EP_HAS_EDITOR_CONTENT 55 +#define EP_CAN_TURN_EACH_MOVE 56 +#define EP_CAN_GROW 57 +#define EP_ACTIVE_BOMB 58 +#define EP_INACTIVE 59 // values for special configurable properties (depending on level settings) -#define EP_EM_SLIPPERY_WALL 59 +#define EP_EM_SLIPPERY_WALL 60 // values for special graphics properties (no effect on game engine) -#define EP_GFX_CRUMBLED 60 +#define EP_GFX_CRUMBLED 61 // values for derived properties (determined from properties above) -#define EP_ACCESSIBLE_OVER 61 -#define EP_ACCESSIBLE_INSIDE 62 -#define EP_ACCESSIBLE_UNDER 63 -#define EP_WALKABLE 64 -#define EP_PASSABLE 65 -#define EP_ACCESSIBLE 66 -#define EP_COLLECTIBLE 67 -#define EP_SNAPPABLE 68 -#define EP_WALL 69 -#define EP_SOLID_FOR_PUSHING 70 -#define EP_DRAGONFIRE_PROOF 71 -#define EP_EXPLOSION_PROOF 72 -#define EP_CAN_SMASH 73 -#define EP_EXPLODES_3X3_OLD 74 -#define EP_CAN_EXPLODE_BY_FIRE 75 -#define EP_CAN_EXPLODE_SMASHED 76 -#define EP_CAN_EXPLODE_IMPACT 77 -#define EP_SP_PORT 78 -#define EP_CAN_EXPLODE_BY_DRAGONFIRE 79 -#define EP_CAN_EXPLODE_BY_EXPLOSION 80 -#define EP_COULD_MOVE_INTO_ACID 81 -#define EP_MAYBE_DONT_COLLIDE_WITH 82 -#define EP_CAN_BE_CLONED_BY_ANDROID 83 +#define EP_ACCESSIBLE_OVER 62 +#define EP_ACCESSIBLE_INSIDE 63 +#define EP_ACCESSIBLE_UNDER 64 +#define EP_WALKABLE 65 +#define EP_PASSABLE 66 +#define EP_ACCESSIBLE 67 +#define EP_COLLECTIBLE 68 +#define EP_SNAPPABLE 69 +#define EP_WALL 70 +#define EP_SOLID_FOR_PUSHING 71 +#define EP_DRAGONFIRE_PROOF 72 +#define EP_EXPLOSION_PROOF 73 +#define EP_CAN_SMASH 74 +#define EP_EXPLODES_3X3_OLD 75 +#define EP_CAN_EXPLODE_BY_FIRE 76 +#define EP_CAN_EXPLODE_SMASHED 77 +#define EP_CAN_EXPLODE_IMPACT 78 +#define EP_SP_PORT 79 +#define EP_CAN_EXPLODE_BY_DRAGONFIRE 80 +#define EP_CAN_EXPLODE_BY_EXPLOSION 81 +#define EP_COULD_MOVE_INTO_ACID 82 +#define EP_MAYBE_DONT_COLLIDE_WITH 83 +#define EP_CAN_BE_CLONED_BY_ANDROID 84 // values for internal purpose only (level editor) -#define EP_WALK_TO_OBJECT 84 -#define EP_DEADLY 85 -#define EP_EDITOR_CASCADE 86 -#define EP_EDITOR_CASCADE_ACTIVE 87 -#define EP_EDITOR_CASCADE_INACTIVE 88 +#define EP_WALK_TO_OBJECT 85 +#define EP_DEADLY 86 +#define EP_EDITOR_CASCADE 87 +#define EP_EDITOR_CASCADE_ACTIVE 88 +#define EP_EDITOR_CASCADE_INACTIVE 89 // values for internal purpose only (game engine) -#define EP_HAS_ACTION 89 -#define EP_CAN_CHANGE_OR_HAS_ACTION 90 +#define EP_HAS_ACTION 90 +#define EP_CAN_CHANGE_OR_HAS_ACTION 91 // values for internal purpose only (other) -#define EP_OBSOLETE 91 +#define EP_OBSOLETE 92 -#define NUM_ELEMENT_PROPERTIES 92 +#define NUM_ELEMENT_PROPERTIES 93 #define NUM_EP_BITFIELDS ((NUM_ELEMENT_PROPERTIES + 31) / 32) #define EP_BITFIELD_BASE_NR 0 @@ -199,10 +200,10 @@ #define EP_BITMASK_BASE_DEFAULT (1 << EP_CAN_MOVE_INTO_ACID) #define EP_BITMASK_DEFAULT 0 -#define PROPERTY_BIT(p) (1 << ((p) % 32)) -#define PROPERTY_VAR(e,p) (element_info[e].properties[(p) / 32]) -#define HAS_PROPERTY(e,p) ((PROPERTY_VAR(e, p) & PROPERTY_BIT(p)) != 0) -#define SET_PROPERTY(e,p,v) ((v) ? \ +#define PROPERTY_BIT(p) (1u << ((p) % 32)) +#define PROPERTY_VAR(e, p) (element_info[e].properties[(p) / 32]) +#define HAS_PROPERTY(e, p) ((PROPERTY_VAR(e, p) & PROPERTY_BIT(p)) != 0) +#define SET_PROPERTY(e, p, v) ((v) ? \ (PROPERTY_VAR(e,p) |= PROPERTY_BIT(p)) : \ (PROPERTY_VAR(e,p) &= ~PROPERTY_BIT(p))) @@ -256,8 +257,11 @@ #define CE_PRESSED_BY_MOUSE 45 #define CE_MOUSE_CLICKED_ON_X 46 #define CE_MOUSE_PRESSED_ON_X 47 +#define CE_NEXT_TO_PLAYER 48 +#define CE_NEXT_TO_X 49 +#define CE_PLAYER_NEXT_TO_X 50 -#define NUM_CHANGE_EVENTS 48 +#define NUM_CHANGE_EVENTS 51 #define NUM_CE_BITFIELDS ((NUM_CHANGE_EVENTS + 31) / 32) @@ -269,19 +273,19 @@ #define CH_EVENT_BITFIELD_NR(e) (e / 32) #define CH_EVENT_BIT(e) (1 << ((e) % 32)) -#define CH_EVENT_VAR(e,c) (element_info[e].change->has_event[c]) -#define CH_ANY_EVENT_VAR(e,c) (element_info[e].has_change_event[c]) +#define CH_EVENT_VAR(e, c) (element_info[e].change->has_event[c]) +#define CH_ANY_EVENT_VAR(e, c) (element_info[e].has_change_event[c]) -#define PAGE_HAS_CHANGE_EVENT(p,c) ((p)->has_event[c]) -#define HAS_CHANGE_EVENT(e,c) (IS_CUSTOM_ELEMENT(e) && \ - CH_EVENT_VAR(e,c)) -#define HAS_ANY_CHANGE_EVENT(e,c) (IS_CUSTOM_ELEMENT(e) && \ - CH_ANY_EVENT_VAR(e,c)) +#define PAGE_HAS_CHANGE_EVENT(p, c) ((p)->has_event[c]) +#define HAS_CHANGE_EVENT(e, c) (IS_CUSTOM_ELEMENT(e) && \ + CH_EVENT_VAR(e, c)) +#define HAS_ANY_CHANGE_EVENT(e, c) (IS_CUSTOM_ELEMENT(e) && \ + CH_ANY_EVENT_VAR(e, c)) -#define SET_CHANGE_EVENT(e,c,v) (IS_CUSTOM_ELEMENT(e) ? \ - CH_EVENT_VAR(e,c) = (v) : 0) -#define SET_ANY_CHANGE_EVENT(e,c,v) (IS_CUSTOM_ELEMENT(e) ? \ - CH_ANY_EVENT_VAR(e,c) = (v) : 0) +#define SET_CHANGE_EVENT(e, c, v) (IS_CUSTOM_ELEMENT(e) ? \ + CH_EVENT_VAR(e, c) = (v) : 0) +#define SET_ANY_CHANGE_EVENT(e, c, v) (IS_CUSTOM_ELEMENT(e) ? \ + CH_ANY_EVENT_VAR(e, c) = (v) : 0) // values for player bitmasks #define PLAYER_BITS_NONE 0 @@ -596,7 +600,8 @@ #define GFX_CRUMBLED(e) HAS_PROPERTY(GFX_ELEMENT(e), EP_GFX_CRUMBLED) // macros for pre-defined properties -#define ELEM_IS_PLAYER(e) HAS_PROPERTY(e, EP_PLAYER) +#define IS_EMPTY_SPACE(e) HAS_PROPERTY(e, EP_EMPTY_SPACE) +#define IS_PLAYER_ELEMENT(e) HAS_PROPERTY(e, EP_PLAYER) #define CAN_PASS_MAGIC_WALL(e) HAS_PROPERTY(e, EP_CAN_PASS_MAGIC_WALL) #define CAN_PASS_DC_MAGIC_WALL(e) HAS_PROPERTY(e, EP_CAN_PASS_DC_MAGIC_WALL) #define IS_SWITCHABLE(e) HAS_PROPERTY(e, EP_SWITCHABLE) @@ -664,6 +669,9 @@ #define IS_OBSOLETE(e) HAS_PROPERTY(e, EP_OBSOLETE) +#define IS_EMPTY(e) IS_EMPTY_SPACE(e) +#define IS_EMPTY_ELEMENT(e) IS_EMPTY_SPACE(e) + // special macros used in game engine #define IS_FILE_ELEMENT(e) ((e) >= 0 && \ (e) <= NUM_FILE_ELEMENTS) @@ -689,11 +697,22 @@ #define IS_INTERNAL_ELEMENT(e) ((e) >= EL_INTERNAL_START && \ (e) <= EL_INTERNAL_END) -#define IS_MM_ELEMENT(e) ((e) >= EL_MM_START && \ - (e) <= EL_MM_END) - -#define IS_DF_ELEMENT(e) ((e) >= EL_DF_START && \ - (e) <= EL_DF_END) +#define IS_MM_ELEMENT_1(e) ((e) >= EL_MM_START_1 && \ + (e) <= EL_MM_END_1) +#define IS_MM_ELEMENT_2(e) ((e) >= EL_MM_START_2 && \ + (e) <= EL_MM_END_2) +#define IS_MM_ELEMENT_3(e) ((e) >= EL_MM_START_3 && \ + (e) <= EL_MM_END_3) +#define IS_MM_ELEMENT(e) (IS_MM_ELEMENT_1(e) || \ + IS_MM_ELEMENT_2(e) || \ + IS_MM_ELEMENT_3(e)) + +#define IS_DF_ELEMENT_1(e) ((e) >= EL_DF_START_1 && \ + (e) <= EL_DF_END_1) +#define IS_DF_ELEMENT_2(e) ((e) >= EL_DF_START_2 && \ + (e) <= EL_DF_END_2) +#define IS_DF_ELEMENT(e) (IS_DF_ELEMENT_1(e) || \ + IS_DF_ELEMENT_2(e)) #define IS_MM_MCDUFFIN(e) ((e) >= EL_MM_MCDUFFIN_START && \ (e) <= EL_MM_MCDUFFIN_END) @@ -719,6 +738,9 @@ #define IS_ENVELOPE(e) ((e) >= EL_ENVELOPE_1 && \ (e) <= EL_ENVELOPE_4) +#define IS_MM_ENVELOPE(e) ((e) >= EL_MM_ENVELOPE_1 && \ + (e) <= EL_MM_ENVELOPE_4) + #define IS_BALLOON_ELEMENT(e) ((e) == EL_BALLOON || \ (e) == EL_BALLOON_SWITCH_LEFT || \ (e) == EL_BALLOON_SWITCH_RIGHT || \ @@ -794,6 +816,11 @@ IS_EM_GATE_GRAY(e) ? EM_GATE_GRAY_NR(e) : \ IS_EMC_GATE_GRAY(e) ? EMC_GATE_GRAY_NR(e) : 0) +#define RND_ENVELOPE_NR(e) ((e) - EL_ENVELOPE_1) +#define MM_ENVELOPE_NR(e) ((e) - EL_MM_ENVELOPE_1) +#define ENVELOPE_NR(e) (IS_ENVELOPE(e) ? RND_ENVELOPE_NR(e) : \ + MM_ENVELOPE_NR(e)) + #define IS_ACID_POOL_OR_ACID(e) (IS_ACID_POOL(e) || (e) == EL_ACID) #define IS_EMC_PILLAR(e) ((e) >= EL_EMC_WALL_1 && \ @@ -850,14 +877,14 @@ (ge == EL_ANY_ELEMENT ? TRUE : \ IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge)) -#define IS_PLAYER(x, y) (ELEM_IS_PLAYER(StorePlayer[x][y])) +#define IS_PLAYER(x, y) (IS_PLAYER_ELEMENT(StorePlayer[x][y])) #define IS_FREE(x, y) (Tile[x][y] == EL_EMPTY && !IS_PLAYER(x, y)) #define IS_FREE_OR_PLAYER(x, y) (Tile[x][y] == EL_EMPTY) -#define IS_MOVING(x,y) (MovPos[x][y] != 0) -#define IS_FALLING(x,y) (MovPos[x][y] != 0 && MovDir[x][y] == MV_DOWN) -#define IS_BLOCKED(x,y) (Tile[x][y] == EL_BLOCKED) +#define IS_MOVING(x, y) (MovPos[x][y] != 0) +#define IS_FALLING(x, y) (MovPos[x][y] != 0 && MovDir[x][y] == MV_DOWN) +#define IS_BLOCKED(x, y) (Tile[x][y] == EL_BLOCKED) #define IS_MV_DIAGONAL(x) ((x) & MV_HORIZONTAL && (x) & MV_VERTICAL) @@ -885,13 +912,13 @@ #define TAPE_IS_EMPTY(x) ((x).length == 0) #define TAPE_IS_STOPPED(x) (!(x).recording && !(x).playing) -#define PLAYERINFO(x,y) (&stored_player[StorePlayer[x][y]-EL_PLAYER_1]) +#define PLAYERINFO(x, y) (&stored_player[StorePlayer[x][y] - EL_PLAYER_1]) #define SHIELD_ON(p) ((p)->shield_normal_time_left > 0) -#define ENEMY_PROTECTED_FIELD(x,y) (IS_PROTECTED(Tile[x][y]) || \ +#define ENEMY_PROTECTED_FIELD(x, y) (IS_PROTECTED(Tile[x][y]) || \ IS_PROTECTED(Back[x][y])) -#define EXPLOSION_PROTECTED_FIELD(x,y) (IS_EXPLOSION_PROOF(Tile[x][y])) -#define PLAYER_ENEMY_PROTECTED(x,y) (SHIELD_ON(PLAYERINFO(x, y)) || \ +#define EXPLOSION_PROTECTED_FIELD(x, y) (IS_EXPLOSION_PROOF(Tile[x][y])) +#define PLAYER_ENEMY_PROTECTED(x, y) (SHIELD_ON(PLAYERINFO(x, y)) || \ ENEMY_PROTECTED_FIELD(x, y)) #define PLAYER_EXPLOSION_PROTECTED(x,y) (SHIELD_ON(PLAYERINFO(x, y)) || \ EXPLOSION_PROTECTED_FIELD(x, y)) @@ -902,13 +929,16 @@ #define PLAYER_DROPPING(p,x,y) ((p)->is_dropping && \ (p)->drop_x == (x) && (p)->drop_y == (y)) -#define PLAYER_NR_GFX(g,i) ((g) + i * (IMG_PLAYER_2 - IMG_PLAYER_1)) +#define PLAYER_NR_GFX(g, i) ((g) + i * (IMG_PLAYER_2 - IMG_PLAYER_1)) #define GET_PLAYER_ELEMENT(e) ((e) >= EL_PLAYER_1 && (e) <= EL_PLAYER_4 ? \ (e) : EL_PLAYER_1) #define GET_PLAYER_NR(e) (GET_PLAYER_ELEMENT(e) - EL_PLAYER_1) +#define GET_EMPTY_ELEMENT(i) ((i) == 0 ? EL_EMPTY_SPACE : \ + EL_EMPTY_SPACE_1 + (i) - 1) + #define ANIM_FRAMES(g) (graphic_info[g].anim_frames) #define ANIM_DELAY(g) (graphic_info[g].anim_delay) #define ANIM_MODE(g) (graphic_info[g].anim_mode) @@ -921,8 +951,8 @@ #define IS_NEW_FRAME(f, g) (IS_ANIMATED(g) && IS_NEW_DELAY(f, g)) #define IS_NEXT_FRAME(f, g) (IS_NEW_FRAME(f, g) && (f) > 0) -#define IS_LOOP_SOUND(s) (sound_info[s].loop) -#define IS_LOOP_MUSIC(s) (music_info[s].loop) +#define IS_LOOP_SOUND(s) ((s) >= 0 && sound_info[s].loop) +#define IS_LOOP_MUSIC(s) ((s) < 0 || music_info[s].loop) #define IS_SPECIAL_GFX_ARG(a) ((a) >= 0 && (a) < NUM_SPECIAL_GFX_ARGS) @@ -973,11 +1003,21 @@ #define MAX_ANDROID_ELEMENTS 32 #define MAX_ANDROID_ELEMENTS_OLD 16 // (extended since version 4.2.0.0) +#define MAX_ISO_DATE_LEN 10 +#define MAX_PLATFORM_TEXT_LEN 16 +#define MAX_VERSION_TEXT_LEN 16 +#define MAX_COUNTRY_CODE_LEN 2 +#define MAX_COUNTRY_NAME_LEN 64 + // values for elements with content #define MIN_ELEMENT_CONTENTS 1 #define STD_ELEMENT_CONTENTS 4 #define MAX_ELEMENT_CONTENTS 8 +#define MIN_MM_BALL_CONTENTS 1 +#define STD_MM_BALL_CONTENTS 8 +#define MAX_MM_BALL_CONTENTS 16 + // values for initial player inventory #define MIN_INITIAL_INVENTORY_SIZE 1 #define MAX_INITIAL_INVENTORY_SIZE 8 @@ -1382,7 +1422,7 @@ #define NUM_GROUP_ELEMENTS 32 #define EL_GROUP_END 655 -// ---------- end of custom elements section ---------------------------------- +// ---------- end of group elements section ----------------------------------- #define EL_UNKNOWN 656 #define EL_TRIGGER_ELEMENT 657 @@ -1681,16 +1721,16 @@ #define EL_MM_WOODEN_GRID_FIXED_4 (EL_MM_WOODEN_GRID_FIXED_START + 3) #define EL_MM_WOODEN_GRID_FIXED_END EL_MM_WOODEN_GRID_FIXED_03 #define EL_MM_FUEL_EMPTY (EL_MM_START + 155) - -#define EL_MM_UNUSED_156 (EL_MM_START + 156) -#define EL_MM_UNUSED_157 (EL_MM_START + 157) -#define EL_MM_UNUSED_158 (EL_MM_START + 158) -#define EL_MM_UNUSED_159 (EL_MM_START + 159) +#define EL_MM_ENVELOPE_1 (EL_MM_START + 156) +#define EL_MM_ENVELOPE_2 (EL_MM_START + 157) +#define EL_MM_ENVELOPE_3 (EL_MM_START + 158) +#define EL_MM_ENVELOPE_4 (EL_MM_START + 159) #define EL_MM_END_1 (EL_MM_START + 159) #define EL_MM_START_2 (EL_MM_START + 160) #define EL_DF_START EL_MM_START_2 +#define EL_DF_START_1 EL_MM_START_2 #define EL_DF_START2 (EL_DF_START - 240) #define EL_DF_MIRROR_START EL_DF_START @@ -1816,7 +1856,7 @@ #define EL_DF_STEEL_GRID_ROTATING_8 (EL_DF_STEEL_GRID_ROTATING_START + 7) #define EL_DF_STEEL_GRID_ROTATING_END EL_DF_STEEL_GRID_ROTATING_07 -#define EL_DF_END (EL_DF_START2 + 355) +#define EL_DF_END_1 (EL_DF_START2 + 355) #define EL_MM_TELEPORTER_RED_START (EL_DF_START2 + 356) #define EL_MM_TELEPORTER_RED_1 (EL_MM_TELEPORTER_RED_START + 0) @@ -1904,12 +1944,53 @@ #define EL_DF_WOODEN_WALL 1214 #define EL_MM_END_2 (EL_DF_START2 + 430) -#define EL_MM_END EL_MM_END_2 #define EL_SPRING_LEFT 1215 #define EL_SPRING_RIGHT 1216 -#define NUM_FILE_ELEMENTS 1217 +// ---------- begin of empty space elements section --------------------------- +#define EL_EMPTY_SPACE_START 1217 + +#include "conf_emp.h" // include auto-generated data structure definitions + +#define NUM_EMPTY_SPACE_ELEMENTS 16 +#define NUM_EMPTY_ELEMENTS_ALL (NUM_EMPTY_SPACE_ELEMENTS + 1) +#define EL_EMPTY_SPACE_END 1232 +// ---------- end of empty space elements section ----------------------------- + +#define EL_MM_START_3 EL_DF_MIRROR_FIXED_START +#define EL_DF_START_2 EL_DF_MIRROR_FIXED_START + +#define EL_DF_MIRROR_FIXED_START 1233 +#define EL_DF_MIRROR_FIXED_1 (EL_DF_MIRROR_FIXED_START + 0) +#define EL_DF_MIRROR_FIXED_2 (EL_DF_MIRROR_FIXED_START + 1) +#define EL_DF_MIRROR_FIXED_3 (EL_DF_MIRROR_FIXED_START + 2) +#define EL_DF_MIRROR_FIXED_4 (EL_DF_MIRROR_FIXED_START + 3) +#define EL_DF_MIRROR_FIXED_5 (EL_DF_MIRROR_FIXED_START + 4) +#define EL_DF_MIRROR_FIXED_6 (EL_DF_MIRROR_FIXED_START + 5) +#define EL_DF_MIRROR_FIXED_7 (EL_DF_MIRROR_FIXED_START + 6) +#define EL_DF_MIRROR_FIXED_8 (EL_DF_MIRROR_FIXED_START + 7) +#define EL_DF_MIRROR_FIXED_9 (EL_DF_MIRROR_FIXED_START + 8) +#define EL_DF_MIRROR_FIXED_10 (EL_DF_MIRROR_FIXED_START + 9) +#define EL_DF_MIRROR_FIXED_11 (EL_DF_MIRROR_FIXED_START + 10) +#define EL_DF_MIRROR_FIXED_12 (EL_DF_MIRROR_FIXED_START + 11) +#define EL_DF_MIRROR_FIXED_13 (EL_DF_MIRROR_FIXED_START + 12) +#define EL_DF_MIRROR_FIXED_14 (EL_DF_MIRROR_FIXED_START + 13) +#define EL_DF_MIRROR_FIXED_15 (EL_DF_MIRROR_FIXED_START + 14) +#define EL_DF_MIRROR_FIXED_16 (EL_DF_MIRROR_FIXED_START + 15) +#define EL_DF_MIRROR_FIXED_END EL_DF_MIRROR_FIXED_16 + +#define EL_DF_SLOPE_START 1249 +#define EL_DF_SLOPE_1 (EL_DF_SLOPE_START + 0) +#define EL_DF_SLOPE_2 (EL_DF_SLOPE_START + 1) +#define EL_DF_SLOPE_3 (EL_DF_SLOPE_START + 2) +#define EL_DF_SLOPE_4 (EL_DF_SLOPE_START + 3) +#define EL_DF_SLOPE_END EL_DF_SLOPE_4 + +#define EL_MM_END_3 EL_DF_SLOPE_END +#define EL_DF_END_2 EL_DF_SLOPE_END + +#define NUM_FILE_ELEMENTS 1253 // "real" (and therefore drawable) runtime elements @@ -1989,15 +2070,18 @@ #define EL_EMC_SPRING_BUMPER_ACTIVE (EL_FIRST_RUNTIME_REAL + 71) #define EL_MM_EXIT_OPENING (EL_FIRST_RUNTIME_REAL + 72) #define EL_MM_EXIT_CLOSING (EL_FIRST_RUNTIME_REAL + 73) -#define EL_MM_GRAY_BALL_OPENING (EL_FIRST_RUNTIME_REAL + 74) -#define EL_MM_ICE_WALL_SHRINKING (EL_FIRST_RUNTIME_REAL + 75) -#define EL_MM_AMOEBA_WALL_GROWING (EL_FIRST_RUNTIME_REAL + 76) -#define EL_MM_PACMAN_EATING_RIGHT (EL_FIRST_RUNTIME_REAL + 77) -#define EL_MM_PACMAN_EATING_UP (EL_FIRST_RUNTIME_REAL + 78) -#define EL_MM_PACMAN_EATING_LEFT (EL_FIRST_RUNTIME_REAL + 79) -#define EL_MM_PACMAN_EATING_DOWN (EL_FIRST_RUNTIME_REAL + 80) - -#define NUM_DRAWABLE_ELEMENTS (EL_FIRST_RUNTIME_REAL + 81) +#define EL_MM_GRAY_BALL_ACTIVE (EL_FIRST_RUNTIME_REAL + 74) +#define EL_MM_GRAY_BALL_OPENING (EL_FIRST_RUNTIME_REAL + 75) +#define EL_MM_ICE_WALL_SHRINKING (EL_FIRST_RUNTIME_REAL + 76) +#define EL_MM_AMOEBA_WALL_GROWING (EL_FIRST_RUNTIME_REAL + 77) +#define EL_MM_PACMAN_EATING_RIGHT (EL_FIRST_RUNTIME_REAL + 78) +#define EL_MM_PACMAN_EATING_UP (EL_FIRST_RUNTIME_REAL + 79) +#define EL_MM_PACMAN_EATING_LEFT (EL_FIRST_RUNTIME_REAL + 80) +#define EL_MM_PACMAN_EATING_DOWN (EL_FIRST_RUNTIME_REAL + 81) +#define EL_MM_BOMB_ACTIVE (EL_FIRST_RUNTIME_REAL + 82) +#define EL_DF_MINE_ACTIVE (EL_FIRST_RUNTIME_REAL + 83) + +#define NUM_DRAWABLE_ELEMENTS (EL_FIRST_RUNTIME_REAL + 84) #define EL_MM_RUNTIME_START EL_MM_EXIT_OPENING #define EL_MM_RUNTIME_END EL_MM_AMOEBA_WALL_GROWING @@ -2059,35 +2143,22 @@ #define EL_MM_LIGHTBALL_RED (EL_FIRST_DUMMY + 21) #define EL_MM_LIGHTBALL_BLUE (EL_FIRST_DUMMY + 22) #define EL_MM_LIGHTBALL_YELLOW (EL_FIRST_DUMMY + 23) -#define EL_MM_MASK_MCDUFFIN_RIGHT (EL_FIRST_DUMMY + 24) -#define EL_MM_MASK_MCDUFFIN_UP (EL_FIRST_DUMMY + 25) -#define EL_MM_MASK_MCDUFFIN_LEFT (EL_FIRST_DUMMY + 26) -#define EL_MM_MASK_MCDUFFIN_DOWN (EL_FIRST_DUMMY + 27) -#define EL_MM_MASK_GRID_1 (EL_FIRST_DUMMY + 28) -#define EL_MM_MASK_GRID_2 (EL_FIRST_DUMMY + 29) -#define EL_MM_MASK_GRID_3 (EL_FIRST_DUMMY + 30) -#define EL_MM_MASK_GRID_4 (EL_FIRST_DUMMY + 31) -#define EL_MM_MASK_RECTANGLE (EL_FIRST_DUMMY + 32) -#define EL_MM_MASK_CIRCLE (EL_FIRST_DUMMY + 33) -#define EL_DEFAULT (EL_FIRST_DUMMY + 34) -#define EL_BD_DEFAULT (EL_FIRST_DUMMY + 35) -#define EL_SP_DEFAULT (EL_FIRST_DUMMY + 36) -#define EL_SB_DEFAULT (EL_FIRST_DUMMY + 37) -#define EL_MM_DEFAULT (EL_FIRST_DUMMY + 38) -#define EL_GRAPHIC_1 (EL_FIRST_DUMMY + 39) -#define EL_GRAPHIC_2 (EL_FIRST_DUMMY + 40) -#define EL_GRAPHIC_3 (EL_FIRST_DUMMY + 41) -#define EL_GRAPHIC_4 (EL_FIRST_DUMMY + 42) -#define EL_GRAPHIC_5 (EL_FIRST_DUMMY + 43) -#define EL_GRAPHIC_6 (EL_FIRST_DUMMY + 44) -#define EL_GRAPHIC_7 (EL_FIRST_DUMMY + 45) -#define EL_GRAPHIC_8 (EL_FIRST_DUMMY + 46) - -#define EL_MM_DUMMY_START EL_MM_MASK_MCDUFFIN_RIGHT -#define EL_MM_DUMMY_END EL_MM_MASK_CIRCLE +#define EL_DEFAULT (EL_FIRST_DUMMY + 24) +#define EL_BD_DEFAULT (EL_FIRST_DUMMY + 25) +#define EL_SP_DEFAULT (EL_FIRST_DUMMY + 26) +#define EL_SB_DEFAULT (EL_FIRST_DUMMY + 27) +#define EL_MM_DEFAULT (EL_FIRST_DUMMY + 28) +#define EL_GRAPHIC_1 (EL_FIRST_DUMMY + 29) +#define EL_GRAPHIC_2 (EL_FIRST_DUMMY + 30) +#define EL_GRAPHIC_3 (EL_FIRST_DUMMY + 31) +#define EL_GRAPHIC_4 (EL_FIRST_DUMMY + 32) +#define EL_GRAPHIC_5 (EL_FIRST_DUMMY + 33) +#define EL_GRAPHIC_6 (EL_FIRST_DUMMY + 34) +#define EL_GRAPHIC_7 (EL_FIRST_DUMMY + 35) +#define EL_GRAPHIC_8 (EL_FIRST_DUMMY + 36) // internal elements (only used for internal purposes like copying) -#define EL_FIRST_INTERNAL (EL_FIRST_DUMMY + 47) +#define EL_FIRST_INTERNAL (EL_FIRST_DUMMY + 37) #define EL_INTERNAL_CLIPBOARD_CUSTOM (EL_FIRST_INTERNAL + 0) #define EL_INTERNAL_CLIPBOARD_CHANGE (EL_FIRST_INTERNAL + 1) @@ -2122,19 +2193,21 @@ #define EL_INTERNAL_CASCADE_CE_ACTIVE (EL_FIRST_INTERNAL + 29) #define EL_INTERNAL_CASCADE_GE (EL_FIRST_INTERNAL + 30) #define EL_INTERNAL_CASCADE_GE_ACTIVE (EL_FIRST_INTERNAL + 31) -#define EL_INTERNAL_CASCADE_REF (EL_FIRST_INTERNAL + 32) -#define EL_INTERNAL_CASCADE_REF_ACTIVE (EL_FIRST_INTERNAL + 33) -#define EL_INTERNAL_CASCADE_USER (EL_FIRST_INTERNAL + 34) -#define EL_INTERNAL_CASCADE_USER_ACTIVE (EL_FIRST_INTERNAL + 35) -#define EL_INTERNAL_CASCADE_DYNAMIC (EL_FIRST_INTERNAL + 36) -#define EL_INTERNAL_CASCADE_DYNAMIC_ACTIVE (EL_FIRST_INTERNAL + 37) +#define EL_INTERNAL_CASCADE_ES (EL_FIRST_INTERNAL + 32) +#define EL_INTERNAL_CASCADE_ES_ACTIVE (EL_FIRST_INTERNAL + 33) +#define EL_INTERNAL_CASCADE_REF (EL_FIRST_INTERNAL + 34) +#define EL_INTERNAL_CASCADE_REF_ACTIVE (EL_FIRST_INTERNAL + 35) +#define EL_INTERNAL_CASCADE_USER (EL_FIRST_INTERNAL + 36) +#define EL_INTERNAL_CASCADE_USER_ACTIVE (EL_FIRST_INTERNAL + 37) +#define EL_INTERNAL_CASCADE_DYNAMIC (EL_FIRST_INTERNAL + 38) +#define EL_INTERNAL_CASCADE_DYNAMIC_ACTIVE (EL_FIRST_INTERNAL + 39) #define EL_INTERNAL_CLIPBOARD_START (EL_FIRST_INTERNAL + 0) #define EL_INTERNAL_CLIPBOARD_END (EL_FIRST_INTERNAL + 2) #define EL_INTERNAL_START (EL_FIRST_INTERNAL + 0) -#define EL_INTERNAL_END (EL_FIRST_INTERNAL + 37) +#define EL_INTERNAL_END (EL_FIRST_INTERNAL + 39) -#define MAX_NUM_ELEMENTS (EL_FIRST_INTERNAL + 38) +#define MAX_NUM_ELEMENTS (EL_FIRST_INTERNAL + 40) // values for graphics/sounds action types @@ -2270,6 +2343,7 @@ enum enum { GFX_SPECIAL_ARG_DEFAULT = 0, + GFX_SPECIAL_ARG_LOADING_INITIAL, GFX_SPECIAL_ARG_LOADING, GFX_SPECIAL_ARG_TITLE_INITIAL, GFX_SPECIAL_ARG_TITLE_INITIAL_1, @@ -2288,6 +2362,7 @@ enum GFX_SPECIAL_ARG_LEVELS, GFX_SPECIAL_ARG_LEVELNR, GFX_SPECIAL_ARG_SCORES, + GFX_SPECIAL_ARG_SCOREINFO, GFX_SPECIAL_ARG_EDITOR, GFX_SPECIAL_ARG_INFO, GFX_SPECIAL_ARG_SETUP, @@ -2299,6 +2374,7 @@ enum GFX_SPECIAL_ARG_CRUMBLED, GFX_SPECIAL_ARG_MAINONLY, GFX_SPECIAL_ARG_NAMESONLY, + GFX_SPECIAL_ARG_SCORESONLY, GFX_SPECIAL_ARG_TYPENAME, GFX_SPECIAL_ARG_TYPENAMES, GFX_SPECIAL_ARG_SUBMENU, @@ -2308,6 +2384,7 @@ enum GFX_SPECIAL_ARG_SCORESNEW, GFX_SPECIAL_ARG_NO_TITLE, GFX_SPECIAL_ARG_FADING, + GFX_SPECIAL_ARG_RESTARTING, GFX_SPECIAL_ARG_QUIT, NUM_SPECIAL_GFX_ARGS @@ -2379,6 +2456,7 @@ enum GFX_ARG_DELAY, GFX_ARG_ANIM_MODE, GFX_ARG_GLOBAL_SYNC, + GFX_ARG_GLOBAL_ANIM_SYNC, GFX_ARG_CRUMBLED_LIKE, GFX_ARG_DIGGABLE_LIKE, GFX_ARG_BORDER_SIZE, @@ -2419,10 +2497,15 @@ enum GFX_ARG_SORT_PRIORITY, GFX_ARG_CLASS, GFX_ARG_STYLE, + GFX_ARG_ALPHA, GFX_ARG_ACTIVE_XOFFSET, GFX_ARG_ACTIVE_YOFFSET, GFX_ARG_PRESSED_XOFFSET, GFX_ARG_PRESSED_YOFFSET, + GFX_ARG_STACKED_XFACTOR, + GFX_ARG_STACKED_YFACTOR, + GFX_ARG_STACKED_XOFFSET, + GFX_ARG_STACKED_YOFFSET, NUM_GFX_ARGS }; @@ -2470,6 +2553,7 @@ enum FONT_ENVELOPE_2, FONT_ENVELOPE_3, FONT_ENVELOPE_4, + FONT_REQUEST_NARROW, FONT_REQUEST, FONT_INPUT_1_ACTIVE, FONT_INPUT_2_ACTIVE, @@ -2521,6 +2605,7 @@ enum // values for game_status (must match special image configuration suffixes) #define GAME_MODE_DEFAULT GFX_SPECIAL_ARG_DEFAULT +#define GAME_MODE_LOADING_INITIAL GFX_SPECIAL_ARG_LOADING_INITIAL #define GAME_MODE_LOADING GFX_SPECIAL_ARG_LOADING #define GAME_MODE_TITLE_INITIAL GFX_SPECIAL_ARG_TITLE_INITIAL #define GAME_MODE_TITLE_INITIAL_1 GFX_SPECIAL_ARG_TITLE_INITIAL_1 @@ -2539,6 +2624,7 @@ enum #define GAME_MODE_LEVELS GFX_SPECIAL_ARG_LEVELS #define GAME_MODE_LEVELNR GFX_SPECIAL_ARG_LEVELNR #define GAME_MODE_SCORES GFX_SPECIAL_ARG_SCORES +#define GAME_MODE_SCOREINFO GFX_SPECIAL_ARG_SCOREINFO #define GAME_MODE_EDITOR GFX_SPECIAL_ARG_EDITOR #define GAME_MODE_INFO GFX_SPECIAL_ARG_INFO #define GAME_MODE_SETUP GFX_SPECIAL_ARG_SETUP @@ -2550,6 +2636,7 @@ enum #define GAME_MODE_PSEUDO_CRUMBLED GFX_SPECIAL_ARG_CRUMBLED #define GAME_MODE_PSEUDO_MAINONLY GFX_SPECIAL_ARG_MAINONLY #define GAME_MODE_PSEUDO_NAMESONLY GFX_SPECIAL_ARG_NAMESONLY +#define GAME_MODE_PSEUDO_SCORESONLY GFX_SPECIAL_ARG_SCORESONLY #define GAME_MODE_PSEUDO_TYPENAME GFX_SPECIAL_ARG_TYPENAME #define GAME_MODE_PSEUDO_TYPENAMES GFX_SPECIAL_ARG_TYPENAMES #define GAME_MODE_PSEUDO_SUBMENU GFX_SPECIAL_ARG_SUBMENU @@ -2559,6 +2646,7 @@ enum #define GAME_MODE_PSEUDO_SCORESNEW GFX_SPECIAL_ARG_SCORESNEW #define GAME_MODE_PSEUDO_NO_TITLE GFX_SPECIAL_ARG_NO_TITLE #define GAME_MODE_PSEUDO_FADING GFX_SPECIAL_ARG_FADING +#define GAME_MODE_PSEUDO_RESTARTING GFX_SPECIAL_ARG_RESTARTING #define GAME_MODE_QUIT GFX_SPECIAL_ARG_QUIT #define NUM_GAME_MODES NUM_SPECIAL_GFX_ARGS @@ -2574,19 +2662,19 @@ enum // program information and versioning definitions #define PROGRAM_VERSION_SUPER 4 -#define PROGRAM_VERSION_MAJOR 2 -#define PROGRAM_VERSION_MINOR 3 -#define PROGRAM_VERSION_PATCH 1 +#define PROGRAM_VERSION_MAJOR 3 +#define PROGRAM_VERSION_MINOR 8 +#define PROGRAM_VERSION_PATCH 2 #define PROGRAM_VERSION_EXTRA "" #define PROGRAM_TITLE_STRING "Rocks'n'Diamonds" #define PROGRAM_AUTHOR_STRING "Holger Schemel" #define PROGRAM_EMAIL_STRING "info@artsoft.org" #define PROGRAM_WEBSITE_STRING "https://www.artsoft.org/" -#define PROGRAM_COPYRIGHT_STRING "Copyright \xa9""1995-2021 by Holger Schemel" +#define PROGRAM_COPYRIGHT_STRING "1995-2024 by Holger Schemel" #define PROGRAM_COMPANY_STRING "A Game by Artsoft Entertainment" -#define PROGRAM_ICON_FILENAME "RocksIcon32x32.png" +#define PROGRAM_ICON_FILENAME "icons/icon.png" #define COOKIE_PREFIX "ROCKSNDIAMONDS" @@ -2624,7 +2712,7 @@ enum // values for game_emulation #define EMU_NONE 0 #define EMU_BOULDERDASH 1 -#define EMU_SOKOBAN 2 +#define EMU_UNUSED_2 2 #define EMU_SUPAPLEX 3 // values for level file type identifier @@ -2655,7 +2743,9 @@ enum #define AUTOPLAY_FFWD (1 << 1) #define AUTOPLAY_WARP (1 << 2) #define AUTOPLAY_TEST (1 << 3) -#define AUTOPLAY_FIX (1 << 4) +#define AUTOPLAY_SAVE (1 << 4) +#define AUTOPLAY_UPLOAD (1 << 5) +#define AUTOPLAY_FIX (1 << 6) #define AUTOPLAY_WARP_NO_DISPLAY AUTOPLAY_TEST #define AUTOPLAY_MODE_NONE 0 @@ -2663,6 +2753,8 @@ enum #define AUTOPLAY_MODE_FFWD (AUTOPLAY_MODE_PLAY | AUTOPLAY_FFWD) #define AUTOPLAY_MODE_WARP (AUTOPLAY_MODE_FFWD | AUTOPLAY_WARP) #define AUTOPLAY_MODE_TEST (AUTOPLAY_MODE_WARP | AUTOPLAY_TEST) +#define AUTOPLAY_MODE_SAVE (AUTOPLAY_MODE_TEST | AUTOPLAY_SAVE) +#define AUTOPLAY_MODE_UPLOAD (AUTOPLAY_MODE_TEST | AUTOPLAY_UPLOAD) #define AUTOPLAY_MODE_FIX (AUTOPLAY_MODE_TEST | AUTOPLAY_FIX) #define AUTOPLAY_MODE_WARP_NO_DISPLAY AUTOPLAY_MODE_TEST @@ -2710,6 +2802,7 @@ struct MenuMainButtonInfo struct MenuPosInfo insert_solution; struct MenuPosInfo play_solution; + struct MenuPosInfo levelset_info; struct MenuPosInfo switch_ecs_aga; }; @@ -2771,6 +2864,20 @@ struct MenuSetupInfo struct MenuSetupButtonInfo button; }; +struct MenuScoresButtonInfo +{ + struct MenuPosInfo prev_level; + struct MenuPosInfo next_level; + struct MenuPosInfo prev_score; + struct MenuPosInfo next_score; + struct MenuPosInfo play_tape; +}; + +struct MenuScoresInfo +{ + struct MenuScoresButtonInfo button; +}; + struct TitleFadingInfo { int fade_mode; @@ -2801,7 +2908,9 @@ struct TitleMessageInfo struct InitInfo { + struct MenuPosInfo busy_initial; struct MenuPosInfo busy; + struct MenuPosInfo busy_playfield; }; struct MenuInfo @@ -2819,10 +2928,13 @@ struct MenuInfo int list_size[NUM_SPECIAL_GFX_ARGS]; int list_size_info[NUM_SPECIAL_GFX_INFO_ARGS]; + int list_entry_size_info[NUM_SPECIAL_GFX_INFO_ARGS]; + int tile_size_info[NUM_SPECIAL_GFX_INFO_ARGS]; int left_spacing[NUM_SPECIAL_GFX_ARGS]; int left_spacing_info[NUM_SPECIAL_GFX_INFO_ARGS]; int left_spacing_setup[NUM_SPECIAL_GFX_SETUP_ARGS]; + int middle_spacing_info[NUM_SPECIAL_GFX_INFO_ARGS]; int right_spacing[NUM_SPECIAL_GFX_ARGS]; int right_spacing_info[NUM_SPECIAL_GFX_INFO_ARGS]; int right_spacing_setup[NUM_SPECIAL_GFX_SETUP_ARGS]; @@ -2860,6 +2972,7 @@ struct MenuInfo struct MenuMainInfo main; struct MenuSetupInfo setup; + struct MenuScoresInfo scores; }; struct DoorInfo @@ -3038,10 +3151,45 @@ struct ViewportInfo struct RectWithBorder door_2[NUM_SPECIAL_GFX_ARGS]; }; -struct HiScore +struct ScoreEntry { - char Name[MAX_PLAYER_NAME_LEN + 1]; - int Score; + char tape_basename[MAX_FILENAME_LEN + 1]; + char name[MAX_PLAYER_NAME_LEN + 1]; + int score; + int time; // time (in frames) or steps played + + // additional score information for score info screen + int id; + char tape_date[MAX_ISO_DATE_LEN + 1]; + char platform[MAX_PLATFORM_TEXT_LEN + 1]; + char version[MAX_VERSION_TEXT_LEN + 1]; + char country_code[MAX_COUNTRY_CODE_LEN + 1]; + char country_name[MAX_COUNTRY_NAME_LEN + 1]; +}; + +struct ScoreInfo +{ + int file_version; // file format version the score is stored with + int game_version; // game release version the score was created with + + char level_identifier[MAX_FILENAME_LEN + 1]; + int level_nr; + + int num_entries; + int last_added; + int last_added_local; + int last_level_nr; + int last_entry_nr; + int next_level_nr; + + boolean updated; + boolean uploaded; + boolean tape_downloaded; + boolean force_last_added; + boolean continue_playing; + boolean continue_on_return; + + struct ScoreEntry entry[MAX_SCORE_ENTRIES]; }; struct Content @@ -3107,6 +3255,7 @@ struct LevelInfo int time; // available time (seconds) int gems_needed; boolean auto_count_gems; + boolean rate_time_over_score; char name[MAX_LEVEL_NAME_LEN + 1]; char author[MAX_LEVEL_AUTHOR_LEN + 1]; @@ -3192,6 +3341,7 @@ struct LevelInfo boolean auto_exit_sokoban; // automatically finish solved Sokoban levels boolean solved_by_one_player; // level is solved if one player enters exit boolean finish_dig_collect; // only finished dig/collect triggers ce action + boolean keep_walkable_ce; // keep walkable CE if it changes to the player boolean continuous_snapping; // repeated snapping without releasing key boolean block_snap_field; // snapping blocks field to show animation @@ -3206,6 +3356,12 @@ struct LevelInfo int mm_time_ball; int mm_time_block; + int num_mm_ball_contents; + int mm_ball_choice_mode; + int mm_ball_content[MAX_MM_BALL_CONTENTS]; + boolean rotate_mm_ball_content; + boolean explode_mm_ball; + // ('int' instead of 'boolean' because used as selectbox value in editor) int use_step_counter; // count steps instead of seconds for level @@ -3241,8 +3397,9 @@ struct GlobalInfo { char *autoplay_leveldir; int autoplay_level[MAX_TAPES_PER_SET]; + int autoplay_mode; boolean autoplay_all; - boolean autoplay_mode; + time_t autoplay_time; char *patchtapes_mode; char *patchtapes_leveldir; @@ -3252,7 +3409,14 @@ struct GlobalInfo char *convert_leveldir; int convert_level_nr; - char *create_images_dir; + char *dumplevel_leveldir; + int dumplevel_level_nr; + + char *dumptape_leveldir; + int dumptape_level_nr; + + char *create_sketch_images_dir; + char *create_collect_images_dir; int num_toons; @@ -3315,6 +3479,8 @@ struct ElementChangeInfo void (*post_change_function)(int x, int y); short actual_trigger_element; // element that actually triggered change + int actual_trigger_x; // element x position that triggered change + int actual_trigger_y; // element y position that triggered change int actual_trigger_side; // element side that triggered the change int actual_trigger_player; // player which actually triggered change int actual_trigger_player_bits; // player bits of triggering players @@ -3429,6 +3595,8 @@ struct ElementInfo struct ElementGroupInfo *group; // pointer to element group info + boolean has_anim_event; // element can trigger global animation + // ---------- internal values used at runtime when playing ---------- boolean has_change_event[NUM_CHANGE_EVENTS]; @@ -3525,6 +3693,7 @@ struct GraphicInfo int anim_mode; boolean anim_global_sync; + boolean anim_global_anim_sync; int crumbled_like; // element for cloning crumble graphics int diggable_like; // element for cloning digging graphics @@ -3576,12 +3745,18 @@ struct GraphicInfo int class; int style; + int alpha; int active_xoffset; int active_yoffset; int pressed_xoffset; int pressed_yoffset; + int stacked_xfactor; + int stacked_yfactor; + int stacked_xoffset; + int stacked_yoffset; + boolean use_image_size; // use image size as default width and height }; @@ -3611,17 +3786,19 @@ struct MusicFileInfo char *artist_header; char *album_header; char *year_header; + char *played_header; char *title; char *artist; char *album; char *year; + char *played; int music; boolean is_sound; - struct MusicFileInfo *next; + struct MusicFileInfo *prev, *next; }; struct ElementActionInfo @@ -3654,7 +3831,6 @@ struct HelpAnimInfo extern Bitmap *bitmap_db_field; -extern Bitmap *bitmap_db_panel; extern Bitmap *bitmap_db_door_1; extern Bitmap *bitmap_db_door_2; extern Bitmap *bitmap_db_store_1; @@ -3665,6 +3841,7 @@ extern DrawBuffer *drawto_field; extern int game_status; extern int game_status_last_screen; extern boolean level_editor_test_game; +extern boolean score_info_tape_play; extern boolean network_playing; extern int key_joystick_mapping; @@ -3700,7 +3877,9 @@ extern int PlayerVisit[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern int GfxFrame[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern int GfxRandom[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; +extern int GfxRandomStatic[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern int GfxElement[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; +extern int GfxElementEmpty[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern int GfxAction[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern int GfxDir[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; extern int GfxRedraw[MAX_LEV_FIELDX][MAX_LEV_FIELDY]; @@ -3749,7 +3928,7 @@ extern boolean network_player_action_received; extern int graphics_action_mapping[]; extern struct LevelInfo level, level_template; -extern struct HiScore highscore[]; +extern struct ScoreInfo scores, server_scores; extern struct TapeInfo tape; extern struct GlobalInfo global; extern struct BorderInfo border; @@ -3805,6 +3984,7 @@ extern SetupFileHash *element_token_hash; extern SetupFileHash *graphic_token_hash; extern SetupFileHash *font_token_hash; extern SetupFileHash *hide_setup_hash; +extern SetupFileHash *anim_url_hash; extern struct ConfigTypeInfo image_config_suffix[]; extern struct ConfigTypeInfo sound_config_suffix[]; extern struct ConfigTypeInfo music_config_suffix[]; diff --git a/src/screens.c b/src/screens.c index 464840b6..fc344373 100644 --- a/src/screens.c +++ b/src/screens.c @@ -22,6 +22,7 @@ #include "network.h" #include "init.h" #include "config.h" +#include "api.h" #define DEBUG_JOYSTICKS 0 @@ -63,33 +64,45 @@ #define SETUP_MODE_CHOOSE_OTHER 16 // sub-screens on the setup screen (specific) -#define SETUP_MODE_CHOOSE_GAME_SPEED 17 -#define SETUP_MODE_CHOOSE_SCROLL_DELAY 18 -#define SETUP_MODE_CHOOSE_SNAPSHOT_MODE 19 -#define SETUP_MODE_CHOOSE_WINDOW_SIZE 20 -#define SETUP_MODE_CHOOSE_SCALING_TYPE 21 -#define SETUP_MODE_CHOOSE_RENDERING 22 -#define SETUP_MODE_CHOOSE_VSYNC 23 -#define SETUP_MODE_CHOOSE_GRAPHICS 24 -#define SETUP_MODE_CHOOSE_SOUNDS 25 -#define SETUP_MODE_CHOOSE_MUSIC 26 -#define SETUP_MODE_CHOOSE_VOLUME_SIMPLE 27 -#define SETUP_MODE_CHOOSE_VOLUME_LOOPS 28 -#define SETUP_MODE_CHOOSE_VOLUME_MUSIC 29 -#define SETUP_MODE_CHOOSE_TOUCH_CONTROL 30 -#define SETUP_MODE_CHOOSE_MOVE_DISTANCE 31 -#define SETUP_MODE_CHOOSE_DROP_DISTANCE 32 -#define SETUP_MODE_CHOOSE_TRANSPARENCY 33 -#define SETUP_MODE_CHOOSE_GRID_XSIZE_0 34 -#define SETUP_MODE_CHOOSE_GRID_YSIZE_0 35 -#define SETUP_MODE_CHOOSE_GRID_XSIZE_1 36 -#define SETUP_MODE_CHOOSE_GRID_YSIZE_1 37 -#define SETUP_MODE_CONFIG_VIRT_BUTTONS 38 - -#define MAX_SETUP_MODES 39 +#define SETUP_MODE_CHOOSE_SCORES_TYPE 17 +#define SETUP_MODE_CHOOSE_GAME_SPEED 18 +#define SETUP_MODE_CHOOSE_SCROLL_DELAY 19 +#define SETUP_MODE_CHOOSE_SNAPSHOT_MODE 20 +#define SETUP_MODE_CHOOSE_WINDOW_SIZE 21 +#define SETUP_MODE_CHOOSE_SCALING_TYPE 22 +#define SETUP_MODE_CHOOSE_RENDERING 23 +#define SETUP_MODE_CHOOSE_VSYNC 24 +#define SETUP_MODE_CHOOSE_GRAPHICS 25 +#define SETUP_MODE_CHOOSE_SOUNDS 26 +#define SETUP_MODE_CHOOSE_MUSIC 27 +#define SETUP_MODE_CHOOSE_VOLUME_SIMPLE 28 +#define SETUP_MODE_CHOOSE_VOLUME_LOOPS 29 +#define SETUP_MODE_CHOOSE_VOLUME_MUSIC 30 +#define SETUP_MODE_CHOOSE_TOUCH_CONTROL 31 +#define SETUP_MODE_CHOOSE_MOVE_DISTANCE 32 +#define SETUP_MODE_CHOOSE_DROP_DISTANCE 33 +#define SETUP_MODE_CHOOSE_TRANSPARENCY 34 +#define SETUP_MODE_CHOOSE_GRID_XSIZE_0 35 +#define SETUP_MODE_CHOOSE_GRID_YSIZE_0 36 +#define SETUP_MODE_CHOOSE_GRID_XSIZE_1 37 +#define SETUP_MODE_CHOOSE_GRID_YSIZE_1 38 +#define SETUP_MODE_CONFIG_VIRT_BUTTONS 39 + +#define MAX_SETUP_MODES 40 #define MAX_MENU_MODES MAX(MAX_INFO_MODES, MAX_SETUP_MODES) +// info screen titles +#define STR_INFO_MAIN "Info Screen" +#define STR_INFO_TITLE "Title Screen" +#define STR_INFO_ELEMENTS "Game Elements" +#define STR_INFO_MUSIC "Music Info" +#define STR_INFO_CREDITS "Credits" +#define STR_INFO_PROGRAM "Program Info" +#define STR_INFO_VERSION "Version Info" +#define STR_INFO_LEVELSET "Level Set Info" +#define STR_INFO_EXIT "Exit" + // setup screen titles #define STR_SETUP_MAIN "Setup" #define STR_SETUP_GAME "Game & Menu" @@ -104,6 +117,7 @@ #define STR_SETUP_EXIT "Exit" #define STR_SETUP_SAVE_AND_EXIT "Save and Exit" +#define STR_SETUP_CHOOSE_SCORES_TYPE "Scores Type" #define STR_SETUP_CHOOSE_GAME_SPEED "Game Speed" #define STR_SETUP_CHOOSE_SCROLL_DELAY "Scroll Delay" #define STR_SETUP_CHOOSE_SNAPSHOT_MODE "Snapshot Mode" @@ -128,6 +142,12 @@ #define MENU_CHOOSE_TREE_FONT(x) (FONT_TEXT_1 + (x)) #define MENU_CHOOSE_TREE_COLOR(ti, a) TREE_COLOR(ti, a) +#define TEXT_MAIN_MENU "Press any key or button for main menu" +#define TEXT_INFO_MENU "Press any key or button for info menu" +#define TEXT_NEXT_PAGE "Press any key or button for next page" +#define TEXT_NEXT_MENU (info_screens_from_main ? \ + TEXT_MAIN_MENU : TEXT_INFO_MENU) + // for input setup functions #define SETUPINPUT_SCREEN_POS_START 0 #define SETUPINPUT_SCREEN_POS_EMPTY1 3 @@ -153,28 +173,40 @@ #define MENU_INFO_FONT_FOOT FONT_TEXT_4 #define MENU_INFO_SPACE_HEAD (menu.headline2_spacing_info[info_mode]) #define MENU_SCREEN_INFO_SPACE_LEFT (menu.left_spacing_info[info_mode]) +#define MENU_SCREEN_INFO_SPACE_MIDDLE (menu.middle_spacing_info[info_mode]) #define MENU_SCREEN_INFO_SPACE_RIGHT (menu.right_spacing_info[info_mode]) #define MENU_SCREEN_INFO_SPACE_TOP (menu.top_spacing_info[info_mode]) #define MENU_SCREEN_INFO_SPACE_BOTTOM (menu.bottom_spacing_info[info_mode]) -#define MENU_SCREEN_INFO_YSTART1 MENU_SCREEN_INFO_SPACE_TOP -#define MENU_SCREEN_INFO_YSTART2 (MENU_SCREEN_INFO_YSTART1 + \ - getMenuTextStep(MENU_INFO_SPACE_HEAD, \ - MENU_INFO_FONT_TITLE)) -#define MENU_SCREEN_INFO_YSTEP (TILEY + 4) +#define MENU_SCREEN_INFO_SPACE_LINE (menu.line_spacing_info[info_mode]) +#define MENU_SCREEN_INFO_SPACE_EXTRA (menu.extra_spacing_info[info_mode]) +#define MENU_SCREEN_INFO_TILE_SIZE_RAW (menu.tile_size_info[info_mode]) +#define MENU_SCREEN_INFO_TILE_SIZE (MENU_SCREEN_INFO_TILE_SIZE_RAW > 0 ? \ + MENU_SCREEN_INFO_TILE_SIZE_RAW : TILEY) +#define MENU_SCREEN_INFO_ENTRY_SIZE_RAW (menu.list_entry_size_info[info_mode]) +#define MENU_SCREEN_INFO_ENTRY_SIZE (MAX(MENU_SCREEN_INFO_ENTRY_SIZE_RAW, \ + MENU_SCREEN_INFO_TILE_SIZE)) +#define MENU_SCREEN_INFO_YSTART MENU_SCREEN_INFO_SPACE_TOP +#define MENU_SCREEN_INFO_YSTEP (MENU_SCREEN_INFO_ENTRY_SIZE + \ + MENU_SCREEN_INFO_SPACE_EXTRA) #define MENU_SCREEN_INFO_YBOTTOM (SYSIZE - MENU_SCREEN_INFO_SPACE_BOTTOM) #define MENU_SCREEN_INFO_YSIZE (MENU_SCREEN_INFO_YBOTTOM - \ - MENU_SCREEN_INFO_YSTART2 - \ + MENU_SCREEN_INFO_YSTART - \ TILEY / 2) -#define MAX_INFO_ELEMENTS_ON_SCREEN 128 -#define STD_INFO_ELEMENTS_ON_SCREEN (MENU_SCREEN_INFO_YSIZE / \ +#define MAX_INFO_ELEMENTS_IN_ARRAY 128 +#define MAX_INFO_ELEMENTS_ON_SCREEN (SYSIZE / TILEY) +#define MAX_INFO_ELEMENTS MIN(MAX_INFO_ELEMENTS_IN_ARRAY, \ + MAX_INFO_ELEMENTS_ON_SCREEN) +#define STD_INFO_ELEMENTS_ON_SCREEN 10 +#define DYN_INFO_ELEMENTS_ON_SCREEN (MENU_SCREEN_INFO_YSIZE / \ MENU_SCREEN_INFO_YSTEP) -#define NUM_INFO_ELEMENTS_FROM_CONF \ +#define DEFAULT_INFO_ELEMENTS MIN(STD_INFO_ELEMENTS_ON_SCREEN,\ + DYN_INFO_ELEMENTS_ON_SCREEN) +#define NUM_INFO_ELEMENTS_FROM_CONF \ (menu.list_size_info[GFX_SPECIAL_ARG_INFO_ELEMENTS] > 0 ? \ menu.list_size_info[GFX_SPECIAL_ARG_INFO_ELEMENTS] : \ - MAX_MENU_ENTRIES_ON_SCREEN) -#define NUM_INFO_ELEMENTS_ON_SCREEN MIN(MIN(STD_INFO_ELEMENTS_ON_SCREEN, \ - MAX_INFO_ELEMENTS_ON_SCREEN), \ - NUM_INFO_ELEMENTS_FROM_CONF) + DEFAULT_INFO_ELEMENTS) +#define NUM_INFO_ELEMENTS_ON_SCREEN MIN(NUM_INFO_ELEMENTS_FROM_CONF,\ + MAX_INFO_ELEMENTS) #define MAX_MENU_ENTRIES_ON_SCREEN (SCR_FIELDY - MENU_SCREEN_START_YPOS) #define MAX_MENU_TEXT_LENGTH_BIG 13 #define MAX_MENU_TEXT_LENGTH_MEDIUM (MAX_MENU_TEXT_LENGTH_BIG * 2) @@ -182,35 +214,46 @@ // screen gadget identifiers #define SCREEN_CTRL_ID_PREV_LEVEL 0 #define SCREEN_CTRL_ID_NEXT_LEVEL 1 -#define SCREEN_CTRL_ID_FIRST_LEVEL 2 -#define SCREEN_CTRL_ID_LAST_LEVEL 3 -#define SCREEN_CTRL_ID_LEVEL_NUMBER 4 -#define SCREEN_CTRL_ID_PREV_PLAYER 5 -#define SCREEN_CTRL_ID_NEXT_PLAYER 6 -#define SCREEN_CTRL_ID_INSERT_SOLUTION 7 -#define SCREEN_CTRL_ID_PLAY_SOLUTION 8 -#define SCREEN_CTRL_ID_SWITCH_ECS_AGA 9 -#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 10 -#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 11 -#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2 12 -#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2 13 -#define SCREEN_CTRL_ID_SCROLL_UP 14 -#define SCREEN_CTRL_ID_SCROLL_DOWN 15 -#define SCREEN_CTRL_ID_SCROLL_VERTICAL 16 -#define SCREEN_CTRL_ID_NETWORK_SERVER 17 - -#define NUM_SCREEN_GADGETS 18 - -#define NUM_SCREEN_MENUBUTTONS 14 +#define SCREEN_CTRL_ID_PREV_LEVEL2 2 +#define SCREEN_CTRL_ID_NEXT_LEVEL2 3 +#define SCREEN_CTRL_ID_PREV_SCORE 4 +#define SCREEN_CTRL_ID_NEXT_SCORE 5 +#define SCREEN_CTRL_ID_PLAY_TAPE 6 +#define SCREEN_CTRL_ID_FIRST_LEVEL 7 +#define SCREEN_CTRL_ID_LAST_LEVEL 8 +#define SCREEN_CTRL_ID_LEVEL_NUMBER 9 +#define SCREEN_CTRL_ID_PREV_PLAYER 10 +#define SCREEN_CTRL_ID_NEXT_PLAYER 11 +#define SCREEN_CTRL_ID_INSERT_SOLUTION 12 +#define SCREEN_CTRL_ID_PLAY_SOLUTION 13 +#define SCREEN_CTRL_ID_LEVELSET_INFO 14 +#define SCREEN_CTRL_ID_SWITCH_ECS_AGA 15 +#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE 16 +#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE 17 +#define SCREEN_CTRL_ID_TOUCH_PREV_PAGE2 18 +#define SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2 19 + +#define NUM_SCREEN_MENUBUTTONS 20 + +#define SCREEN_CTRL_ID_SCROLL_UP 20 +#define SCREEN_CTRL_ID_SCROLL_DOWN 21 +#define SCREEN_CTRL_ID_SCROLL_VERTICAL 22 +#define SCREEN_CTRL_ID_NETWORK_SERVER 23 + +#define NUM_SCREEN_GADGETS 24 + #define NUM_SCREEN_SCROLLBUTTONS 2 #define NUM_SCREEN_SCROLLBARS 1 #define NUM_SCREEN_TEXTINPUT 1 #define SCREEN_MASK_MAIN (1 << 0) #define SCREEN_MASK_MAIN_HAS_SOLUTION (1 << 1) -#define SCREEN_MASK_INPUT (1 << 2) -#define SCREEN_MASK_TOUCH (1 << 3) -#define SCREEN_MASK_TOUCH2 (1 << 4) +#define SCREEN_MASK_MAIN_HAS_SET_INFO (1 << 2) +#define SCREEN_MASK_INPUT (1 << 3) +#define SCREEN_MASK_TOUCH (1 << 4) +#define SCREEN_MASK_TOUCH2 (1 << 5) +#define SCREEN_MASK_SCORES (1 << 6) +#define SCREEN_MASK_SCORES_INFO (1 << 7) // graphic position and size values for buttons and scrollbars #define SC_MENUBUTTON_XSIZE TILEX @@ -257,6 +300,8 @@ static void HandleChooseTree(int, int, int, int, int, TreeInfo **); static void DrawChoosePlayerName(void); static void DrawChooseLevelSet(void); static void DrawChooseLevelNr(void); +static void DrawScoreInfo(int); +static void DrawScoreInfo_Content(int); static void DrawInfoScreen(void); static void DrawSetupScreen(void); static void DrawTypeName(void); @@ -265,28 +310,48 @@ static void DrawInfoScreen_NotAvailable(char *, char *); static void DrawInfoScreen_HelpAnim(int, int, boolean); static void DrawInfoScreen_HelpText(int, int, int, int); static void HandleInfoScreen_Main(int, int, int, int, int); -static void HandleInfoScreen_TitleScreen(int); -static void HandleInfoScreen_Elements(int); -static void HandleInfoScreen_Music(int); -static void HandleInfoScreen_Credits(int); -static void HandleInfoScreen_Program(int); +static void HandleInfoScreen_TitleScreen(int, int, int); +static void HandleInfoScreen_Elements(int, int, int); +static void HandleInfoScreen_Music(int, int, int); static void HandleInfoScreen_Version(int); +static void HandleInfoScreen_Generic(int, int, int); static void ModifyGameSpeedIfNeeded(void); static void DisableVsyncIfNeeded(void); +static void RedrawScreenMenuGadgets(int); static void MapScreenMenuGadgets(int); static void UnmapScreenMenuGadgets(int); static void MapScreenGadgets(int); +static void UnmapScreenGadgets(void); static void MapScreenTreeGadgets(TreeInfo *); +static void UnmapScreenTreeGadgets(void); static void UpdateScreenMenuGadgets(int, boolean); +static void AdjustScoreInfoButtons_SelectScore(int, int, int); +static void AdjustScoreInfoButtons_PlayTape(int, int, boolean); + +static boolean OfferUploadTapes(void); +static void execOfferUploadTapes(void); + +static void DrawHallOfFame_setScoreEntries(void); +static void HandleHallOfFame_SelectLevel(int, int); +static char *getHallOfFameRankText(int, int); +static char *getHallOfFameScoreText(int, int); +static char *getInfoScreenTitle_Generic(void); +static int getInfoScreenBackgroundImage_Generic(void); +static int getInfoScreenBackgroundSound_Generic(void); +static int getInfoScreenBackgroundMusic_Generic(void); + +static struct TokenInfo *getSetupInfoFinal(struct TokenInfo *); static struct GadgetInfo *screen_gadget[NUM_SCREEN_GADGETS]; static int info_mode = INFO_MODE_MAIN; static int setup_mode = SETUP_MODE_MAIN; +static boolean info_screens_from_main = FALSE; + static TreeInfo *window_sizes = NULL; static TreeInfo *window_size_current = NULL; @@ -305,6 +370,9 @@ static TreeInfo *scroll_delay_current = NULL; static TreeInfo *snapshot_modes = NULL; static TreeInfo *snapshot_mode_current = NULL; +static TreeInfo *scores_types = NULL; +static TreeInfo *scores_type_current = NULL; + static TreeInfo *game_speeds_normal = NULL; static TreeInfo *game_speeds_extended = NULL; static TreeInfo *game_speeds = NULL; @@ -340,6 +408,9 @@ static TreeInfo *player_name_current = NULL; static TreeInfo *level_number = NULL; static TreeInfo *level_number_current = NULL; +static TreeInfo *score_entries = NULL; +static TreeInfo *score_entry_current = NULL; + static struct ValueTextInfo window_sizes_list[] = { { 50, "50 %" }, @@ -389,6 +460,15 @@ static struct StringValueTextInfo vsync_modes_list[] = { NULL, NULL }, }; +static struct StringValueTextInfo scores_types_list[] = +{ + { STR_SCORES_TYPE_LOCAL_ONLY, "Local scores only" }, + { STR_SCORES_TYPE_SERVER_ONLY, "Server scores only" }, + { STR_SCORES_TYPE_LOCAL_AND_SERVER, "Local and server scores" }, + + { NULL, NULL }, +}; + static struct ValueTextInfo game_speeds_list_normal[] = { { 30, "Very Slow" }, @@ -587,6 +667,10 @@ static int align_yoffset = 0; menu.extra_spacing[GAME_MODE_SETUP] : \ menu.extra_spacing_setup[DRAW_MODE_SETUP(i)]) +#define EXTRA_SPACING_SCORES(i) (EXTRA_SPACING_INFO(i)) + +#define EXTRA_SPACING_SCOREINFO(i) (menu.extra_spacing[GAME_MODE_SCOREINFO]) + #define DRAW_XOFFSET(s) ((s) == GAME_MODE_INFO ? \ DRAW_XOFFSET_INFO(info_mode) : \ (s) == GAME_MODE_SETUP ? \ @@ -601,6 +685,8 @@ static int align_yoffset = 0; EXTRA_SPACING_INFO(info_mode) : \ (s) == GAME_MODE_SETUP ? \ EXTRA_SPACING_SETUP(setup_mode) : \ + (s) == GAME_MODE_SCORES ? \ + EXTRA_SPACING_SCORES(info_mode) : \ menu.extra_spacing[DRAW_MODE(s)]) #define mSX (SX + DRAW_XOFFSET(game_status)) @@ -638,6 +724,7 @@ struct TitleControlInfo struct TitleControlInfo title_controls[MAX_NUM_TITLE_SCREENS]; + // main menu display and control definitions #define MAIN_CONTROL_NAME 0 @@ -865,6 +952,11 @@ static struct MainControlInfo main_controls[] = }; +static boolean hasLevelSetInfo(void) +{ + return (getLevelSetInfoFilename(0) != NULL); +} + static int getTitleScreenGraphic(int nr, boolean initial) { return (initial ? IMG_TITLESCREEN_INITIAL_1 : IMG_TITLESCREEN_1) + nr; @@ -1376,10 +1468,10 @@ static void AdjustScrollbar(int id, int items_max, int items_visible, GDI_SCROLLBAR_ITEM_POSITION, item_position, GDI_END); } -static void AdjustChooseTreeScrollbar(int id, int first_entry, TreeInfo *ti) +static void AdjustChooseTreeScrollbar(TreeInfo *ti, int id) { AdjustScrollbar(id, numTreeInfoInGroup(ti), NUM_MENU_ENTRIES_ON_SCREEN, - first_entry); + ti->cl_first); } static void clearMenuListArea(void) @@ -1393,6 +1485,11 @@ static void clearMenuListArea(void) // clear menu list area, but not title or scrollbar DrawBackground(mSX, mSY + MENU_SCREEN_START_YPOS * 32, scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32); + + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + ClearRectangle(drawto, mSX, mSY + MENU_SCREEN_START_YPOS * 32, + scrollbar_xpos - mSX, NUM_MENU_ENTRIES_ON_SCREEN * 32); } static void drawCursorExt(int xpos, int ypos, boolean active, int graphic) @@ -1461,6 +1558,15 @@ static int getChooseTreeEditYPos(int ypos_raw) return sy; } +static int getChooseTreeEditXPosReal(int pos) +{ + int xpos = getChooseTreeEditXPos(pos); + int font_nr = getChooseTreeEditFont(FALSE); + int font_xoffset = getFontDrawOffsetX(font_nr); + + return xpos + font_xoffset; +} + static void drawChooseTreeEdit(int ypos_raw, boolean active) { int sx = getChooseTreeEditXPos(POS_LEFT); @@ -1470,16 +1576,39 @@ static void drawChooseTreeEdit(int ypos_raw, boolean active) DrawText(sx, sy, STR_CHOOSE_TREE_EDIT, font_nr); } -static void DrawHeadline(void) +static void DrawInfoScreen_Headline(int screen_nr, int num_screens, + int use_global_screens) { - DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, main_text_title_1); - DrawTextSCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, main_text_title_2); + char *info_text_title_1 = getInfoScreenTitle_Generic(); + char info_text_title_2[MAX_LINE_LEN + 1]; + + if (num_screens > 1) + { + sprintf(info_text_title_2, "Page %d of %d", screen_nr + 1, num_screens); + } + else + { + char *text_format = (use_global_screens ? "for %s" : "for \"%s\""); + int text_format_len = strlen(text_format) - strlen("%s"); + int max_text_len = SXSIZE / getFontWidth(FONT_TITLE_2); + int max_name_len = max_text_len - text_format_len; + char name_cut[max_name_len]; + char *name_full = (use_global_screens ? getProgramTitleString() : + leveldir_current->name); + + snprintf(name_cut, max_name_len, "%s", name_full); + snprintf(info_text_title_2, max_text_len, text_format, name_cut); + } + + DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, info_text_title_1); + DrawTextSCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, info_text_title_2); } static void DrawTitleScreenImage(int nr, boolean initial) { int graphic = getTitleScreenGraphic(nr, initial); Bitmap *bitmap = graphic_info[graphic].bitmap; + int draw_masked = graphic_info[graphic].draw_masked; int width = graphic_info[graphic].width; int height = graphic_info[graphic].height; int src_x = graphic_info[graphic].src_x; @@ -1512,7 +1641,7 @@ static void DrawTitleScreenImage(int nr, boolean initial) ClearRectangleOnBackground(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE); - if (DrawingOnBackground(dst_x, dst_y)) + if (DrawingOnBackground(dst_x, dst_y) && draw_masked) BlitBitmapMasked(bitmap, drawto, src_x, src_y, width, height, dst_x, dst_y); else BlitBitmap(bitmap, drawto, src_x, src_y, width, height, dst_x, dst_y); @@ -1630,11 +1759,31 @@ void DrawMainMenu(void) return; } + // needed if last screen was the playing screen, invoked from hall of fame + if (score_info_tape_play) + { + CloseDoor(DOOR_CLOSE_ALL); + + SetGameStatus(GAME_MODE_SCOREINFO); + + DrawScoreInfo(scores.last_entry_nr); + + return; + } + + // reset flag to continue playing next level from hall of fame + scores.continue_playing = FALSE; + // leveldir_current may be invalid (level group, parent link, node copy) leveldir_current = getValidLevelSeries(leveldir_current, leveldir_last_valid); if (leveldir_current != leveldir_last_valid) { + // level setup config may have been loaded to "last played" tree node copy, + // but "leveldir_current" now points to the "original" level set tree node, + // in which case "handicap_level" may still default to the first level + LoadLevelSetup_SeriesInfo(); + UpdateLastPlayedLevels_TreeInfo(); levelset_has_changed = TRUE; @@ -1643,7 +1792,8 @@ void DrawMainMenu(void) // store valid level series information leveldir_last_valid = leveldir_current; - init_last = init; // switch to new busy animation + // store first level of this level set for "level_nr" style animations + SetAnimationFirstLevel(leveldir_current->first_level); // needed if last screen (level choice) changed graphics, sounds or music ReloadCustomArtwork(0); @@ -1719,6 +1869,7 @@ void DrawMainMenu(void) MapTapeButtons(); MapScreenMenuGadgets(SCREEN_MASK_MAIN); UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SOLUTION, hasSolutionTape()); + UpdateScreenMenuGadgets(SCREEN_MASK_MAIN_HAS_SET_INFO, hasLevelSetInfo()); // copy actual game door content to door double buffer for OpenDoor() BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0); @@ -1739,15 +1890,10 @@ void DrawMainMenu(void) OpenDoor(DOOR_CLOSE_1 | DOOR_OPEN_2); -#if defined(PLATFORM_EMSCRIPTEN) - EM_ASM - ( - FS.syncfs(function(err) - { - assert(!err); - }); - ); -#endif + SyncEmscriptenFilesystem(); + + // needed once after program start or after user change + CheckApiServerTasks(); } static void gotoTopLevelDir(void) @@ -1785,7 +1931,7 @@ static unsigned int getAutoDelayCounter(struct TitleFadingInfo *fi) static boolean TitleAutoDelayReached(unsigned int *counter_var, struct TitleFadingInfo *fi) { - return DelayReachedExt(counter_var, fi->auto_delay, getAutoDelayCounter(fi)); + return DelayReachedExt2(counter_var, fi->auto_delay, getAutoDelayCounter(fi)); } static void ResetTitleAutoDelay(unsigned int *counter_var, @@ -1880,7 +2026,7 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button) { return_to_main_menu = TRUE; } - else if (button == MB_MENU_CHOICE) + else if (button == MB_MENU_CHOICE || dx) { if (game_status_last_screen == GAME_MODE_INFO && num_title_screens == 0) { @@ -1893,9 +2039,10 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button) return; } - title_screen_nr++; + title_screen_nr += + (game_status_last_screen == GAME_MODE_INFO && dx < 0 ? -1 : +1); - if (title_screen_nr < num_title_screens) + if (title_screen_nr >= 0 && title_screen_nr < num_title_screens) { tci = &title_controls[title_screen_nr]; @@ -1965,6 +2112,16 @@ void HandleTitleScreen(int mx, int my, int dx, int dy, int button) } } +static void HandleMainMenu_ToggleTeamMode(void) +{ + setup.team_mode = !setup.team_mode; + + InitializeMainControls(); + DrawCursorAndText_Main(MAIN_CONTROL_NAME, TRUE, FALSE); + + DrawPreviewPlayers(); +} + static void HandleMainMenu_SelectLevel(int step, int direction, int selected_level_nr) { @@ -1985,7 +2142,7 @@ static void HandleMainMenu_SelectLevel(int step, int direction, { // skipping levels is only allowed when trying to skip single level if (setup.skip_levels && new_level_nr == old_level_nr + 1 && - Request("Level still unsolved! Skip despite handicap?", REQ_ASK)) + Request("Level still unsolved! Skip it anyway?", REQ_ASK)) { leveldir_current->handicap_level++; SaveLevelSetup_SeriesInfo(); @@ -2114,9 +2271,16 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) } else if (dx != 0) { - if (choice != MAIN_CONTROL_INFO && - choice != MAIN_CONTROL_SETUP) + if (choice == MAIN_CONTROL_NAME) + { + // special case: cursor left or right pressed -- toggle team mode + HandleMainMenu_ToggleTeamMode(); + } + else if (choice != MAIN_CONTROL_INFO && + choice != MAIN_CONTROL_SETUP) + { HandleMainMenu_SelectLevel(1, dx, NO_DIRECT_LEVEL_SELECT); + } } } else @@ -2129,12 +2293,7 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) insideTextPosRect(main_controls[i].pos_text, mx - mSX, my - mSY)) { // special case: menu text "name/team" clicked -- toggle team mode - setup.team_mode = !setup.team_mode; - - InitializeMainControls(); - DrawCursorAndText_Main(choice, TRUE, FALSE); - - DrawPreviewPlayers(); + HandleMainMenu_ToggleTeamMode(); } else { @@ -2180,7 +2339,7 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) SetGameStatus(GAME_MODE_SCORES); - DrawHallOfFame(level_nr, -1); + DrawHallOfFame(level_nr); } else if (pos == MAIN_CONTROL_EDITOR) { @@ -2225,9 +2384,13 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button) SaveLevelSetup_LastSeries(); SaveLevelSetup_SeriesInfo(); +#if defined(PLATFORM_EMSCRIPTEN) + Request("Close the browser window to quit!", REQ_CONFIRM); +#else if (!setup.ask_on_quit_program || Request("Do you really want to quit?", REQ_ASK | REQ_STAY_CLOSED)) SetGameStatus(GAME_MODE_QUIT); +#endif } } } @@ -2302,15 +2465,15 @@ static void execExitInfo(void) static struct TokenInfo info_info_main[] = { - { TYPE_ENTER_SCREEN, execInfoTitleScreen, "Title Screen" }, - { TYPE_ENTER_SCREEN, execInfoElements, "Elements Info" }, - { TYPE_ENTER_SCREEN, execInfoMusic, "Music Info" }, - { TYPE_ENTER_SCREEN, execInfoCredits, "Credits" }, - { TYPE_ENTER_SCREEN, execInfoProgram, "Program Info" }, - { TYPE_ENTER_SCREEN, execInfoVersion, "Version Info" }, - { TYPE_ENTER_SCREEN, execInfoLevelSet, "Level Set Info" }, + { TYPE_ENTER_SCREEN, execInfoTitleScreen, STR_INFO_TITLE }, + { TYPE_ENTER_SCREEN, execInfoElements, STR_INFO_ELEMENTS }, + { TYPE_ENTER_SCREEN, execInfoMusic, STR_INFO_MUSIC }, + { TYPE_ENTER_SCREEN, execInfoCredits, STR_INFO_CREDITS }, + { TYPE_ENTER_SCREEN, execInfoProgram, STR_INFO_PROGRAM }, + { TYPE_ENTER_SCREEN, execInfoVersion, STR_INFO_VERSION }, + { TYPE_ENTER_SCREEN, execInfoLevelSet, STR_INFO_LEVELSET }, { TYPE_EMPTY, NULL, "" }, - { TYPE_LEAVE_MENU, execExitInfo, "Exit" }, + { TYPE_LEAVE_MENU, execExitInfo, STR_INFO_EXIT }, { 0, NULL, NULL } }; @@ -2336,6 +2499,78 @@ static struct TokenInfo setup_info_input[]; static struct TokenInfo *menu_info; +static void PlayInfoSound(void) +{ + int info_sound = getInfoScreenBackgroundSound_Generic(); + char *next_sound = getSoundInfoEntryFilename(info_sound); + + if (next_sound != NULL) + PlayMenuSoundExt(info_sound); + else + PlayMenuSound(); +} + +static void PlayInfoSoundIfLoop(void) +{ + int info_sound = getInfoScreenBackgroundSound_Generic(); + char *next_sound = getSoundInfoEntryFilename(info_sound); + + if (next_sound != NULL) + PlayMenuSoundIfLoopExt(info_sound); + else + PlayMenuSoundIfLoop(); +} + +static void PlayInfoMusic(void) +{ + int info_music = getInfoScreenBackgroundMusic_Generic(); + char *curr_music = getCurrentlyPlayingMusicFilename(); + char *next_music = getMusicInfoEntryFilename(info_music); + + if (next_music != NULL) + { + // play music if info screen music differs from current music + if (!strEqual(curr_music, next_music)) + PlayMenuMusicExt(info_music); + } + else + { + // only needed if info screen was directly invoked from main menu + PlayMenuMusic(); + } +} + +static void PlayInfoSoundsAndMusic(void) +{ + PlayInfoSound(); + PlayInfoMusic(); +} + +static void FadeInfoSounds(void) +{ + FadeSounds(); +} + +static void FadeInfoMusic(void) +{ + int info_music = getInfoScreenBackgroundMusic_Generic(); + char *curr_music = getCurrentlyPlayingMusicFilename(); + char *next_music = getMusicInfoEntryFilename(info_music); + + if (next_music != NULL) + { + // fade music if info screen music differs from current music + if (!strEqual(curr_music, next_music)) + FadeMusic(); + } +} + +static void FadeInfoSoundsAndMusic(void) +{ + FadeInfoSounds(); + FadeInfoMusic(); +} + static void DrawCursorAndText_Menu_Ext(struct TokenInfo *token_info, int screen_pos, int menu_info_pos_raw, boolean active) @@ -2440,6 +2675,18 @@ static void DrawInfoScreen_Main(void) int fade_mask = REDRAW_FIELD; int i; + // (needed after displaying info sub-screens directly from main menu) + if (info_screens_from_main) + { + info_screens_from_main = FALSE; + + SetGameStatus(GAME_MODE_MAIN); + + DrawMainMenu(); + + return; + } + if (redraw_mask & REDRAW_ALL) fade_mask = REDRAW_ALL; @@ -2468,10 +2715,13 @@ static void DrawInfoScreen_Main(void) OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); - DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, "Info Screen"); + DrawTextSCentered(mSY - SY + 16, FONT_TITLE_1, STR_INFO_MAIN); info_info = info_info_main; + // use modified info screen info without info screen entries marked as hidden + info_info = getSetupInfoFinal(info_info); + // determine maximal number of info entries that can be displayed on screen num_info_info = 0; for (i = 0; info_info[i].type != 0 && i < NUM_MENU_ENTRIES_ON_SCREEN; i++) @@ -2834,15 +3084,23 @@ static int getMenuTextStep(int spacing_height, int font_nr) return getFontHeight(font_nr) + getMenuTextSpacing(spacing_height, font_nr); } +static int getHeadlineSpacing(void) +{ + // special compatibility handling for "R'n'D jue 2022" game editions + int spacing_check = menu.headline1_spacing[GAME_MODE_SCOREINFO]; + int spacing = (game_status == GAME_MODE_SCOREINFO ? + menu.headline1_spacing[GAME_MODE_SCOREINFO] : + menu.headline1_spacing_info[info_mode]); + int font = MENU_INFO_FONT_TITLE; + + return (spacing_check != -2 ? getMenuTextStep(spacing, font) : 0); +} + void DrawInfoScreen_NotAvailable(char *text_title, char *text_error) { - int font_title = MENU_INFO_FONT_TITLE; int font_error = FONT_TEXT_2; int font_foot = MENU_INFO_FONT_FOOT; - int spacing_title = menu.headline1_spacing_info[info_mode]; - int ystep_title = getMenuTextStep(spacing_title, font_title); - int ystart1 = mSY - SY + MENU_SCREEN_INFO_YSTART1; - int ystart2 = ystart1 + ystep_title; + int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing(); int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO); @@ -2850,28 +3108,27 @@ void DrawInfoScreen_NotAvailable(char *text_title, char *text_error) FadeOut(REDRAW_FIELD); ClearField(); - DrawHeadline(); - DrawTextSCentered(ystart1, font_title, text_title); - DrawTextSCentered(ystart2, font_error, text_error); + DrawInfoScreen_Headline(0, 1, FALSE); - DrawTextSCentered(ybottom, font_foot, - "Press any key or button for info menu"); + DrawTextSCentered(ystart, font_error, text_error); + DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU); FadeIn(REDRAW_FIELD); } void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init) { - static int infoscreen_step[MAX_INFO_ELEMENTS_ON_SCREEN]; - static int infoscreen_frame[MAX_INFO_ELEMENTS_ON_SCREEN]; - int font_title = MENU_INFO_FONT_TITLE; - int font_foot = MENU_INFO_FONT_FOOT; - int xstart = mSX + MENU_SCREEN_INFO_SPACE_LEFT; - int ystart1 = mSY - SY + MENU_SCREEN_INFO_YSTART1; - int ystart2 = mSY + MENU_SCREEN_INFO_YSTART2; + static int infoscreen_step[MAX_INFO_ELEMENTS_IN_ARRAY]; + static int infoscreen_frame[MAX_INFO_ELEMENTS_IN_ARRAY]; + int font_foot = MENU_INFO_FONT_FOOT; + int xstart = mSX + MENU_SCREEN_INFO_SPACE_LEFT; + int ystart = mSY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing(); int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; int ystep = MENU_SCREEN_INFO_YSTEP; + int row_height = MENU_SCREEN_INFO_ENTRY_SIZE; + int tilesize = MENU_SCREEN_INFO_TILE_SIZE; + int yoffset = (row_height - tilesize) / 2; int element, action, direction; int graphic; int delay; @@ -2883,13 +3140,7 @@ void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init) for (i = 0; i < NUM_INFO_ELEMENTS_ON_SCREEN; i++) infoscreen_step[i] = infoscreen_frame[i] = 0; - ClearField(); - DrawHeadline(); - - DrawTextSCentered(ystart1, font_title, "The Game Elements:"); - - DrawTextSCentered(ybottom, font_foot, - "Press any key or button for next page"); + DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_PAGE); FrameCounter = 0; } @@ -2911,7 +3162,10 @@ void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init) continue; } - j += infoscreen_step[i - start]; + int ypos = i - start; + int ystart_pos = ystart + ypos * ystep + yoffset; + + j += infoscreen_step[ypos]; element = helpanim_info[j].element; action = helpanim_info[j].action; @@ -2934,39 +3188,38 @@ void DrawInfoScreen_HelpAnim(int start, int max_anims, boolean init) if (delay == -1) delay = 1000000; - if (infoscreen_frame[i - start] == 0) + if (infoscreen_frame[ypos] == 0) { sync_frame = 0; - infoscreen_frame[i - start] = delay - 1; + infoscreen_frame[ypos] = delay - 1; } else { - sync_frame = delay - infoscreen_frame[i - start]; - infoscreen_frame[i - start]--; + sync_frame = delay - infoscreen_frame[ypos]; + infoscreen_frame[ypos]--; } if (helpanim_info[j].element == HELPANIM_LIST_NEXT) { - if (!infoscreen_frame[i - start]) - infoscreen_step[i - start] = 0; + if (!infoscreen_frame[ypos]) + infoscreen_step[ypos] = 0; } else { - if (!infoscreen_frame[i - start]) - infoscreen_step[i - start]++; + if (!infoscreen_frame[ypos]) + infoscreen_step[ypos]++; while (helpanim_info[j].element != HELPANIM_LIST_NEXT) j++; } j++; - ClearRectangleOnBackground(drawto, xstart, ystart2 + (i - start) * ystep, - TILEX, TILEY); - DrawFixedGraphicAnimationExt(drawto, xstart, ystart2 + (i - start) * ystep, - graphic, sync_frame, USE_MASKING); + ClearRectangleOnBackground(drawto, xstart, ystart_pos, tilesize, tilesize); + DrawSizedGraphicAnimationExt(drawto, xstart, ystart_pos, + graphic, sync_frame, tilesize, USE_MASKING); if (init) - DrawInfoScreen_HelpText(element, action, direction, i - start); + DrawInfoScreen_HelpText(element, action, direction, ypos); i++; } @@ -2996,15 +3249,24 @@ void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos) int font_nr = FONT_INFO_ELEMENTS; int font_width = getFontWidth(font_nr); int font_height = getFontHeight(font_nr); - int yoffset = (TILEX - 2 * font_height) / 2; - int xstart = mSX + MENU_SCREEN_INFO_SPACE_LEFT + TILEX + MINI_TILEX; - int ystart = mSY + MENU_SCREEN_INFO_YSTART2 + yoffset; - int ystep = TILEY + 4; + int line_spacing = MENU_SCREEN_INFO_SPACE_LINE; + int left_spacing = MENU_SCREEN_INFO_SPACE_LEFT; + int middle_spacing = MENU_SCREEN_INFO_SPACE_MIDDLE; + int right_spacing = MENU_SCREEN_INFO_SPACE_RIGHT; + int line_height = font_height + line_spacing; + int row_height = MENU_SCREEN_INFO_ENTRY_SIZE; + int tilesize = MENU_SCREEN_INFO_TILE_SIZE; + int xstart = mSX + left_spacing + tilesize + middle_spacing; + int ystart = mSY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing(); + int ystep = MENU_SCREEN_INFO_YSTEP; int pad_left = xstart - SX; - int pad_right = MENU_SCREEN_INFO_SPACE_RIGHT; + int pad_right = right_spacing; int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width; - int max_lines_per_text = 2; + int max_lines_per_text = (row_height + line_spacing) / line_height; char *text = NULL; + boolean autowrap = TRUE; + boolean centered = FALSE; + boolean parse_comments = FALSE; if (action != -1 && direction != -1) // element.action.direction text = getHelpText(element, action, direction); @@ -3021,49 +3283,68 @@ void DrawInfoScreen_HelpText(int element, int action, int direction, int ypos) if (text == NULL) // not found text = "No description available"; - if (strlen(text) <= max_chars_per_line) // only one line of text - ystart += getFontHeight(font_nr) / 2; + DisableDrawingText(); + + // first get number of text lines to calculate offset for centering text + int num_lines_printed = + DrawTextBuffer(0, 0, text, font_nr, + max_chars_per_line, -1, max_lines_per_text, line_spacing, -1, + autowrap, centered, parse_comments); + + EnableDrawingText(); + + int size_lines_printed = num_lines_printed * line_height - line_spacing; + int yoffset = (row_height - size_lines_printed) / 2; - DrawTextBuffer(xstart, ystart + ypos * ystep, text, font_nr, - max_chars_per_line, -1, max_lines_per_text, 0, -1, - TRUE, FALSE, FALSE); + DrawTextBuffer(xstart, ystart + ypos * ystep + yoffset, text, font_nr, + max_chars_per_line, -1, max_lines_per_text, line_spacing, -1, + autowrap, centered, parse_comments); } static void DrawInfoScreen_TitleScreen(void) { SetGameStatus(GAME_MODE_TITLE); + UnmapAllGadgets(); + DrawTitleScreen(); } -void HandleInfoScreen_TitleScreen(int button) +void HandleInfoScreen_TitleScreen(int dx, int dy, int button) { - HandleTitleScreen(0, 0, 0, 0, button); + HandleTitleScreen(0, 0, dx, dy, button); } static void DrawInfoScreen_Elements(void) { SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_ELEMENTS); + UnmapAllGadgets(); + FadeInfoSoundsAndMusic(); + FadeOut(REDRAW_FIELD); LoadHelpAnimInfo(); LoadHelpTextInfo(); - HandleInfoScreen_Elements(MB_MENU_INITIALIZE); + HandleInfoScreen_Elements(0, 0, MB_MENU_INITIALIZE); + + PlayInfoSoundsAndMusic(); FadeIn(REDRAW_FIELD); } -void HandleInfoScreen_Elements(int button) +void HandleInfoScreen_Elements(int dx, int dy, int button) { - static unsigned int info_delay = 0; + static DelayCounter info_delay = { 0 }; static int num_anims; static int num_pages; static int page; int anims_per_page = NUM_INFO_ELEMENTS_ON_SCREEN; int i; + info_delay.value = GameFrameDelay; + if (button == MB_MENU_INITIALIZE) { boolean new_element = TRUE; @@ -3094,18 +3375,18 @@ void HandleInfoScreen_Elements(int button) return; } - else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE) + else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx) { if (button != MB_MENU_INITIALIZE) { PlaySound(SND_MENU_ITEM_SELECTING); - page++; + page += (dx < 0 ? -1 : +1); } - if (page >= num_pages) + if (page < 0 || page >= num_pages) { - FadeMenuSoundsAndMusic(); + FadeInfoSoundsAndMusic(); info_mode = INFO_MODE_MAIN; DrawInfoScreen(); @@ -3113,12 +3394,15 @@ void HandleInfoScreen_Elements(int button) return; } - if (page > 0) + if (button != MB_MENU_INITIALIZE) FadeSetNextScreen(); if (button != MB_MENU_INITIALIZE) FadeOut(REDRAW_FIELD); + ClearField(); + + DrawInfoScreen_Headline(page, num_pages, TRUE); DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, TRUE); if (button != MB_MENU_INITIALIZE) @@ -3126,11 +3410,11 @@ void HandleInfoScreen_Elements(int button) } else { - if (DelayReached(&info_delay, GameFrameDelay)) + if (DelayReached(&info_delay)) if (page < num_pages) DrawInfoScreen_HelpAnim(page * anims_per_page, num_anims, FALSE); - PlayMenuSoundIfLoop(); + PlayInfoSoundIfLoop(); } } @@ -3138,34 +3422,48 @@ static void DrawInfoScreen_Music(void) { SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_MUSIC); + UnmapAllGadgets(); + FadeOut(REDRAW_FIELD); ClearField(); - DrawHeadline(); + + DrawInfoScreen_Headline(0, 1, TRUE); LoadMusicInfo(); - HandleInfoScreen_Music(MB_MENU_INITIALIZE); + HandleInfoScreen_Music(0, 0, MB_MENU_INITIALIZE); FadeIn(REDRAW_FIELD); } -void HandleInfoScreen_Music(int button) +void HandleInfoScreen_Music(int dx, int dy, int button) { static struct MusicFileInfo *list = NULL; + static int num_screens = 0; + static int screen_nr = 0; int font_title = MENU_INFO_FONT_TITLE; int font_head = MENU_INFO_FONT_HEAD; int font_text = MENU_INFO_FONT_TEXT; int font_foot = MENU_INFO_FONT_FOOT; - int spacing_title = menu.headline1_spacing_info[info_mode]; - int spacing_head = menu.headline2_spacing_info[info_mode]; - int ystep_title = getMenuTextStep(spacing_title, font_title); - int ystep_head = getMenuTextStep(spacing_head, font_head); - int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; + int spacing_head = menu.headline2_spacing_info[info_mode]; + int ystep_head = getMenuTextStep(spacing_head, font_head); + int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART; int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; if (button == MB_MENU_INITIALIZE) { + struct MusicFileInfo *list_ptr = music_file_info; + + num_screens = 0; + screen_nr = 0; + + while (list_ptr != NULL) + { + list_ptr = list_ptr->next; + num_screens++; + } + list = music_file_info; if (list == NULL) @@ -3173,13 +3471,11 @@ void HandleInfoScreen_Music(int button) FadeMenuSoundsAndMusic(); ClearField(); - DrawHeadline(); - DrawTextSCentered(ystart, font_title, - "No music info for this level set."); + DrawInfoScreen_Headline(0, 1, TRUE); - DrawTextSCentered(ybottom, font_foot, - "Press any key or button for info menu"); + DrawTextSCentered(ystart, font_title, "No music info for this level set."); + DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU); return; } @@ -3196,14 +3492,17 @@ void HandleInfoScreen_Music(int button) return; } - else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE) + else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE || dx) { if (button != MB_MENU_INITIALIZE) { PlaySound(SND_MENU_ITEM_SELECTING); if (list != NULL) - list = list->next; + { + list = (dx < 0 ? list->prev : list->next); + screen_nr += (dx < 0 ? -1 : +1); + } } if (list == NULL) @@ -3225,40 +3524,36 @@ void HandleInfoScreen_Music(int button) FadeOut(REDRAW_FIELD); ClearField(); - DrawHeadline(); + + DrawInfoScreen_Headline(screen_nr, num_screens, TRUE); if (list->is_sound) { int sound = list->music; - if (sound_info[sound].loop) + if (IS_LOOP_SOUND(sound)) PlaySoundLoop(sound); else PlaySound(sound); - - DrawTextSCentered(ystart, font_title, "The Game Background Sounds:"); } else { int music = list->music; - if (music_info[music].loop) + if (IS_LOOP_MUSIC(music)) PlayMusicLoop(music); else PlayMusic(music); - - DrawTextSCentered(ystart, font_title, "The Game Background Music:"); } - ystart += ystep_title; - if (!strEqual(list->title, UNKNOWN_NAME)) { if (!strEqual(list->title_header, UNKNOWN_NAME)) - { DrawTextSCentered(ystart, font_head, list->title_header); - ystart += ystep_head; - } + else + DrawTextSCentered(ystart, font_head, "Track"); + + ystart += ystep_head; DrawTextFCentered(ystart, font_text, "\"%s\"", list->title); ystart += ystep_head; @@ -3303,469 +3598,125 @@ void HandleInfoScreen_Music(int button) ystart += ystep_head; } - DrawTextSCentered(ybottom, FONT_TEXT_4, - "Press any key or button for next page"); + if (!strEqual(list->played, UNKNOWN_NAME)) + { + if (!strEqual(list->played_header, UNKNOWN_NAME)) + DrawTextSCentered(ystart, font_head, list->played_header); + else + DrawTextSCentered(ystart, font_head, "played in"); + + ystart += ystep_head; + + DrawTextFCentered(ystart, font_text, "%s", list->played); + ystart += ystep_head; + } + else if (!list->is_sound) + { + int music_level_nr = -1; + int i; + + // check if this music is configured for a certain level + for (i = leveldir_current->first_level; + i <= leveldir_current->last_level; i++) + { + // (special case: "list->music" may be negative for unconfigured music) + if (levelset.music[i] != MUS_UNDEFINED && + levelset.music[i] == list->music) + { + music_level_nr = i; + + break; + } + } + + if (music_level_nr != -1) + { + if (!strEqual(list->played_header, UNKNOWN_NAME)) + DrawTextSCentered(ystart, font_head, list->played_header); + else + DrawTextSCentered(ystart, font_head, "played in"); + + ystart += ystep_head; + + DrawTextFCentered(ystart, font_text, "level %03d", music_level_nr); + ystart += ystep_head; + } + } + + DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_PAGE); if (button != MB_MENU_INITIALIZE) FadeIn(REDRAW_FIELD); } - if (list != NULL && list->is_sound && sound_info[list->music].loop) + if (list != NULL && list->is_sound && IS_LOOP_SOUND(list->music)) PlaySoundLoop(list->music); } -static void DrawInfoScreen_CreditsScreen(int screen_nr) +static void DrawInfoScreen_Version(void) { - int font_title = MENU_INFO_FONT_TITLE; - int font_head = MENU_INFO_FONT_HEAD; - int font_text = MENU_INFO_FONT_TEXT; - int font_foot = MENU_INFO_FONT_FOOT; - int spacing_title = menu.headline1_spacing_info[info_mode]; - int spacing_head = menu.headline2_spacing_info[info_mode]; - int spacing_para = menu.paragraph_spacing_info[info_mode]; - int spacing_line = menu.line_spacing_info[info_mode]; - int ystep_title = getMenuTextStep(spacing_title, font_title); - int ystep_head = getMenuTextStep(spacing_head, font_head); - int ystep_para = getMenuTextStep(spacing_para, font_text); - int ystep_line = getMenuTextStep(spacing_line, font_text); - int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; + int font_head = MENU_INFO_FONT_HEAD; + int font_text = MENU_INFO_FONT_TEXT; + int font_foot = MENU_INFO_FONT_FOOT; + int spacing_head = menu.headline2_spacing_info[info_mode]; + int spacing_para = menu.paragraph_spacing_info[info_mode]; + int spacing_line = menu.line_spacing_info[info_mode]; + int xstep = getFontWidth(font_text); + int ystep_head = getMenuTextStep(spacing_head, font_head); + int ystep_para = getMenuTextStep(spacing_para, font_text); + int ystep_line = getMenuTextStep(spacing_line, font_text); + int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing(); int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; + int xstart1 = mSX - SX + 2 * xstep; + int xstart2 = mSX - SX + 18 * xstep; + int xstart3 = mSX - SX + 28 * xstep; + SDL_version sdl_version_compiled; + const SDL_version *sdl_version_linked; + int driver_name_len = 10; + SDL_version sdl_version_linked_ext; + const char *driver_name = NULL; - ClearField(); - DrawHeadline(); - - DrawTextSCentered(ystart, font_title, "Credits:"); - ystart += ystep_title; - - if (screen_nr == 0) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Peter Liepa"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Boulder Dash\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1984"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "First Star Software"); - } - else if (screen_nr == 1) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Klaus Heinz & Volker Wertich"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Emerald Mine\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1987"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Kingsoft"); - } - else if (screen_nr == 2) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Michael Stopp & Philip Jespersen"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Supaplex\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1991"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Digital Integration"); - } - else if (screen_nr == 3) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Hiroyuki Imabayashi"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for creating"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "\"Sokoban\""); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "in the year"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "1982"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "published by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Thinking Rabbit"); - } - else if (screen_nr == 4) - { - DrawTextSCentered(ystart, font_head, - "Special thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Alan Bond"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "and"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "J\xfcrgen Bonhagen"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for the continuous creation"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_head, - "of outstanding level sets"); - } - else if (screen_nr == 5) - { - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Peter Elzner"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for ideas and inspiration by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Diamond Caves"); - ystart += ystep_para; - - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Steffest"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for ideas and inspiration by"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "DX-Boulderdash"); - } - else if (screen_nr == 6) - { - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "David Tritscher"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for the code base used for the"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_head, - "native Emerald Mine engine"); - } - else if (screen_nr == 7) - { - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Guido Schulz"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for the initial DOS port"); - ystart += ystep_para; - - DrawTextSCentered(ystart, font_head, - "Thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "Karl H\xf6rnell"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "for some additional toons"); - } - else if (screen_nr == 8) - { - DrawTextSCentered(ystart, font_head, - "And not to forget:"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "Many thanks to"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - "All those who contributed"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_text, - "levels to this game"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_text, - "since 1995"); - } - - DrawTextSCentered(ybottom, font_foot, - "Press any key or button for next page"); -} - -static void DrawInfoScreen_Credits(void) -{ - SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_CREDITS); + SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_VERSION); - FadeMenuSoundsAndMusic(); + UnmapAllGadgets(); + FadeInfoSoundsAndMusic(); FadeOut(REDRAW_FIELD); - HandleInfoScreen_Credits(MB_MENU_INITIALIZE); + ClearField(); - FadeIn(REDRAW_FIELD); -} + DrawInfoScreen_Headline(0, 1, TRUE); -void HandleInfoScreen_Credits(int button) -{ - static int screen_nr = 0; - int num_screens = 9; + DrawTextF(xstart1, ystart, font_head, "Name"); + DrawTextF(xstart2, ystart, font_text, getProgramTitleString()); + ystart += ystep_line; - if (button == MB_MENU_INITIALIZE) + if (!strEqual(getProgramVersionString(), getProgramRealVersionString())) { - screen_nr = 0; + DrawTextF(xstart1, ystart, font_head, "Version (fake)"); + DrawTextF(xstart2, ystart, font_text, getProgramVersionString()); + ystart += ystep_line; - // DrawInfoScreen_CreditsScreen(screen_nr); + DrawTextF(xstart1, ystart, font_head, "Version (real)"); + DrawTextF(xstart2, ystart, font_text, getProgramRealVersionString()); + ystart += ystep_line; } - - if (button == MB_MENU_LEAVE) + else { - PlaySound(SND_MENU_ITEM_SELECTING); - - info_mode = INFO_MODE_MAIN; - DrawInfoScreen(); - - return; + DrawTextF(xstart1, ystart, font_head, "Version"); + DrawTextF(xstart2, ystart, font_text, getProgramVersionString()); + ystart += ystep_line; } - else if (button == MB_MENU_CHOICE || button == MB_MENU_INITIALIZE) - { - if (button != MB_MENU_INITIALIZE) - { - PlaySound(SND_MENU_ITEM_SELECTING); - screen_nr++; - } + DrawTextF(xstart1, ystart, font_head, "Platform"); + DrawTextF(xstart2, ystart, font_text, "%s (%s)", + PLATFORM_STRING, + PLATFORM_XX_BIT_STRING); + ystart += ystep_line; - if (screen_nr >= num_screens) - { - FadeMenuSoundsAndMusic(); - - info_mode = INFO_MODE_MAIN; - DrawInfoScreen(); - - return; - } - - if (screen_nr > 0) - FadeSetNextScreen(); - - if (button != MB_MENU_INITIALIZE) - FadeOut(REDRAW_FIELD); - - DrawInfoScreen_CreditsScreen(screen_nr); - - if (button != MB_MENU_INITIALIZE) - FadeIn(REDRAW_FIELD); - } - else - { - PlayMenuSoundIfLoop(); - } -} - -static void DrawInfoScreen_Program(void) -{ - int font_title = MENU_INFO_FONT_TITLE; - int font_head = MENU_INFO_FONT_HEAD; - int font_text = MENU_INFO_FONT_TEXT; - int font_foot = MENU_INFO_FONT_FOOT; - int spacing_title = menu.headline1_spacing_info[info_mode]; - int spacing_head = menu.headline2_spacing_info[info_mode]; - int spacing_para = menu.paragraph_spacing_info[info_mode]; - int spacing_line = menu.line_spacing_info[info_mode]; - int ystep_title = getMenuTextStep(spacing_title, font_title); - int ystep_head = getMenuTextStep(spacing_head, font_head); - int ystep_para = getMenuTextStep(spacing_para, font_text); - int ystep_line = getMenuTextStep(spacing_line, font_text); - int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; - int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; - - SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_PROGRAM); - - FadeOut(REDRAW_FIELD); - - ClearField(); - DrawHeadline(); - - DrawTextSCentered(ystart, font_title, "Program Information:"); - ystart += ystep_title; - - DrawTextSCentered(ystart, font_head, - "This game is Freeware!"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - "If you like it, send e-mail to:"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - setup.internal.program_email); - ystart += ystep_para; - - DrawTextSCentered(ystart, font_head, - "More information and levels:"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_text, - setup.internal.program_website); - ystart += ystep_para; - - DrawTextSCentered(ystart, font_head, - "If you have created new levels,"); - ystart += ystep_line; - DrawTextSCentered(ystart, font_head, - "send them to me to include them!"); - ystart += ystep_head; - DrawTextSCentered(ystart, font_head, - ":-)"); - - DrawTextSCentered(ybottom, font_foot, - "Press any key or button for info menu"); - - FadeIn(REDRAW_FIELD); -} - -void HandleInfoScreen_Program(int button) -{ - if (button == MB_MENU_LEAVE) - { - PlaySound(SND_MENU_ITEM_SELECTING); - - info_mode = INFO_MODE_MAIN; - DrawInfoScreen(); - - return; - } - else if (button == MB_MENU_CHOICE) - { - PlaySound(SND_MENU_ITEM_SELECTING); - - FadeMenuSoundsAndMusic(); - - info_mode = INFO_MODE_MAIN; - DrawInfoScreen(); - } - else - { - PlayMenuSoundIfLoop(); - } -} - -static void DrawInfoScreen_Version(void) -{ - int font_title = MENU_INFO_FONT_TITLE; - int font_head = MENU_INFO_FONT_HEAD; - int font_text = MENU_INFO_FONT_TEXT; - int font_foot = MENU_INFO_FONT_FOOT; - int spacing_title = menu.headline1_spacing_info[info_mode]; - int spacing_head = menu.headline2_spacing_info[info_mode]; - int spacing_para = menu.paragraph_spacing_info[info_mode]; - int spacing_line = menu.line_spacing_info[info_mode]; - int xstep = getFontWidth(font_text); - int ystep_title = getMenuTextStep(spacing_title, font_title); - int ystep_head = getMenuTextStep(spacing_head, font_head); - int ystep_para = getMenuTextStep(spacing_para, font_text); - int ystep_line = getMenuTextStep(spacing_line, font_text); - int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; - int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; - int xstart1 = mSX - SX + 2 * xstep; - int xstart2 = mSX - SX + 18 * xstep; - int xstart3 = mSX - SX + 28 * xstep; - SDL_version sdl_version_compiled; - const SDL_version *sdl_version_linked; - int driver_name_len = 10; - SDL_version sdl_version_linked_ext; - const char *driver_name = NULL; - - SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_VERSION); - - FadeOut(REDRAW_FIELD); - - ClearField(); - DrawHeadline(); - - DrawTextSCentered(ystart, font_title, "Version Information:"); - ystart += ystep_title; - - DrawTextF(xstart1, ystart, font_head, "Name"); - DrawTextF(xstart2, ystart, font_text, getProgramTitleString()); - ystart += ystep_line; - - if (!strEqual(getProgramVersionString(), getProgramRealVersionString())) - { - DrawTextF(xstart1, ystart, font_head, "Version (fake)"); - DrawTextF(xstart2, ystart, font_text, getProgramVersionString()); - ystart += ystep_line; - - DrawTextF(xstart1, ystart, font_head, "Version (real)"); - DrawTextF(xstart2, ystart, font_text, getProgramRealVersionString()); - ystart += ystep_line; - } - else - { - DrawTextF(xstart1, ystart, font_head, "Version"); - DrawTextF(xstart2, ystart, font_text, getProgramVersionString()); - ystart += ystep_line; - } - - DrawTextF(xstart1, ystart, font_head, "Platform"); - DrawTextF(xstart2, ystart, font_text, "%s (%s)", - PLATFORM_STRING, - PLATFORM_XX_BIT_STRING); - ystart += ystep_line; - - DrawTextF(xstart1, ystart, font_head, "Target"); - DrawTextF(xstart2, ystart, font_text, TARGET_STRING); - ystart += ystep_line; + DrawTextF(xstart1, ystart, font_head, "Target"); + DrawTextF(xstart2, ystart, font_text, TARGET_STRING); + ystart += ystep_line; DrawTextF(xstart1, ystart, font_head, "Source date"); DrawTextF(xstart2, ystart, font_text, getSourceDateString()); @@ -3865,8 +3816,9 @@ static void DrawInfoScreen_Version(void) DrawTextF(xstart2, ystart, font_text, "%s", setup.system.sdl_audiodriver); DrawTextF(xstart3, ystart, font_text, "%s", driver_name); - DrawTextSCentered(ybottom, font_foot, - "Press any key or button for info menu"); + DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU); + + PlayInfoSoundsAndMusic(); FadeIn(REDRAW_FIELD); } @@ -3897,91 +3849,253 @@ void HandleInfoScreen_Version(int button) } } -static void DrawInfoScreen_LevelSet(void) +static char *getInfoScreenTitle_Generic(void) +{ + return (info_mode == INFO_MODE_MAIN ? STR_INFO_MAIN : + info_mode == INFO_MODE_TITLE ? STR_INFO_TITLE : + info_mode == INFO_MODE_ELEMENTS ? STR_INFO_ELEMENTS : + info_mode == INFO_MODE_MUSIC ? STR_INFO_MUSIC : + info_mode == INFO_MODE_CREDITS ? STR_INFO_CREDITS : + info_mode == INFO_MODE_PROGRAM ? STR_INFO_PROGRAM : + info_mode == INFO_MODE_VERSION ? STR_INFO_VERSION : + info_mode == INFO_MODE_LEVELSET ? STR_INFO_LEVELSET : + ""); +} + +static int getInfoScreenBackgroundImage_Generic(void) { - struct TitleMessageInfo *tmi = &readme; - char *filename = getLevelSetInfoFilename(); - char *title = "Level Set Information:"; - int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART1; + return (info_mode == INFO_MODE_ELEMENTS ? IMG_BACKGROUND_INFO_ELEMENTS : + info_mode == INFO_MODE_MUSIC ? IMG_BACKGROUND_INFO_MUSIC : + info_mode == INFO_MODE_CREDITS ? IMG_BACKGROUND_INFO_CREDITS : + info_mode == INFO_MODE_PROGRAM ? IMG_BACKGROUND_INFO_PROGRAM : + info_mode == INFO_MODE_VERSION ? IMG_BACKGROUND_INFO_VERSION : + info_mode == INFO_MODE_LEVELSET ? IMG_BACKGROUND_INFO_LEVELSET : + IMG_BACKGROUND_INFO); +} + +static int getInfoScreenBackgroundSound_Generic(void) +{ + return (info_mode == INFO_MODE_ELEMENTS ? SND_BACKGROUND_INFO_ELEMENTS : + info_mode == INFO_MODE_CREDITS ? SND_BACKGROUND_INFO_CREDITS : + info_mode == INFO_MODE_PROGRAM ? SND_BACKGROUND_INFO_PROGRAM : + info_mode == INFO_MODE_VERSION ? SND_BACKGROUND_INFO_VERSION : + info_mode == INFO_MODE_LEVELSET ? SND_BACKGROUND_INFO_LEVELSET : + SND_BACKGROUND_INFO); +} + +static int getInfoScreenBackgroundMusic_Generic(void) +{ + return (info_mode == INFO_MODE_ELEMENTS ? MUS_BACKGROUND_INFO_ELEMENTS : + info_mode == INFO_MODE_CREDITS ? MUS_BACKGROUND_INFO_CREDITS : + info_mode == INFO_MODE_PROGRAM ? MUS_BACKGROUND_INFO_PROGRAM : + info_mode == INFO_MODE_VERSION ? MUS_BACKGROUND_INFO_VERSION : + info_mode == INFO_MODE_LEVELSET ? MUS_BACKGROUND_INFO_LEVELSET : + MUS_BACKGROUND_INFO); +} + +static char *getInfoScreenFilename_Generic(int nr, boolean global) +{ + return (info_mode == INFO_MODE_CREDITS ? getCreditsFilename(nr, global) : + info_mode == INFO_MODE_PROGRAM ? getProgramInfoFilename(nr) : + info_mode == INFO_MODE_LEVELSET ? getLevelSetInfoFilename(nr) : + NULL); +} + +static void DrawInfoScreen_GenericScreen(int screen_nr, int num_screens, + int use_global_screens) +{ + char *filename = getInfoScreenFilename_Generic(screen_nr, use_global_screens); + int font_text = MENU_INFO_FONT_TEXT; + int font_foot = MENU_INFO_FONT_FOOT; + int spacing_line = menu.line_spacing_info[info_mode]; int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; - if (filename == NULL) + ClearField(); + + DrawInfoScreen_Headline(screen_nr, num_screens, use_global_screens); + + if (info_mode == INFO_MODE_CREDITS || + info_mode == INFO_MODE_PROGRAM) { - DrawInfoScreen_NotAvailable(title, "No information for this level set."); + int width = SXSIZE; + int height = MENU_SCREEN_INFO_YBOTTOM - MENU_SCREEN_INFO_YSTART; + int chars = width / getFontWidth(font_text); + int lines = height / getFontHeight(font_text); + int padx = (width - chars * getFontWidth(font_text)) / 2; + int line_spacing = getMenuTextSpacing(spacing_line, font_text); + int xstart = mSX + padx; + int ystart = mSY + MENU_SCREEN_INFO_YSTART + getHeadlineSpacing(); + boolean autowrap = FALSE; + boolean centered = TRUE; + boolean parse_comments = TRUE; - return; + DrawTextFile(xstart, ystart, + filename, font_text, chars, -1, lines, line_spacing, -1, + autowrap, centered, parse_comments); } + else if (info_mode == INFO_MODE_LEVELSET) + { + struct TitleMessageInfo *tmi = &readme; - SetMainBackgroundImageIfDefined(IMG_BACKGROUND_INFO_LEVELSET); + // if x position set to "-1", automatically determine by playfield width + if (tmi->x == -1) + tmi->x = SXSIZE / 2; - FadeOut(REDRAW_FIELD); + // if y position set to "-1", use static default value + if (tmi->y == -1) + tmi->y = MENU_SCREEN_INFO_YSTART + getHeadlineSpacing(); - ClearField(); - DrawHeadline(); + // if width set to "-1", automatically determine by playfield width + if (tmi->width == -1) + tmi->width = SXSIZE - 2 * TILEX; - DrawTextSCentered(ystart, FONT_TEXT_1, title); + // if height set to "-1", automatically determine by playfield height + if (tmi->height == -1) + tmi->height = MENU_SCREEN_INFO_YBOTTOM - tmi->y - 10; - // if x position set to "-1", automatically determine by playfield width - if (tmi->x == -1) - tmi->x = SXSIZE / 2; + // if chars set to "-1", automatically determine by text and font width + if (tmi->chars == -1) + tmi->chars = tmi->width / getFontWidth(tmi->font); + else + tmi->width = tmi->chars * getFontWidth(tmi->font); - // if y position set to "-1", use static default value - if (tmi->y == -1) - tmi->y = 150; + // if lines set to "-1", automatically determine by text and font height + if (tmi->lines == -1) + tmi->lines = tmi->height / getFontHeight(tmi->font); + else + tmi->height = tmi->lines * getFontHeight(tmi->font); - // if width set to "-1", automatically determine by playfield width - if (tmi->width == -1) - tmi->width = SXSIZE - 2 * TILEX; + DrawTextFile(mSX + ALIGNED_TEXT_XPOS(tmi), mSY + ALIGNED_TEXT_YPOS(tmi), + filename, tmi->font, tmi->chars, -1, tmi->lines, 0, -1, + tmi->autowrap, tmi->centered, tmi->parse_comments); + } - // if height set to "-1", automatically determine by playfield height - if (tmi->height == -1) - tmi->height = MENU_SCREEN_INFO_YBOTTOM - tmi->y - 10; + boolean last_screen = (screen_nr == num_screens - 1); + char *text_foot = (last_screen ? TEXT_NEXT_MENU : TEXT_NEXT_PAGE); - // if chars set to "-1", automatically determine by text and font width - if (tmi->chars == -1) - tmi->chars = tmi->width / getFontWidth(tmi->font); - else - tmi->width = tmi->chars * getFontWidth(tmi->font); + DrawTextSCentered(ybottom, font_foot, text_foot); +} - // if lines set to "-1", automatically determine by text and font height - if (tmi->lines == -1) - tmi->lines = tmi->height / getFontHeight(tmi->font); - else - tmi->height = tmi->lines * getFontHeight(tmi->font); +static void DrawInfoScreen_Generic(void) +{ + SetMainBackgroundImageIfDefined(getInfoScreenBackgroundImage_Generic()); - DrawTextFile(mSX + ALIGNED_TEXT_XPOS(tmi), mSY + ALIGNED_TEXT_YPOS(tmi), - filename, tmi->font, tmi->chars, -1, tmi->lines, 0, -1, - tmi->autowrap, tmi->centered, tmi->parse_comments); + UnmapAllGadgets(); + FadeInfoSoundsAndMusic(); + + FadeOut(REDRAW_FIELD); + + HandleInfoScreen_Generic(0, 0, MB_MENU_INITIALIZE); - DrawTextSCentered(ybottom, FONT_TEXT_4, - "Press any key or button for info menu"); + PlayInfoSoundsAndMusic(); FadeIn(REDRAW_FIELD); } -static void HandleInfoScreen_LevelSet(int button) +void HandleInfoScreen_Generic(int dx, int dy, int button) { - if (button == MB_MENU_LEAVE) + static char *text_no_info = ""; + static int num_screens = 0; + static int screen_nr = 0; + static boolean use_global_screens = FALSE; + + if (button == MB_MENU_INITIALIZE) + { + num_screens = 0; + screen_nr = 0; + + if (info_mode == INFO_MODE_CREDITS) + { + int i; + + for (i = 0; i < 2; i++) + { + use_global_screens = i; // check for "FALSE", then "TRUE" + + // determine number of (global or level set specific) credits screens + while (getCreditsFilename(num_screens, use_global_screens) != NULL) + num_screens++; + + if (num_screens > 0) + break; + } + + text_no_info = "No credits available."; + } + else if (info_mode == INFO_MODE_PROGRAM) + { + use_global_screens = TRUE; + + // determine number of program info screens + while (getProgramInfoFilename(num_screens) != NULL) + num_screens++; + + text_no_info = "No program info available."; + } + else if (info_mode == INFO_MODE_LEVELSET) + { + use_global_screens = FALSE; + + // determine number of levelset info screens + while (getLevelSetInfoFilename(num_screens) != NULL) + num_screens++; + + text_no_info = "No level set info available."; + } + + if (num_screens == 0) + { + int font_title = MENU_INFO_FONT_TITLE; + int font_foot = MENU_INFO_FONT_FOOT; + int ystart = mSY - SY + MENU_SCREEN_INFO_YSTART; + int ybottom = mSY - SY + MENU_SCREEN_INFO_YBOTTOM; + + ClearField(); + + DrawInfoScreen_Headline(screen_nr, num_screens, use_global_screens); + + DrawTextSCentered(ystart, font_title, text_no_info); + DrawTextSCentered(ybottom, font_foot, TEXT_NEXT_MENU); + + return; + } + + DrawInfoScreen_GenericScreen(screen_nr, num_screens, use_global_screens); + } + else if (button == MB_MENU_LEAVE) { PlaySound(SND_MENU_ITEM_SELECTING); info_mode = INFO_MODE_MAIN; DrawInfoScreen(); - - return; } - else if (button == MB_MENU_CHOICE) + else if (button == MB_MENU_CHOICE || dx) { PlaySound(SND_MENU_ITEM_SELECTING); - FadeMenuSoundsAndMusic(); + screen_nr += (dx < 0 ? -1 : +1); - info_mode = INFO_MODE_MAIN; - DrawInfoScreen(); + if (screen_nr < 0 || screen_nr >= num_screens) + { + FadeInfoSoundsAndMusic(); + + info_mode = INFO_MODE_MAIN; + DrawInfoScreen(); + } + else + { + FadeSetNextScreen(); + + FadeOut(REDRAW_FIELD); + + DrawInfoScreen_GenericScreen(screen_nr, num_screens, use_global_screens); + + FadeIn(REDRAW_FIELD); + } } else { - PlayMenuSoundIfLoop(); + PlayInfoSoundIfLoop(); } } @@ -3994,38 +4108,70 @@ static void DrawInfoScreen(void) else if (info_mode == INFO_MODE_MUSIC) DrawInfoScreen_Music(); else if (info_mode == INFO_MODE_CREDITS) - DrawInfoScreen_Credits(); + DrawInfoScreen_Generic(); else if (info_mode == INFO_MODE_PROGRAM) - DrawInfoScreen_Program(); + DrawInfoScreen_Generic(); else if (info_mode == INFO_MODE_VERSION) DrawInfoScreen_Version(); else if (info_mode == INFO_MODE_LEVELSET) - DrawInfoScreen_LevelSet(); + DrawInfoScreen_Generic(); else DrawInfoScreen_Main(); +} - if (info_mode != INFO_MODE_MAIN && - info_mode != INFO_MODE_TITLE && - info_mode != INFO_MODE_MUSIC) - PlayMenuSoundsAndMusic(); +void DrawInfoScreen_FromMainMenu(int nr) +{ + int fade_mask = REDRAW_FIELD; + + if (nr < INFO_MODE_MAIN || nr >= MAX_INFO_MODES) + return; + + CloseDoor(DOOR_CLOSE_2); + + SetGameStatus(GAME_MODE_INFO); + + info_mode = nr; + info_screens_from_main = TRUE; + + if (redraw_mask & REDRAW_ALL) + fade_mask = REDRAW_ALL; + + if (CheckFadeAll()) + fade_mask = REDRAW_ALL; + + UnmapAllGadgets(); + FadeMenuSoundsAndMusic(); + + FadeSetEnterScreen(); + + FadeOut(fade_mask); + + FadeSkipNextFadeOut(); + + // needed if different viewport properties defined for info screen + ChangeViewportPropertiesIfNeeded(); + + SetMainBackgroundImage(IMG_BACKGROUND_INFO); + + DrawInfoScreen(); } void HandleInfoScreen(int mx, int my, int dx, int dy, int button) { if (info_mode == INFO_MODE_TITLE) - HandleInfoScreen_TitleScreen(button); + HandleInfoScreen_TitleScreen(dx, dy, button); else if (info_mode == INFO_MODE_ELEMENTS) - HandleInfoScreen_Elements(button); + HandleInfoScreen_Elements(dx, dy, button); else if (info_mode == INFO_MODE_MUSIC) - HandleInfoScreen_Music(button); + HandleInfoScreen_Music(dx, dy, button); else if (info_mode == INFO_MODE_CREDITS) - HandleInfoScreen_Credits(button); + HandleInfoScreen_Generic(dx, dy, button); else if (info_mode == INFO_MODE_PROGRAM) - HandleInfoScreen_Program(button); + HandleInfoScreen_Generic(dx, dy, button); else if (info_mode == INFO_MODE_VERSION) HandleInfoScreen_Version(button); else if (info_mode == INFO_MODE_LEVELSET) - HandleInfoScreen_LevelSet(button); + HandleInfoScreen_Generic(dx, dy, button); else HandleInfoScreen_Main(mx, my, dx, dy, button); } @@ -4045,7 +4191,7 @@ static int getPlayerNameColor(char *name) } static void drawTypeNameText(char *name, struct TextPosInfo *pos, - boolean active) + boolean active) { char text[MAX_PLAYER_NAME_LEN + 2] = { 0 }; boolean multiple_users = (game_status == GAME_MODE_PSEUDO_TYPENAMES); @@ -4053,8 +4199,12 @@ static void drawTypeNameText(char *name, struct TextPosInfo *pos, int sy = (multiple_users ? amSY + pos->y : mSY + ALIGNED_TEXT_YPOS(pos)); int font_nr = (active ? FONT_ACTIVE(pos->font) : pos->font); int font_width = getFontWidth(font_nr); + int font_xoffset = getFontDrawOffsetX(font_nr); + int font_yoffset = getFontDrawOffsetY(font_nr); + int font_sx = sx + font_xoffset; + int font_sy = sy + font_yoffset; - DrawBackgroundForFont(sx, sy, pos->width, pos->height, font_nr); + DrawBackgroundForFont(font_sx, font_sy, pos->width, pos->height, font_nr); sprintf(text, "%s%c", name, (active ? '_' : '\0')); @@ -4153,10 +4303,17 @@ static void setTypeNameValues(char *name, struct TextPosInfo *pos, // temporarily change active user to edited user user.nr = type_name_nr; - // load setup of edited user (unless creating user with current setup) - if (!create_user || - !Request("Use current setup values for the new player?", REQ_ASK)) + if (create_user && + Request("Use current setup values for the new player?", REQ_ASK)) + { + // use current setup values for new user, but create new player UUID + setup.player_uuid = getStringCopy(getUUID()); + } + else + { + // load setup for existing user (or start with defaults for new user) LoadSetup(); + } } char *setup_filename = getSetupFilename(); @@ -4168,6 +4325,9 @@ static void setTypeNameValues(char *name, struct TextPosInfo *pos, // save setup of edited user SaveSetup(); + // change name of edited user on score server + ApiRenamePlayerAsThread(); + if (game_status == GAME_MODE_PSEUDO_TYPENAMES || reset_setup) { if (reset_setup) @@ -4360,20 +4520,37 @@ static int getAlignYOffsetFromTreeInfo(TreeInfo *ti) return align_yoffset; } +static void StartPlayingFromHallOfFame(void) +{ + level_nr = scores.next_level_nr; + LoadLevel(level_nr); + + StartGameActions(network.enabled, setup.autorecord, level.random_seed); +} + static void DrawChooseTree(TreeInfo **ti_ptr) { int fade_mask = REDRAW_FIELD; + boolean restart_music = (game_status != game_status_last_screen && + game_status_last_screen != GAME_MODE_SCOREINFO); + + scores.continue_on_return = (game_status == GAME_MODE_SCORES && + game_status_last_screen == GAME_MODE_PLAYING); if (CheckFadeAll()) fade_mask = REDRAW_ALL; - if (strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY)) + if (*ti_ptr != NULL && strEqual((*ti_ptr)->subdir, STRING_TOP_DIRECTORY)) { if (game_status == GAME_MODE_SETUP) { execSetupArtwork(); } - else // GAME_MODE_LEVELS + else if (game_status == GAME_MODE_SCORES && scores.continue_playing) + { + StartPlayingFromHallOfFame(); + } + else { SetGameStatus(GAME_MODE_MAIN); @@ -4388,9 +4565,12 @@ static void DrawChooseTree(TreeInfo **ti_ptr) FreeScreenGadgets(); CreateScreenGadgets(); + if (restart_music) + FadeMenuSoundsAndMusic(); + FadeOut(fade_mask); - // needed if different viewport properties defined for choosing level (set) + // needed if different viewport properties defined for this screen ChangeViewportPropertiesIfNeeded(); if (game_status == GAME_MODE_NAMES) @@ -4399,20 +4579,38 @@ static void DrawChooseTree(TreeInfo **ti_ptr) SetMainBackgroundImage(IMG_BACKGROUND_LEVELNR); else if (game_status == GAME_MODE_LEVELS) SetMainBackgroundImage(IMG_BACKGROUND_LEVELS); + else if (game_status == GAME_MODE_SCORES) + SetMainBackgroundImage(IMG_BACKGROUND_SCORES); ClearField(); OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); + // map gadgets for high score screen + if (game_status == GAME_MODE_SCORES) + MapScreenMenuGadgets(SCREEN_MASK_SCORES); + MapScreenTreeGadgets(*ti_ptr); + HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, ti_ptr); DrawMaskedBorder(fade_mask); + if (restart_music) + PlayMenuSoundsAndMusic(); + FadeIn(fade_mask); } -static void drawChooseTreeText(int y, boolean active, TreeInfo *ti) +static int getChooseTreeFont(TreeInfo *node, boolean active) +{ + if (game_status == GAME_MODE_SCORES) + return (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1); + else + return MENU_CHOOSE_TREE_FONT(MENU_CHOOSE_TREE_COLOR(node, active)); +} + +static void drawChooseTreeText(TreeInfo *ti, int y, boolean active) { int num_entries = numTreeInfoInGroup(ti); boolean scrollbar_needed = (num_entries > NUM_MENU_ENTRIES_ON_SCREEN); @@ -4422,38 +4620,84 @@ static void drawChooseTreeText(int y, boolean active, TreeInfo *ti) int entry_pos = first_entry + y; TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti); TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos); - int font_color = MENU_CHOOSE_TREE_COLOR(node, active); - int font_nr = MENU_CHOOSE_TREE_FONT(font_color); - int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; + int font_nr = getChooseTreeFont(node, active); + int font_xoffset = getFontDrawOffsetX(font_nr); int xpos = MENU_SCREEN_START_XPOS; int ypos = MENU_SCREEN_START_YPOS + y; - int startx = amSX + xpos * 32; - int starty = amSY + ypos * 32; + int startdx = xpos * 32; + int startdy = ypos * 32; + int startx = amSX + startdx; + int starty = amSY + startdy; int startx_text = startx + font_xoffset; int endx_text = amSX + screen_width; int max_text_size = endx_text - startx_text; int max_buffer_len = max_text_size / getFontWidth(font_nr); char buffer[max_buffer_len + 1]; - strncpy(buffer, node->name, max_buffer_len); - buffer[max_buffer_len] = '\0'; + if (game_status == GAME_MODE_SCORES && !node->parent_link) + { + int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1); + int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2); + int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3); + int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4); + int font_size_1 = getFontWidth(font_nr1); + int font_size_3 = getFontWidth(font_nr3); + int font_size_4 = getFontWidth(font_nr4); + int text_size_1 = 4 * font_size_1; + int text_size_4 = 5 * font_size_4; + int border = amSX - SX + getFontDrawOffsetX(font_nr1); + int dx1 = 0; + int dx3 = text_size_1; + int dx4 = SXSIZE - 2 * startdx - 2 * border - text_size_4; + int num_dots = (dx4 - dx3) / font_size_3; + int startx1 = startx + dx1; + int startx3 = startx + dx3; + int startx4 = startx + dx4; + int pos = node->pos; + char *pos_text = getHallOfFameRankText(pos, 3); + int i; + + DrawText(startx1, starty, pos_text, font_nr1); + + for (i = 0; i < num_dots; i++) + DrawText(startx3 + i * font_size_3, starty, ".", font_nr3); + + if (!strEqual(scores.entry[pos].name, EMPTY_PLAYER_NAME)) + DrawText(startx3, starty, scores.entry[pos].name, font_nr2); + + DrawText(startx4, starty, getHallOfFameScoreText(pos, 5), font_nr4); + } + else + { + strncpy(buffer, node->name, max_buffer_len); + buffer[max_buffer_len] = '\0'; + + DrawText(startx, starty, buffer, font_nr); + } +} + +static void drawChooseTreeHeadExt(int type, char *title_string) +{ + int yoffset_sets = MENU_TITLE1_YPOS; + int yoffset_setup = 16; + int yoffset = (type == TREE_TYPE_SCORE_ENTRY || + type == TREE_TYPE_LEVEL_DIR || + type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup); + + DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string); +} - DrawText(startx, starty, buffer, font_nr); +static void drawChooseTreeHead(TreeInfo *ti) +{ + drawChooseTreeHeadExt(ti->type, ti->infotext); } -static void drawChooseTreeList(int first_entry, int num_page_entries, - TreeInfo *ti) +static void drawChooseTreeList(TreeInfo *ti) { + int first_entry = ti->cl_first; + int num_entries = numTreeInfoInGroup(ti); + int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); int i; - char *title_string = NULL; - int yoffset_sets = MENU_TITLE1_YPOS; - int yoffset_setup = 16; - int yoffset = (ti->type == TREE_TYPE_LEVEL_DIR || - ti->type == TREE_TYPE_LEVEL_NR ? yoffset_sets : yoffset_setup); - - title_string = ti->infotext; - - DrawTextSCentered(mSY - SY + yoffset, FONT_TITLE_1, title_string); clearMenuListArea(); @@ -4465,7 +4709,7 @@ static void drawChooseTreeList(int first_entry, int num_page_entries, node_first = getTreeInfoFirstGroupEntry(ti); node = getTreeInfoFromPos(node_first, entry_pos); - drawChooseTreeText(i, FALSE, ti); + drawChooseTreeText(ti, i, FALSE); if (node->parent_link) initCursor(i, IMG_MENU_BUTTON_LEAVE_MENU); @@ -4474,6 +4718,9 @@ static void drawChooseTreeList(int first_entry, int num_page_entries, else initCursor(i, IMG_MENU_BUTTON); + if (game_status == GAME_MODE_SCORES && node->pos == scores.last_added) + initCursor(i, IMG_MENU_BUTTON_ENTER_MENU); + if (game_status == GAME_MODE_NAMES) drawChooseTreeEdit(i, FALSE); } @@ -4481,21 +4728,26 @@ static void drawChooseTreeList(int first_entry, int num_page_entries, redraw_mask |= REDRAW_FIELD; } -static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti) +static void drawChooseTreeInfo(TreeInfo *ti) { - TreeInfo *node, *node_first; - int x, last_redraw_mask = redraw_mask; + int entry_pos = ti->cl_first + ti->cl_cursor; + int last_redraw_mask = redraw_mask; int ypos = MENU_TITLE2_YPOS; int font_nr = FONT_TITLE_2; + int x; if (ti->type == TREE_TYPE_LEVEL_NR) DrawTextFCentered(ypos, font_nr, leveldir_current->name); + if (ti->type == TREE_TYPE_SCORE_ENTRY) + DrawTextFCentered(ypos, font_nr, "HighScores of Level %d", + scores.last_level_nr); + if (ti->type != TREE_TYPE_LEVEL_DIR) return; - node_first = getTreeInfoFirstGroupEntry(ti); - node = getTreeInfoFromPos(node_first, entry_pos); + TreeInfo *node_first = getTreeInfoFirstGroupEntry(ti); + TreeInfo *node = getTreeInfoFromPos(node_first, entry_pos); DrawBackgroundForFont(SX, SY + ypos, SXSIZE, getFontHeight(font_nr), font_nr); @@ -4516,10 +4768,50 @@ static void drawChooseTreeInfo(int entry_pos, TreeInfo *ti) MarkTileDirty(x, 1); } -static void drawChooseTreeCursorAndText(int y, boolean active, TreeInfo *ti) +static void drawChooseTreeCursorAndText(TreeInfo *ti, boolean active) { - drawChooseTreeCursor(y, active); - drawChooseTreeText(y, active, ti); + drawChooseTreeCursor(ti->cl_cursor, active); + drawChooseTreeText(ti, ti->cl_cursor, active); +} + +static void drawChooseTreeScreen(TreeInfo *ti) +{ + drawChooseTreeHead(ti); + drawChooseTreeList(ti); + drawChooseTreeInfo(ti); + drawChooseTreeCursorAndText(ti, TRUE); + + AdjustChooseTreeScrollbar(ti, SCREEN_CTRL_ID_SCROLL_VERTICAL); + + // scroll bar and buttons may just have been added after reloading scores + if (game_status == GAME_MODE_SCORES) + MapScreenTreeGadgets(ti); +} + +static TreeInfo *setHallOfFameActiveEntry(TreeInfo **ti_ptr) +{ + int score_pos = scores.last_added; + + if (game_status_last_screen == GAME_MODE_SCOREINFO) + score_pos = scores.last_entry_nr; + + // set current tree entry to last added score entry + *ti_ptr = getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos)); + + // if that fails, set current tree entry to first entry (back link) + if (*ti_ptr == NULL) + *ti_ptr = score_entries->node_group; + + int num_entries = numTreeInfoInGroup(*ti_ptr); + int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); + int pos_score = getPosFromTreeInfo(*ti_ptr); + int pos_first_raw = pos_score - (num_page_entries + 1) / 2 + 1; + int pos_first = MIN(MAX(0, pos_first_raw), num_entries - num_page_entries); + + (*ti_ptr)->cl_first = pos_first; + (*ti_ptr)->cl_cursor = pos_score - pos_first; + + return *ti_ptr; } static void HandleChooseTree(int mx, int my, int dx, int dy, int button, @@ -4529,15 +4821,33 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, boolean has_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->mapped; int mx_scrollbar = screen_gadget[SCREEN_CTRL_ID_SCROLL_VERTICAL]->x; int mx_right_border = (has_scrollbar ? mx_scrollbar : SX + SXSIZE); - int sx1_edit_name = getChooseTreeEditXPos(POS_LEFT); - int sx2_edit_name = getChooseTreeEditXPos(POS_RIGHT); + int sx1_edit_name = getChooseTreeEditXPosReal(POS_LEFT); + int sx2_edit_name = getChooseTreeEditXPosReal(POS_RIGHT); int x = 0; - int y = ti->cl_cursor; + int y = (ti != NULL ? ti->cl_cursor : 0); int step = (button == 1 ? 1 : button == 2 ? 5 : 10); int num_entries = numTreeInfoInGroup(ti); int num_page_entries = MIN(num_entries, NUM_MENU_ENTRIES_ON_SCREEN); boolean position_set_by_scrollbar = (dx == 999); + if (game_status == GAME_MODE_SCORES) + { + if (server_scores.updated) + { + // reload scores, using updated server score cache file + LoadLocalAndServerScore(scores.last_level_nr, FALSE); + + server_scores.updated = FALSE; + + DrawHallOfFame_setScoreEntries(); + + ti = setHallOfFameActiveEntry(ti_ptr); + + if (button != MB_MENU_INITIALIZE) + drawChooseTreeScreen(ti); + } + } + if (button == MB_MENU_INITIALIZE) { int num_entries = numTreeInfoInGroup(ti); @@ -4546,11 +4856,16 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, align_xoffset = getAlignXOffsetFromTreeInfo(ti); align_yoffset = getAlignYOffsetFromTreeInfo(ti); - if (ti->cl_first == -1) + if (game_status == GAME_MODE_SCORES) + { + ti = setHallOfFameActiveEntry(ti_ptr); + } + else if (ti->cl_first == -1) { // only on initialization ti->cl_first = MAX(0, entry_pos - num_page_entries + 1); ti->cl_cursor = entry_pos - ti->cl_first; + } else if (ti->cl_cursor >= num_page_entries || (num_entries > num_page_entries && @@ -4563,19 +4878,15 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, if (position_set_by_scrollbar) ti->cl_first = dy; - else - AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, - ti->cl_first, ti); - drawChooseTreeList(ti->cl_first, num_page_entries, ti); - drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti); - drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti); + drawChooseTreeScreen(ti); return; } else if (button == MB_MENU_LEAVE) { - FadeSetLeaveMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetLeaveMenu(); PlaySound(SND_MENU_ITEM_SELECTING); @@ -4586,7 +4897,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } else if (game_status == GAME_MODE_SETUP) { - if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || + if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE || + setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY || setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE) execSetupGame(); @@ -4628,6 +4940,23 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, return; } +#if defined(PLATFORM_ANDROID) + // directly continue when touching the screen after playing + if ((mx || my) && scores.continue_on_return) + { + // ignore touch events until released + mx = my = 0; + } +#endif + + // any mouse click or cursor key stops leaving scores by "Return" key + if ((mx || my || dx || dy) && scores.continue_on_return) + { + scores.continue_on_return = FALSE; + level_nr = scores.last_level_nr; + LoadLevel(level_nr); + } + if (mx || my) // mouse input { x = (mx - amSX) / 32; @@ -4676,14 +5005,7 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } if (redraw) - { - drawChooseTreeList(ti->cl_first, num_page_entries, ti); - drawChooseTreeInfo(ti->cl_first + ti->cl_cursor, ti); - drawChooseTreeCursorAndText(ti->cl_cursor, TRUE, ti); - - AdjustChooseTreeScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, - ti->cl_first, ti); - } + drawChooseTreeScreen(ti); return; } @@ -4692,7 +5014,13 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, y = ti->cl_cursor + dy; } - if (dx == 1) + if (game_status == GAME_MODE_SCORES && ABS(dx) == 1) + { + HandleHallOfFame_SelectLevel(1, dx); + + return; + } + else if (dx == 1) { TreeInfo *node_first, *node_cursor; int entry_pos = ti->cl_first + y; @@ -4708,15 +5036,17 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; + *ti_ptr = node_cursor->node_group; DrawChooseTree(ti_ptr); return; } } - else if (dx == -1 && ti->node_parent) + else if ((dx == -1 || button == MB_MENU_CONTINUE) && ti->node_parent) { - FadeSetLeaveMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetLeaveMenu(); PlaySound(SND_MENU_ITEM_SELECTING); @@ -4743,17 +5073,20 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, { PlaySound(SND_MENU_ITEM_ACTIVATING); - drawChooseTreeCursorAndText(ti->cl_cursor, FALSE, ti); - drawChooseTreeCursorAndText(y, TRUE, ti); - drawChooseTreeInfo(ti->cl_first + y, ti); + drawChooseTreeCursorAndText(ti, FALSE); ti->cl_cursor = y; + + drawChooseTreeCursorAndText(ti, TRUE); + + drawChooseTreeInfo(ti); } else if (dx < 0) { if (game_status == GAME_MODE_SETUP) { - if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || + if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE || + setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY || setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE) execSetupGame(); @@ -4796,22 +5129,26 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; + *ti_ptr = node_cursor->node_group; DrawChooseTree(ti_ptr); } else if (node_cursor->parent_link) { - FadeSetLeaveMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetLeaveMenu(); *ti_ptr = node_cursor->node_parent; DrawChooseTree(ti_ptr); } else { - FadeSetEnterMenu(); + if (game_status != GAME_MODE_SCORES) + FadeSetEnterMenu(); node_cursor->cl_first = ti->cl_first; node_cursor->cl_cursor = ti->cl_cursor; + *ti_ptr = node_cursor; if (ti->type == TREE_TYPE_LEVEL_DIR) @@ -4825,7 +5162,8 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, if (game_status == GAME_MODE_SETUP) { - if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || + if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE || + setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED || setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY || setup_mode == SETUP_MODE_CHOOSE_SNAPSHOT_MODE) execSetupGame(); @@ -4862,6 +5200,9 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, { // store level set if chosen from "last played level set" menu StoreLastPlayedLevels(leveldir_current); + + // store if level set chosen from "last played level set" menu + SaveLevelSetup_LastSeries(); } else if (game_status == GAME_MODE_NAMES) { @@ -4899,6 +5240,23 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS); ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC); } + else if (game_status == GAME_MODE_SCORES) + { + if (scores.continue_playing && scores.continue_on_return) + { + StartPlayingFromHallOfFame(); + + return; + } + else if (!scores.continue_on_return) + { + SetGameStatus(GAME_MODE_SCOREINFO); + + DrawScoreInfo(node_cursor->pos); + + return; + } + } SetGameStatus(GAME_MODE_MAIN); @@ -4907,14 +5265,15 @@ static void HandleChooseTree(int mx, int my, int dx, int dy, int button, } } } + + if (game_status == GAME_MODE_SCORES) + PlayMenuSoundIfLoop(); } void DrawChoosePlayerName(void) { int i; - FadeMenuSoundsAndMusic(); - if (player_name != NULL) { freeTreeInfo(player_name); @@ -4953,9 +5312,10 @@ void DrawChoosePlayerName(void) if (player_name_current == NULL) player_name_current = player_name; - DrawChooseTree(&player_name_current); + // set text size for main name input (also used on name selection screen) + InitializeMainControls(); - PlayMenuSoundsAndMusic(); + DrawChooseTree(&player_name_current); } void HandleChoosePlayerName(int mx, int my, int dx, int dy, int button) @@ -4965,11 +5325,7 @@ void HandleChoosePlayerName(int mx, int my, int dx, int dy, int button) void DrawChooseLevelSet(void) { - FadeMenuSoundsAndMusic(); - DrawChooseTree(&leveldir_current); - - PlayMenuSoundsAndMusic(); } void HandleChooseLevelSet(int mx, int my, int dx, int dy, int button) @@ -4981,8 +5337,6 @@ void DrawChooseLevelNr(void) { int i; - FadeMenuSoundsAndMusic(); - if (level_number != NULL) { freeTreeInfo(level_number); @@ -5028,8 +5382,6 @@ void DrawChooseLevelNr(void) level_number_current = level_number; DrawChooseTree(&level_number_current); - - PlayMenuSoundsAndMusic(); } void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button) @@ -5037,15 +5389,69 @@ void HandleChooseLevelNr(int mx, int my, int dx, int dy, int button) HandleChooseTree(mx, my, dx, dy, button, &level_number_current); } -void DrawHallOfFame(int level_nr, int highlight_position) +static void DrawHallOfFame_setScoreEntries(void) { - int fade_mask = REDRAW_FIELD; + int max_empty_entries = 10; // at least show "top ten" list, if empty + int max_visible_entries = NUM_MENU_ENTRIES_ON_SCREEN - 1; // w/o back link + int min_score_entries = MIN(max_empty_entries, max_visible_entries); + int score_pos = (scores.last_added >= 0 ? scores.last_added : 0); + int i; - if (CheckFadeAll()) - fade_mask = REDRAW_ALL; + if (score_entries != NULL) + { + freeTreeInfo(score_entries); - UnmapAllGadgets(); - FadeMenuSoundsAndMusic(); + score_entries = NULL; + } + + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + // do not add empty score entries if off-screen + if (scores.entry[i].score == 0 && + scores.entry[i].time == 0 && + i >= min_score_entries) + break; + + TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_SCORE_ENTRY); + char identifier[32], name[64]; + int value = i; + + ti->node_top = &score_entries; + ti->sort_priority = 10000 + value; + ti->color = FC_YELLOW; + ti->pos = i; + + snprintf(identifier, sizeof(identifier), "%d", value); + snprintf(name, sizeof(name), "%03d.", value + 1); + + setString(&ti->identifier, identifier); + setString(&ti->name, name); + setString(&ti->name_sorting, name); + + pushTreeInfo(&score_entries, ti); + } + + // sort score entries to start with highest score entry + sortTreeInfo(&score_entries); + + // add top tree node to create back link to main menu + score_entries = addTopTreeInfoNode(score_entries); + + // set current score entry to last added or highest score entry + score_entry_current = + getTreeInfoFromIdentifier(score_entries, i_to_a(score_pos)); + + // if that fails, set current score entry to first valid score entry + if (score_entry_current == NULL) + score_entry_current = getFirstValidTreeInfoEntry(score_entries); + + if (score_entries != NULL && scores.continue_playing) + setString(&score_entries->node_group->name, BACKLINK_TEXT_NEXT); +} + +void DrawHallOfFame(int nr) +{ + scores.last_level_nr = nr; // (this is needed when called from GameEnd() after winning a game) KeyboardAutoRepeatOn(); @@ -5054,144 +5460,341 @@ void DrawHallOfFame(int level_nr, int highlight_position) SetDrawDeactivationMask(REDRAW_NONE); SetDrawBackgroundMask(REDRAW_FIELD); - if (highlight_position < 0) - LoadScore(level_nr); - else + LoadLocalAndServerScore(scores.last_level_nr, TRUE); + + DrawHallOfFame_setScoreEntries(); + + if (scores.last_added >= 0) SetAnimStatus(GAME_MODE_PSEUDO_SCORESNEW); FadeSetEnterScreen(); - FadeOut(fade_mask); + DrawChooseTree(&score_entry_current); +} - // needed if different viewport properties defined for scores - ChangeViewportPropertiesIfNeeded(); +static char *getHallOfFameRankText(int nr, int size) +{ + static char rank_text[10]; + boolean forced = (scores.force_last_added && nr == scores.last_added); + char *rank_text_raw = (forced ? "???" : int2str(nr + 1, size)); - PlayMenuSoundsAndMusic(); + sprintf(rank_text, "%s%s", rank_text_raw, (size > 0 || !forced ? "." : "")); - OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); + return rank_text; +} - HandleHallOfFame(level_nr, highlight_position, 0, 0, MB_MENU_INITIALIZE); +static char *getHallOfFameTimeText(int nr) +{ + static char score_text[10]; + int time_seconds = scores.entry[nr].time / FRAMES_PER_SECOND; + int mm = (time_seconds / 60) % 60; + int ss = (time_seconds % 60); - DrawMaskedBorder(fade_mask); + sprintf(score_text, "%02d:%02d", mm, ss); // show playing time - FadeIn(fade_mask); + return score_text; +} + +static char *getHallOfFameScoreText(int nr, int size) +{ + if (!level.rate_time_over_score) + return int2str(scores.entry[nr].score, size); // show normal score + else if (level.use_step_counter) + return int2str(scores.entry[nr].time, size); // show number of steps + else + return getHallOfFameTimeText(nr); // show playing time } -static void drawHallOfFameList(int level_nr, int first_entry, - int highlight_position) +static char *getHallOfFameTapeDateText(struct ScoreEntry *entry) { + static char tape_date[MAX_ISO_DATE_LEN + 1]; int i, j; - SetMainBackgroundImage(IMG_BACKGROUND_SCORES); + if (!strEqual(entry->tape_date, UNKNOWN_NAME) || + strEqual(entry->tape_basename, UNDEFINED_FILENAME)) + return entry->tape_date; + + for (i = 0, j = 0; i < 8; i++, j++) + { + tape_date[j] = entry->tape_basename[i]; + + if (i == 3 || i == 5) + tape_date[++j] = '-'; + } + + tape_date[MAX_ISO_DATE_LEN] = '\0'; + + return tape_date; +} + +static void HandleHallOfFame_SelectLevel(int step, int direction) +{ + int old_level_nr = scores.last_level_nr; + int new_level_nr = old_level_nr + step * direction; + + if (new_level_nr < leveldir_current->first_level) + new_level_nr = leveldir_current->first_level; + if (new_level_nr > leveldir_current->last_level) + new_level_nr = leveldir_current->last_level; + + if (setup.handicap && new_level_nr > leveldir_current->handicap_level) + new_level_nr = leveldir_current->handicap_level; + + if (new_level_nr != old_level_nr) + { + PlaySound(SND_MENU_ITEM_SELECTING); + + scores.last_level_nr = level_nr = new_level_nr; + scores.last_entry_nr = 0; + + LoadLevel(level_nr); + LoadLocalAndServerScore(level_nr, TRUE); + + DrawHallOfFame_setScoreEntries(); + + if (game_status == GAME_MODE_SCORES) + { + // force remapping optional gadgets (especially scroll bar) + UnmapScreenTreeGadgets(); + + // redraw complete high score screen, as sub-title has changed + ClearField(); + + // redraw level selection buttons (which have just been erased) + RedrawScreenMenuGadgets(SCREEN_MASK_SCORES); + + HandleChooseTree(0, 0, 0, 0, MB_MENU_INITIALIZE, &score_entry_current); + } + else + { + DrawScoreInfo_Content(scores.last_entry_nr); + } + + SaveLevelSetup_SeriesInfo(); + } +} + +void HandleHallOfFame(int mx, int my, int dx, int dy, int button) +{ + HandleChooseTree(mx, my, dx, dy, button, &score_entry_current); +} + +static void DrawScoreInfo_Content(int entry_nr) +{ + struct ScoreEntry *entry = &scores.entry[entry_nr]; + char *pos_text = getHallOfFameRankText(entry_nr, 0); + char *tape_date = getHallOfFameTapeDateText(entry); + int font_head = MENU_INFO_FONT_HEAD; + int font_text = MENU_INFO_FONT_TEXT; + int font_foot = MENU_INFO_FONT_FOOT; + int spacing_para = menu.paragraph_spacing[GAME_MODE_SCOREINFO]; + int spacing_line = menu.line_spacing[GAME_MODE_SCOREINFO]; + int spacing_left = menu.left_spacing[GAME_MODE_SCOREINFO]; + int spacing_top = menu.top_spacing[GAME_MODE_SCOREINFO]; + int xstep = getFontWidth(font_text); + int ystep_para = getMenuTextStep(spacing_para, font_text); + int ystep_line = getMenuTextStep(spacing_line, font_text); + int xstart = mSX - SX + spacing_left; + int ystart = mSY - SY + spacing_top + getHeadlineSpacing(); + int ybottom = mSY - SY + SYSIZE - menu.bottom_spacing[GAME_MODE_SCOREINFO]; + int xstart1 = xstart + xstep; + int xstart2 = xstart + xstep * 12; + int select_x = SX + xstart1; + int select_y1, select_y2; + int play_x, play_y; + int play_height = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]->height; + boolean play_visible = !strEqual(tape_date, UNKNOWN_NAME); + int font_width = getFontWidth(font_text); + int font_height = getFontHeight(font_text); + int tape_date_width = getTextWidth(tape_date, font_text); + int pad_left = xstart2; + int pad_right = menu.right_spacing[GAME_MODE_SCOREINFO]; + int max_chars_per_line = (SXSIZE - pad_left - pad_right) / font_width; + int max_lines_per_text = 5; + int lines; + ClearField(); - DrawTextSCentered(MENU_TITLE1_YPOS, FONT_TITLE_1, "Hall Of Fame"); - DrawTextFCentered(MENU_TITLE2_YPOS, FONT_TITLE_2, - "HighScores of Level %d", level_nr); + // redraw level selection buttons (which have just been erased) + RedrawScreenMenuGadgets(SCREEN_MASK_SCORES); + + drawChooseTreeHead(score_entries); + drawChooseTreeInfo(score_entries); + + DrawTextF(xstart1, ystart, font_head, "Level Set"); + lines = DrawTextBufferS(xstart2, ystart, leveldir_current->name, font_text, + max_chars_per_line, -1, max_lines_per_text, 0, -1, + TRUE, FALSE, FALSE); + ystart += ystep_line + (lines > 0 ? lines - 1 : 0) * font_height; + + DrawTextF(xstart1, ystart, font_head, "Level"); + lines = DrawTextBufferS(xstart2, ystart, level.name, font_text, + max_chars_per_line, -1, max_lines_per_text, 0, -1, + TRUE, FALSE, FALSE); + ystart += ystep_para + (lines > 0 ? lines - 1 : 0) * font_height; + + select_y1 = SY + ystart; + ystart += graphic_info[IMG_MENU_BUTTON_PREV_SCORE].height; - for (i = 0; i < NUM_MENU_ENTRIES_ON_SCREEN; i++) + DrawTextF(xstart1, ystart, font_head, "Rank"); + DrawTextF(xstart2, ystart, font_text, pos_text); + ystart += ystep_line; + + DrawTextF(xstart1, ystart, font_head, "Player"); + DrawTextF(xstart2, ystart, font_text, entry->name); + ystart += ystep_line; + + if (level.use_step_counter) { - int entry = first_entry + i; - boolean active = (entry == highlight_position); - int font_nr1 = (active ? FONT_TEXT_1_ACTIVE : FONT_TEXT_1); - int font_nr2 = (active ? FONT_TEXT_2_ACTIVE : FONT_TEXT_2); - int font_nr3 = (active ? FONT_TEXT_3_ACTIVE : FONT_TEXT_3); - int font_nr4 = (active ? FONT_TEXT_4_ACTIVE : FONT_TEXT_4); - int dxoff = getFontDrawOffsetX(font_nr1); - int dx1 = 3 * getFontWidth(font_nr1); - int dx2 = dx1 + getFontWidth(font_nr1); - int dx3 = SXSIZE - 2 * (mSX - SX + dxoff) - 5 * getFontWidth(font_nr4); - int num_dots = (dx3 - dx2) / getFontWidth(font_nr3); - int sy = mSY + 64 + i * 32; + DrawTextF(xstart1, ystart, font_head, "Steps"); + DrawTextF(xstart2, ystart, font_text, int2str(entry->time, 5)); + ystart += ystep_line; + } + else + { + DrawTextF(xstart1, ystart, font_head, "Time"); + DrawTextF(xstart2, ystart, font_text, getHallOfFameTimeText(entry_nr)); + ystart += ystep_line; + } + + if (!level.rate_time_over_score || entry->score > 0) + { + DrawTextF(xstart1, ystart, font_head, "Score"); + DrawTextF(xstart2, ystart, font_text, int2str(entry->score, 5)); + ystart += ystep_line; + } + + ystart += ystep_line; + + play_x = SX + xstart2 + tape_date_width + font_width; + play_y = SY + ystart + (font_height - play_height) / 2; + + DrawTextF(xstart1, ystart, font_head, "Tape Date"); + DrawTextF(xstart2, ystart, font_text, tape_date); + ystart += ystep_line; + + DrawTextF(xstart1, ystart, font_head, "Platform"); + DrawTextF(xstart2, ystart, font_text, entry->platform); + ystart += ystep_line; + + DrawTextF(xstart1, ystart, font_head, "Version"); + DrawTextF(xstart2, ystart, font_text, entry->version); + ystart += ystep_line; + + DrawTextF(xstart1, ystart, font_head, "Country"); + lines = DrawTextBufferS(xstart2, ystart, entry->country_name, font_text, + max_chars_per_line, -1, max_lines_per_text, 0, -1, + TRUE, FALSE, FALSE); + ystart += ystep_line; + + select_y2 = SY + ystart; + + DrawTextSCentered(ybottom, font_foot, "Press any key or button to go back"); + + AdjustScoreInfoButtons_SelectScore(select_x, select_y1, select_y2); + AdjustScoreInfoButtons_PlayTape(play_x, play_y, play_visible); +} + +static void DrawScoreInfo(int entry_nr) +{ + scores.last_entry_nr = entry_nr; + score_info_tape_play = FALSE; + + UnmapAllGadgets(); + + FreeScreenGadgets(); + CreateScreenGadgets(); + + FadeOut(REDRAW_FIELD); + + // needed if different viewport properties defined after playing score tape + ChangeViewportPropertiesIfNeeded(); + + // set this after "ChangeViewportPropertiesIfNeeded()" (which may reset it) + SetDrawDeactivationMask(REDRAW_NONE); + SetDrawBackgroundMask(REDRAW_FIELD); + + // needed if different background image defined after playing score tape + SetMainBackgroundImage(IMG_BACKGROUND_SCORES); + SetMainBackgroundImageIfDefined(IMG_BACKGROUND_SCOREINFO); + + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + ClearRectangle(gfx.background_bitmap, gfx.real_sx, gfx.real_sy + 64, + gfx.full_sxsize, gfx.full_sysize - 64); + + DrawScoreInfo_Content(entry_nr); + + // map gadgets for score info screen + MapScreenMenuGadgets(SCREEN_MASK_SCORES_INFO); + + FadeIn(REDRAW_FIELD); +} - DrawText(mSX, sy, int2str(entry + 1, 3), font_nr1); - DrawText(mSX + dx1, sy, ".", font_nr1); +static void HandleScoreInfo_SelectScore(int step, int direction) +{ + int old_entry_nr = scores.last_entry_nr; + int new_entry_nr = old_entry_nr + step * direction; + int num_nodes = numTreeInfoInGroup(score_entry_current); + int num_entries = num_nodes - 1; // score nodes only, without back link - for (j = 0; j < num_dots; j++) - DrawText(mSX + dx2 + j * getFontWidth(font_nr3), sy, ".", font_nr3); + if (new_entry_nr < 0) + new_entry_nr = 0; + if (new_entry_nr > num_entries - 1) + new_entry_nr = num_entries - 1; - if (!strEqual(highscore[entry].Name, EMPTY_PLAYER_NAME)) - DrawText(mSX + dx2, sy, highscore[entry].Name, font_nr2); + if (new_entry_nr != old_entry_nr) + { + scores.last_entry_nr = new_entry_nr; - DrawText(mSX + dx3, sy, int2str(highscore[entry].Score, 5), font_nr4); + DrawScoreInfo_Content(new_entry_nr); } +} - redraw_mask |= REDRAW_FIELD; +static void HandleScoreInfo_PlayTape(void) +{ + if (!PlayScoreTape(scores.last_entry_nr)) + { + DrawScoreInfo_Content(scores.last_entry_nr); + + FadeIn(REDRAW_FIELD); + } } -void HandleHallOfFame(int mx, int my, int dx, int dy, int button) +void HandleScoreInfo(int mx, int my, int dx, int dy, int button) { - static int level_nr = 0; - static int first_entry = 0; - static int highlight_position = 0; - int step = (button == 1 ? 1 : button == 2 ? 5 : 10); + boolean button_action = (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE); + boolean button_is_valid = (mx >= 0 && my >= 0); + boolean button_screen_clicked = (button_action && button_is_valid); - if (button == MB_MENU_INITIALIZE) + if (server_scores.updated) { - level_nr = mx; - highlight_position = my; + // reload scores, using updated server score cache file + LoadLocalAndServerScore(scores.last_level_nr, FALSE); - first_entry = highlight_position - (NUM_MENU_ENTRIES_ON_SCREEN + 1) / 2 + 1; + server_scores.updated = FALSE; - if (first_entry < 0) - first_entry = 0; - else if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES) - first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN); + DrawHallOfFame_setScoreEntries(); - drawHallOfFameList(level_nr, first_entry, highlight_position); - - return; + DrawScoreInfo_Content(scores.last_entry_nr); } - if (ABS(dy) == SCROLL_PAGE) // handle scrolling one page - step = NUM_MENU_ENTRIES_ON_SCREEN - 1; - - if (dy < 0) + if (button_screen_clicked) { - if (first_entry > 0) - { - first_entry -= step; - if (first_entry < 0) - first_entry = 0; + PlaySound(SND_MENU_ITEM_SELECTING); - drawHallOfFameList(level_nr, first_entry, highlight_position); - } + SetGameStatus(GAME_MODE_SCORES); + + DrawHallOfFame(scores.last_level_nr); } - else if (dy > 0) + else if (dx) { - if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN < MAX_SCORE_ENTRIES) - { - first_entry += step; - if (first_entry + NUM_MENU_ENTRIES_ON_SCREEN > MAX_SCORE_ENTRIES) - first_entry = MAX(0, MAX_SCORE_ENTRIES - NUM_MENU_ENTRIES_ON_SCREEN); - - drawHallOfFameList(level_nr, first_entry, highlight_position); - } + HandleHallOfFame_SelectLevel(1, SIGN(dx) * (ABS(dx) > 1 ? 10 : 1)); } - else if (button == MB_MENU_LEAVE || button == MB_MENU_CHOICE) + else if (dy) { - PlaySound(SND_MENU_ITEM_SELECTING); - - FadeSound(SND_BACKGROUND_SCORES); - - if (button == MB_MENU_CHOICE && - game_status_last_screen == GAME_MODE_PLAYING && - setup.auto_play_next_level && setup.increment_levels && - level_nr < leveldir_current->last_level && - !network_playing) - { - StartGameActions(network.enabled, setup.autorecord, level.random_seed); - } - else - { - SetGameStatus(GAME_MODE_MAIN); - - DrawMainMenu(); - } + HandleScoreInfo_SelectScore(1, SIGN(dy) * (ABS(dy) > 1 ? 10 : 1)); } - - if (game_status == GAME_MODE_SCORES) - PlayMenuSoundIfLoop(); } @@ -5210,6 +5813,7 @@ static char *vsync_mode_text; static char *scroll_delay_text; static char *snapshot_mode_text; static char *game_speed_text; +static char *scores_type_text; static char *network_server_text; static char *graphics_set_name; static char *sounds_set_name; @@ -5230,6 +5834,56 @@ static void execSetupMain(void) DrawSetupScreen(); } +static void execSetupGame_setScoresType(void) +{ + if (scores_types == NULL) + { + int i; + + for (i = 0; scores_types_list[i].value != NULL; i++) + { + TreeInfo *ti = newTreeInfo_setDefaults(TREE_TYPE_UNDEFINED); + char identifier[32], name[32]; + char *value = scores_types_list[i].value; + char *text = scores_types_list[i].text; + + ti->node_top = &scores_types; + ti->sort_priority = i; + + sprintf(identifier, "%s", value); + sprintf(name, "%s", text); + + setString(&ti->identifier, identifier); + setString(&ti->name, name); + setString(&ti->name_sorting, name); + setString(&ti->infotext, STR_SETUP_CHOOSE_SCORES_TYPE); + + pushTreeInfo(&scores_types, ti); + } + + // sort scores type values to start with lowest scores type value + sortTreeInfo(&scores_types); + + // set current scores type value to configured scores type value + scores_type_current = + getTreeInfoFromIdentifier(scores_types, setup.scores_in_highscore_list); + + // if that fails, set current scores type to reliable default value + if (scores_type_current == NULL) + scores_type_current = + getTreeInfoFromIdentifier(scores_types, STR_SCORES_TYPE_DEFAULT); + + // if that also fails, set current scores type to first available value + if (scores_type_current == NULL) + scores_type_current = scores_types; + } + + setup.scores_in_highscore_list = scores_type_current->identifier; + + // needed for displaying scores type text instead of identifier + scores_type_text = scores_type_current->name; +} + static void execSetupGame_setGameSpeeds(boolean update_value) { if (setup.game_speed_extended) @@ -5333,7 +5987,7 @@ static void execSetupGame_setScrollDelays(void) // set current scroll delay value to configured scroll delay value scroll_delay_current = - getTreeInfoFromIdentifier(scroll_delays,i_to_a(setup.scroll_delay_value)); + getTreeInfoFromIdentifier(scroll_delays, i_to_a(setup.scroll_delay_value)); // if that fails, set current scroll delay to reliable default value if (scroll_delay_current == NULL) @@ -5423,11 +6077,15 @@ static void execSetupGame(void) boolean check_vsync_mode = (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED); execSetupGame_setGameSpeeds(FALSE); + execSetupGame_setScoresType(); execSetupGame_setScrollDelays(); execSetupGame_setSnapshotModes(); execSetupGame_setNetworkServerText(); + if (!setup.provide_uploading_tapes) + setHideSetupEntry(execOfferUploadTapes); + setup_mode = SETUP_MODE_GAME; DrawSetupScreen(); @@ -5437,6 +6095,13 @@ static void execSetupGame(void) DisableVsyncIfNeeded(); } +static void execSetupChooseScoresType(void) +{ + setup_mode = SETUP_MODE_CHOOSE_SCORES_TYPE; + + DrawSetupScreen(); +} + static void execSetupChooseGameSpeed(void) { setup_mode = SETUP_MODE_CHOOSE_GAME_SPEED; @@ -5882,7 +6547,7 @@ static void execSetupSound(void) // set current volume value to configured volume value volume_simple_current = - getTreeInfoFromIdentifier(volumes_simple,i_to_a(setup.volume_simple)); + getTreeInfoFromIdentifier(volumes_simple, i_to_a(setup.volume_simple)); // if that fails, set current volume to reliable default value if (volume_simple_current == NULL) @@ -5950,7 +6615,7 @@ static void execSetupSound(void) // set current volume value to configured volume value volume_loops_current = - getTreeInfoFromIdentifier(volumes_loops,i_to_a(setup.volume_loops)); + getTreeInfoFromIdentifier(volumes_loops, i_to_a(setup.volume_loops)); // if that fails, set current volume to reliable default value if (volume_loops_current == NULL) @@ -6018,7 +6683,7 @@ static void execSetupSound(void) // set current volume value to configured volume value volume_music_current = - getTreeInfoFromIdentifier(volumes_music,i_to_a(setup.volume_music)); + getTreeInfoFromIdentifier(volumes_music, i_to_a(setup.volume_music)); // if that fails, set current volume to reliable default value if (volume_music_current == NULL) @@ -6513,6 +7178,11 @@ static void execGadgetNetworkServer(void) ClickOnGadget(gi, MB_LEFTBUTTON); } +static void execOfferUploadTapes(void) +{ + OfferUploadTapes(); +} + static void ToggleNetworkModeIfNeeded(void) { int font_title = FONT_TITLE_1; @@ -6571,6 +7241,22 @@ static void ToggleGameSpeedsListIfNeeded(void) DrawSetupScreen(); } +static void ToggleUseApiServerIfNeeded(void) +{ + if (runtime.use_api_server == setup.use_api_server) + return; + + runtime.use_api_server = setup.use_api_server; + + if (runtime.use_api_server) + { + if (setup.has_remaining_tapes) + setup.ask_for_uploading_tapes = TRUE; + + CheckApiServerTasks(); + } +} + static void ModifyGameSpeedIfNeeded(void) { if (strEqual(setup.vsync_mode, STR_VSYNC_MODE_OFF) || @@ -6618,6 +7304,12 @@ static struct void *related_value; } hide_related_entry_list[] = { + { &setup.network_server_hostname, execGadgetNetworkServer }, + { &setup.network_server_hostname, &network_server_text }, + + { &setup.scores_in_highscore_list, execSetupChooseScoresType }, + { &setup.scores_in_highscore_list, &scores_type_text }, + { &setup.game_frame_delay, execSetupChooseGameSpeed }, { &setup.game_frame_delay, &game_speed_text }, @@ -6693,6 +7385,21 @@ static struct { &setup.internal.menu_exit, execExitSetup }, { &setup.internal.menu_save_and_exit, execSaveAndExitSetup }, + { &setup.internal.menu_shortcuts_various, execSetupShortcuts1 }, + { &setup.internal.menu_shortcuts_focus, execSetupShortcuts2 }, + { &setup.internal.menu_shortcuts_tape, execSetupShortcuts3 }, + { &setup.internal.menu_shortcuts_sound, execSetupShortcuts4 }, + { &setup.internal.menu_shortcuts_snap, execSetupShortcuts5 }, + + { &setup.internal.info_title, execInfoTitleScreen }, + { &setup.internal.info_elements, execInfoElements }, + { &setup.internal.info_music, execInfoMusic }, + { &setup.internal.info_credits, execInfoCredits }, + { &setup.internal.info_program, execInfoProgram }, + { &setup.internal.info_version, execInfoVersion }, + { &setup.internal.info_levelset, execInfoLevelSet }, + { &setup.internal.info_exit, execExitInfo }, + { NULL, NULL } }; @@ -6730,11 +7437,15 @@ static struct TokenInfo setup_info_game[] = { TYPE_PLAYER, &setup.network_player_nr,"Preferred Network Player:" }, { TYPE_TEXT_INPUT, execGadgetNetworkServer, "Network Server Hostname:" }, { TYPE_STRING, &network_server_text, "" }, + { TYPE_SWITCH, &setup.use_api_server, "Use Highscore Server:" }, + { TYPE_ENTER_LIST, execSetupChooseScoresType,"Scores in Highscore List:" }, + { TYPE_STRING, &scores_type_text, "" }, + { TYPE_ENTER_LIST, execOfferUploadTapes, "Upload Tapes to Server" }, { TYPE_SWITCH, &setup.multiple_users, "Multiple Users/Teams:" }, { TYPE_YES_NO, &setup.input_on_focus, "Only Move Focussed Player:" }, { TYPE_SWITCH, &setup.time_limit, "Time Limit:" }, - { TYPE_SWITCH, &setup.handicap, "Handicap:" }, - { TYPE_SWITCH, &setup.skip_levels, "Skip Unsolved Levels:" }, + { TYPE_SWITCH, &setup.handicap, "Force Solving Levels:" }, + { TYPE_SWITCH, &setup.skip_levels, "Allow Skipping Levels:" }, { TYPE_SWITCH, &setup.increment_levels,"Increment Solved Levels:" }, { TYPE_SWITCH, &setup.auto_play_next_level,"Auto-play Next Level:" }, { TYPE_SWITCH, &setup.count_score_after_game,"Count Score After Game:" }, @@ -6742,7 +7453,9 @@ static struct TokenInfo setup_info_game[] = { TYPE_YES_NO, &setup.ask_on_game_over, "Ask on Game Over:" }, { TYPE_YES_NO, &setup.ask_on_quit_game, "Ask on Quit Game:" }, { TYPE_YES_NO, &setup.ask_on_quit_program, "Ask on Quit Program:" }, - { TYPE_SWITCH, &setup.autorecord, "Auto-Record Tapes:" }, + { TYPE_SWITCH, &setup.autorecord, "Auto-Record When Playing:" }, + { TYPE_SWITCH, &setup.autorecord_after_replay, "Auto-Record After Replay:" }, + { TYPE_SWITCH, &setup.auto_pause_on_start, "Start Game in Pause Mode:" }, { TYPE_ENTER_LIST, execSetupChooseGameSpeed, "Game Speed:" }, { TYPE_STRING, &game_speed_text, "" }, { TYPE_SWITCH, &setup.game_speed_extended, "Game Speed Extended List:" }, @@ -6752,7 +7465,8 @@ static struct TokenInfo setup_info_game[] = #endif { TYPE_ENTER_LIST, execSetupChooseSnapshotMode,"Game Engine Snapshot Mode:" }, { TYPE_STRING, &snapshot_mode_text, "" }, - { TYPE_SWITCH, &setup.show_snapshot_buttons,"Show Snapshot Buttons:" }, + { TYPE_SWITCH, &setup.show_load_save_buttons,"Show Load/Save Buttons:" }, + { TYPE_SWITCH, &setup.show_undo_redo_buttons,"Show Undo/Redo Buttons:" }, { TYPE_EMPTY, NULL, "" }, { TYPE_LEAVE_MENU, execSetupMain, "Back" }, @@ -6813,7 +7527,7 @@ static struct TokenInfo setup_info_editor[] = static struct TokenInfo setup_info_graphics[] = { -#if !defined(PLATFORM_ANDROID) +#if !defined(PLATFORM_ANDROID) && !defined(PLATFORM_EMSCRIPTEN) { TYPE_SWITCH, &setup.fullscreen, "Fullscreen:" }, { TYPE_ENTER_LIST, execSetupChooseWindowSize, "Window Scaling:" }, { TYPE_STRING, &window_size_text, "" }, @@ -6826,13 +7540,15 @@ static struct TokenInfo setup_info_graphics[] = { TYPE_ENTER_LIST, execSetupChooseScrollDelay, "Scroll Delay:" }, { TYPE_STRING, &scroll_delay_text, "" }, #endif +#if !defined(PLATFORM_EMSCRIPTEN) { TYPE_ENTER_LIST, execSetupChooseVsyncMode, "Vertical Sync (VSync):" }, { TYPE_STRING, &vsync_mode_text, "" }, +#endif { TYPE_SWITCH, &setup.fade_screens, "Fade Screens:" }, { TYPE_SWITCH, &setup.quick_switch, "Quick Player Focus Switch:" }, { TYPE_SWITCH, &setup.quick_doors, "Quick Menu Doors:" }, { TYPE_SWITCH, &setup.show_titlescreen,"Show Title Screens:" }, - { TYPE_SWITCH, &setup.toons, "Show Menu Animations:" }, + { TYPE_SWITCH, &setup.toons, "Show Toons:" }, { TYPE_SWITCH, &setup.small_game_graphics, "Small Game Graphics:" }, { TYPE_YES_NO_AUTO, &setup.debug.xsn_mode, debug_xsn_mode }, { TYPE_EMPTY, NULL, "" }, @@ -6989,6 +7705,10 @@ static struct TokenInfo setup_info_shortcuts_1[] = { TYPE_KEY, &setup.shortcut.save_game, "" }, { TYPE_KEYTEXT, NULL, "Quick Load Game from Tape:", }, { TYPE_KEY, &setup.shortcut.load_game, "" }, + { TYPE_KEYTEXT, NULL, "Restart Game:", }, + { TYPE_KEY, &setup.shortcut.restart_game, "" }, + { TYPE_KEYTEXT, NULL, "Replay & Pause Before End:", }, + { TYPE_KEY, &setup.shortcut.pause_before_end, "" }, { TYPE_KEYTEXT, NULL, "Start Game & Toggle Pause:", }, { TYPE_KEY, &setup.shortcut.toggle_pause, "" }, { TYPE_EMPTY, NULL, "" }, @@ -7083,7 +7803,7 @@ static Key getSetupKey(void) { case EVENT_KEYPRESS: { - key = GetEventKey((KeyEvent *)&event, TRUE); + key = GetEventKey((KeyEvent *)&event); // press 'Escape' or 'Enter' to keep the existing key binding if (key == KSYM_Escape || key == KSYM_Return) @@ -7197,10 +7917,10 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw) if (scrollbar_needed && xpos > MENU_SCREEN_START_XPOS) { int max_menu_text_length = 26; // maximum text length for classic menu - int font_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; + int font_xoffset = getFontDrawOffsetX(font_nr); int text_startx = mSX + MENU_SCREEN_START_XPOS * 32; int text_font_nr = getMenuTextFont(FONT_MENU_2); - int text_font_xoffset = getFontBitmapInfo(text_font_nr)->draw_xoffset; + int text_font_xoffset = getFontDrawOffsetX(text_font_nr); int text_width = max_menu_text_length * getFontWidth(text_font_nr); if (startx + font_xoffset < text_startx + text_width + text_font_xoffset) @@ -7223,11 +7943,11 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw) MENU_SCREEN_START_XPOS); int max_menu_text_length_medium = max_menu_text_length_big * 2; int check_font_nr = FONT_OPTION_ON; // known font that needs correction - int font1_xoffset = getFontBitmapInfo(font_nr)->draw_xoffset; - int font2_xoffset = getFontBitmapInfo(check_font_nr)->draw_xoffset; + int font1_xoffset = getFontDrawOffsetX(font_nr); + int font2_xoffset = getFontDrawOffsetX(check_font_nr); int text_startx = mSX + MENU_SCREEN_START_XPOS * 32; int text_font_nr = getMenuTextFont(FONT_MENU_2); - int text_font_xoffset = getFontBitmapInfo(text_font_nr)->draw_xoffset; + int text_font_xoffset = getFontDrawOffsetX(text_font_nr); int text_width = max_menu_text_length_medium * getFontWidth(text_font_nr); boolean correct_font_draw_xoffset = FALSE; @@ -7243,7 +7963,7 @@ static void drawSetupValue(int screen_pos, int setup_info_pos_raw) // (this can happen for extreme/wrong values for font draw offset) if (correct_font_draw_xoffset) { - font_draw_xoffset_old = getFontBitmapInfo(font_nr)->draw_xoffset; + font_draw_xoffset_old = getFontDrawOffsetX(font_nr); font_draw_xoffset_modified = TRUE; if (type & TYPE_KEY) @@ -7331,6 +8051,10 @@ static void changeSetupValue(int screen_pos, int setup_info_pos_raw, int dx) if (si->value == &setup.network_mode) ToggleNetworkModeIfNeeded(); + // API server mode may have changed at this point + if (si->value == &setup.use_api_server) + ToggleUseApiServerIfNeeded(); + // game speed list may have changed at this point if (si->value == &setup.game_speed_extended) ToggleGameSpeedsListIfNeeded(); @@ -7865,14 +8589,18 @@ static boolean CustomizeKeyboardMain(int player_nr) while (!finished) { Event event; + DelayCounter event_frame_delay = { GAME_FRAME_DELAY }; - if (NextValidEvent(&event)) + // reset frame delay counter directly after updating screen + ResetDelayCounter(&event_frame_delay); + + while (NextValidEvent(&event)) { switch (event.type) { case EVENT_KEYPRESS: { - Key key = GetEventKey((KeyEvent *)&event, FALSE); + Key key = GetEventKey((KeyEvent *)&event); // press 'Escape' to abort and keep the old key bindings if (key == KSYM_Escape) @@ -7937,6 +8665,10 @@ static boolean CustomizeKeyboardMain(int player_nr) HandleOtherEvents(&event); break; } + + // do not handle events for longer than standard frame delay period + if (DelayReached(&event_frame_delay)) + break; } BackToFront(); @@ -7959,8 +8691,7 @@ void CustomizeKeyboard(int player_nr) int font_height = getFontHeight(font_nr); int ypos1 = SYSIZE / 2 - font_height * 2; int ypos2 = SYSIZE / 2 - font_height * 1; - unsigned int wait_frame_delay = 0; - unsigned int wait_frame_delay_value = 2000; + DelayCounter wait_frame_delay = { 2000 }; ResetDelayCounter(&wait_frame_delay); @@ -7969,7 +8700,7 @@ void CustomizeKeyboard(int player_nr) DrawTextSCentered(ypos1, font_nr, "Keyboard"); DrawTextSCentered(ypos2, font_nr, "configured!"); - while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + while (!DelayReached(&wait_frame_delay)) BackToFront(); ClearEventQueue(); @@ -8039,11 +8770,6 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) { 282, 210, MARKER_AXIS_Y, "righty", }, }; - unsigned int event_frame_delay = 0; - unsigned int event_frame_delay_value = GAME_FRAME_DELAY; - - ResetDelayCounter(&event_frame_delay); - if (!bitmaps_initialized) { controller = LoadCustomImage("joystick/controller.png"); @@ -8175,6 +8901,11 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) screen_initialized = TRUE; + DelayCounter event_frame_delay = { GAME_FRAME_DELAY }; + + // reset frame delay counter directly after updating screen + ResetDelayCounter(&event_frame_delay); + while (NextValidEvent(&event)) { switch (event.type) @@ -8308,7 +9039,7 @@ static boolean ConfigureJoystickMapButtonsAndAxes(SDL_Joystick *joystick) } // do not handle events for longer than standard frame delay period - if (DelayReached(&event_frame_delay, event_frame_delay_value)) + if (DelayReached(&event_frame_delay)) break; } } @@ -8392,8 +9123,7 @@ void ConfigureJoystick(int player_nr) int font_height = getFontHeight(font_nr); int ypos1 = SYSIZE / 2 - font_height * 2; int ypos2 = SYSIZE / 2 - font_height * 1; - unsigned int wait_frame_delay = 0; - unsigned int wait_frame_delay_value = 2000; + DelayCounter wait_frame_delay = { 2000 }; ResetDelayCounter(&wait_frame_delay); @@ -8404,7 +9134,7 @@ void ConfigureJoystick(int player_nr) DrawTextSCentered(ypos1, font_nr, message1); DrawTextSCentered(ypos2, font_nr, message2); - while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + while (!DelayReached(&wait_frame_delay)) BackToFront(); ClearEventQueue(); @@ -8521,7 +9251,7 @@ static boolean ConfigureVirtualButtonsMain(void) case EVENT_KEYPRESS: { - Key key = GetEventKey((KeyEvent *)&event, FALSE); + Key key = GetEventKey((KeyEvent *)&event); action = (key == KSYM_Escape ? ACTION_ESCAPE : key == KSYM_BackSpace || @@ -8722,8 +9452,7 @@ void ConfigureVirtualButtons(void) int font_height = getFontHeight(font_nr); int ypos1 = SYSIZE / 2 - font_height * 2; int ypos2 = SYSIZE / 2 - font_height * 1; - unsigned int wait_frame_delay = 0; - unsigned int wait_frame_delay_value = 2000; + DelayCounter wait_frame_delay = { 2000 }; ResetDelayCounter(&wait_frame_delay); @@ -8732,7 +9461,7 @@ void ConfigureVirtualButtons(void) DrawTextSCentered(ypos1, font_nr, "Virtual buttons"); DrawTextSCentered(ypos2, font_nr, "configured!"); - while (!DelayReached(&wait_frame_delay, wait_frame_delay_value)) + while (!DelayReached(&wait_frame_delay)) BackToFront(); ClearEventQueue(); @@ -8746,6 +9475,8 @@ void DrawSetupScreen(void) if (setup_mode == SETUP_MODE_INPUT) DrawSetupScreen_Input(); + else if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE) + DrawChooseTree(&scores_type_current); else if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED) DrawChooseTree(&game_speed_current); else if (setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY) @@ -8828,6 +9559,8 @@ void HandleSetupScreen(int mx, int my, int dx, int dy, int button) { if (setup_mode == SETUP_MODE_INPUT) HandleSetupScreen_Input(mx, my, dx, dy, button); + else if (setup_mode == SETUP_MODE_CHOOSE_SCORES_TYPE) + HandleChooseTree(mx, my, dx, dy, button, &scores_type_current); else if (setup_mode == SETUP_MODE_CHOOSE_GAME_SPEED) HandleChooseTree(mx, my, dx, dy, button, &game_speed_current); else if (setup_mode == SETUP_MODE_CHOOSE_SCROLL_DELAY) @@ -8876,23 +9609,16 @@ void HandleSetupScreen(int mx, int my, int dx, int dy, int button) void HandleGameActions(void) { - if (setup.ask_on_game_over) - CheckGameOver(); - - if (game.restart_game_message != NULL) - { - RequestRestartGame(game.restart_game_message); - + if (CheckRestartGame()) return; - } if (game_status != GAME_MODE_PLAYING) return; - GameActions(); // main game loop + GameActions(); // main game loop if (tape.auto_play && !tape.playing) - AutoPlayTapes(); // continue automatically playing next tape + AutoPlayTapesContinue(); // continue automatically playing next tape } @@ -8900,7 +9626,7 @@ void HandleGameActions(void) static struct { - int gfx_unpressed, gfx_pressed; + int gfx_unpressed, gfx_pressed, gfx_active; struct MenuPosInfo *pos; boolean *check_value; int gadget_id; @@ -8911,7 +9637,7 @@ static struct } menubutton_info[NUM_SCREEN_MENUBUTTONS] = { { - IMG_MENU_BUTTON_PREV_LEVEL, IMG_MENU_BUTTON_PREV_LEVEL_ACTIVE, + IMG_MENU_BUTTON_PREV_LEVEL, IMG_MENU_BUTTON_PREV_LEVEL_ACTIVE, -1, &menu.main.button.prev_level, NULL, SCREEN_CTRL_ID_PREV_LEVEL, SCREEN_MASK_MAIN, @@ -8919,7 +9645,7 @@ static struct FALSE, "previous level" }, { - IMG_MENU_BUTTON_NEXT_LEVEL, IMG_MENU_BUTTON_NEXT_LEVEL_ACTIVE, + IMG_MENU_BUTTON_NEXT_LEVEL, IMG_MENU_BUTTON_NEXT_LEVEL_ACTIVE, -1, &menu.main.button.next_level, NULL, SCREEN_CTRL_ID_NEXT_LEVEL, SCREEN_MASK_MAIN, @@ -8927,7 +9653,47 @@ static struct FALSE, "next level" }, { - IMG_MENU_BUTTON_FIRST_LEVEL, IMG_MENU_BUTTON_FIRST_LEVEL_ACTIVE, + IMG_MENU_BUTTON_PREV_LEVEL2, IMG_MENU_BUTTON_PREV_LEVEL2_ACTIVE, -1, + &menu.scores.button.prev_level, NULL, + SCREEN_CTRL_ID_PREV_LEVEL2, + SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "previous level" + }, + { + IMG_MENU_BUTTON_NEXT_LEVEL2, IMG_MENU_BUTTON_NEXT_LEVEL2_ACTIVE, -1, + &menu.scores.button.next_level, NULL, + SCREEN_CTRL_ID_NEXT_LEVEL2, + SCREEN_MASK_SCORES | SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "next level" + }, + { + IMG_MENU_BUTTON_PREV_SCORE, IMG_MENU_BUTTON_PREV_SCORE_ACTIVE, -1, + &menu.scores.button.prev_score, NULL, + SCREEN_CTRL_ID_PREV_SCORE, + SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "previous score" + }, + { + IMG_MENU_BUTTON_NEXT_SCORE, IMG_MENU_BUTTON_NEXT_SCORE_ACTIVE, -1, + &menu.scores.button.next_score, NULL, + SCREEN_CTRL_ID_NEXT_SCORE, + SCREEN_MASK_SCORES_INFO, + GD_EVENT_PRESSED | GD_EVENT_REPEATED, + FALSE, "next score" + }, + { + IMG_MENU_BUTTON_PLAY_TAPE, IMG_MENU_BUTTON_PLAY_TAPE, -1, + &menu.scores.button.play_tape, NULL, + SCREEN_CTRL_ID_PLAY_TAPE, + SCREEN_MASK_SCORES_INFO, + GD_EVENT_RELEASED, + FALSE, "play tape" + }, + { + IMG_MENU_BUTTON_FIRST_LEVEL, IMG_MENU_BUTTON_FIRST_LEVEL_ACTIVE, -1, &menu.main.button.first_level, NULL, SCREEN_CTRL_ID_FIRST_LEVEL, SCREEN_MASK_MAIN, @@ -8935,7 +9701,7 @@ static struct FALSE, "first level" }, { - IMG_MENU_BUTTON_LAST_LEVEL, IMG_MENU_BUTTON_LAST_LEVEL_ACTIVE, + IMG_MENU_BUTTON_LAST_LEVEL, IMG_MENU_BUTTON_LAST_LEVEL_ACTIVE, -1, &menu.main.button.last_level, NULL, SCREEN_CTRL_ID_LAST_LEVEL, SCREEN_MASK_MAIN, @@ -8943,7 +9709,7 @@ static struct FALSE, "last level" }, { - IMG_MENU_BUTTON_LEVEL_NUMBER, IMG_MENU_BUTTON_LEVEL_NUMBER_ACTIVE, + IMG_MENU_BUTTON_LEVEL_NUMBER, IMG_MENU_BUTTON_LEVEL_NUMBER_ACTIVE, -1, &menu.main.button.level_number, NULL, SCREEN_CTRL_ID_LEVEL_NUMBER, SCREEN_MASK_MAIN, @@ -8951,7 +9717,7 @@ static struct FALSE, "level number" }, { - IMG_MENU_BUTTON_LEFT, IMG_MENU_BUTTON_LEFT_ACTIVE, + IMG_MENU_BUTTON_LEFT, IMG_MENU_BUTTON_LEFT_ACTIVE, -1, &menu.setup.button.prev_player, NULL, SCREEN_CTRL_ID_PREV_PLAYER, SCREEN_MASK_INPUT, @@ -8959,7 +9725,7 @@ static struct FALSE, "previous player" }, { - IMG_MENU_BUTTON_RIGHT, IMG_MENU_BUTTON_RIGHT_ACTIVE, + IMG_MENU_BUTTON_RIGHT, IMG_MENU_BUTTON_RIGHT_ACTIVE, -1, &menu.setup.button.next_player, NULL, SCREEN_CTRL_ID_NEXT_PLAYER, SCREEN_MASK_INPUT, @@ -8967,7 +9733,7 @@ static struct FALSE, "next player" }, { - IMG_MENU_BUTTON_INSERT_SOLUTION, IMG_MENU_BUTTON_INSERT_SOLUTION_ACTIVE, + IMG_MENU_BUTTON_INSERT_SOLUTION, IMG_MENU_BUTTON_INSERT_SOLUTION_ACTIVE, -1, &menu.main.button.insert_solution, NULL, SCREEN_CTRL_ID_INSERT_SOLUTION, SCREEN_MASK_MAIN_HAS_SOLUTION, @@ -8975,7 +9741,7 @@ static struct FALSE, "insert solution tape" }, { - IMG_MENU_BUTTON_PLAY_SOLUTION, IMG_MENU_BUTTON_PLAY_SOLUTION_ACTIVE, + IMG_MENU_BUTTON_PLAY_SOLUTION, IMG_MENU_BUTTON_PLAY_SOLUTION_ACTIVE, -1, &menu.main.button.play_solution, NULL, SCREEN_CTRL_ID_PLAY_SOLUTION, SCREEN_MASK_MAIN_HAS_SOLUTION, @@ -8983,7 +9749,16 @@ static struct FALSE, "play solution tape" }, { - IMG_MENU_BUTTON_SWITCH_ECS_AGA, IMG_MENU_BUTTON_SWITCH_ECS_AGA_ACTIVE, + IMG_MENU_BUTTON_LEVELSET_INFO, IMG_MENU_BUTTON_LEVELSET_INFO_PRESSED, + IMG_MENU_BUTTON_LEVELSET_INFO_ACTIVE, + &menu.main.button.levelset_info, NULL, + SCREEN_CTRL_ID_LEVELSET_INFO, + SCREEN_MASK_MAIN_HAS_SET_INFO, + GD_EVENT_RELEASED, + FALSE, "show level set info" + }, + { + IMG_MENU_BUTTON_SWITCH_ECS_AGA, IMG_MENU_BUTTON_SWITCH_ECS_AGA_ACTIVE, -1, &menu.main.button.switch_ecs_aga, &setup.prefer_aga_graphics, SCREEN_CTRL_ID_SWITCH_ECS_AGA, SCREEN_MASK_MAIN, @@ -8991,7 +9766,7 @@ static struct FALSE, "switch ECS/AGA chipset" }, { - IMG_MENU_BUTTON_TOUCH_BACK, IMG_MENU_BUTTON_TOUCH_BACK, + IMG_MENU_BUTTON_TOUCH_BACK, IMG_MENU_BUTTON_TOUCH_BACK, -1, &menu.setup.button.touch_back, NULL, SCREEN_CTRL_ID_TOUCH_PREV_PAGE, SCREEN_MASK_TOUCH, @@ -8999,7 +9774,7 @@ static struct TRUE, "previous page" }, { - IMG_MENU_BUTTON_TOUCH_NEXT, IMG_MENU_BUTTON_TOUCH_NEXT, + IMG_MENU_BUTTON_TOUCH_NEXT, IMG_MENU_BUTTON_TOUCH_NEXT, -1, &menu.setup.button.touch_next, NULL, SCREEN_CTRL_ID_TOUCH_NEXT_PAGE, SCREEN_MASK_TOUCH, @@ -9007,7 +9782,7 @@ static struct TRUE, "next page" }, { - IMG_MENU_BUTTON_TOUCH_BACK2, IMG_MENU_BUTTON_TOUCH_BACK2, + IMG_MENU_BUTTON_TOUCH_BACK2, IMG_MENU_BUTTON_TOUCH_BACK2, -1, &menu.setup.button.touch_back2, NULL, SCREEN_CTRL_ID_TOUCH_PREV_PAGE2, SCREEN_MASK_TOUCH2, @@ -9015,7 +9790,7 @@ static struct TRUE, "previous page" }, { - IMG_MENU_BUTTON_TOUCH_NEXT2, IMG_MENU_BUTTON_TOUCH_NEXT2, + IMG_MENU_BUTTON_TOUCH_NEXT2, IMG_MENU_BUTTON_TOUCH_NEXT2, -1, &menu.setup.button.touch_next2, NULL, SCREEN_CTRL_ID_TOUCH_NEXT_PAGE2, SCREEN_MASK_TOUCH2, @@ -9095,10 +9870,17 @@ static void CreateScreenMenubuttons(void) for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++) { struct MenuPosInfo *pos = menubutton_info[i].pos; + int screen_mask = menubutton_info[i].screen_mask; boolean is_touch_button = menubutton_info[i].is_touch_button; boolean is_check_button = menubutton_info[i].check_value != NULL; + boolean is_score_button = (screen_mask & SCREEN_MASK_SCORES_INFO); + boolean has_gfx_pressed = (menubutton_info[i].gfx_pressed == + menubutton_info[i].gfx_unpressed); + boolean has_gfx_active = (menubutton_info[i].gfx_active != -1); Bitmap *gd_bitmap_unpressed, *gd_bitmap_pressed; + Bitmap *gd_bitmap_unpressed_alt, *gd_bitmap_pressed_alt; int gfx_unpressed, gfx_pressed; + int gfx_unpressed_alt, gfx_pressed_alt; int x, y, width, height; int gd_x1, gd_x2, gd_y1, gd_y2; int gd_x1a, gd_x2a, gd_y1a, gd_y2a; @@ -9106,6 +9888,10 @@ static void CreateScreenMenubuttons(void) int type = GD_TYPE_NORMAL_BUTTON; boolean checked = FALSE; + // do not use touch buttons if overlay touch buttons are disabled + if (is_touch_button && !setup.touch.overlay_buttons) + continue; + event_mask = menubutton_info[i].event_mask; x = (is_touch_button ? pos->x : mSX + GDI_ACTIVE_POS(pos->x)); @@ -9116,18 +9902,35 @@ static void CreateScreenMenubuttons(void) gfx_unpressed = menubutton_info[i].gfx_unpressed; gfx_pressed = menubutton_info[i].gfx_pressed; + gfx_unpressed_alt = gfx_unpressed; + gfx_pressed_alt = gfx_pressed; + + if (has_gfx_active) + { + gfx_unpressed_alt = menubutton_info[i].gfx_active; + + type = GD_TYPE_CHECK_BUTTON_2; + + if (menubutton_info[i].check_value != NULL) + checked = *menubutton_info[i].check_value; + } + gd_bitmap_unpressed = graphic_info[gfx_unpressed].bitmap; gd_bitmap_pressed = graphic_info[gfx_pressed].bitmap; + gd_bitmap_unpressed_alt = graphic_info[gfx_unpressed_alt].bitmap; + gd_bitmap_pressed_alt = graphic_info[gfx_pressed_alt].bitmap; + gd_x1 = graphic_info[gfx_unpressed].src_x; gd_y1 = graphic_info[gfx_unpressed].src_y; gd_x2 = graphic_info[gfx_pressed].src_x; gd_y2 = graphic_info[gfx_pressed].src_y; - gd_x1a = gd_x1; - gd_y1a = gd_y1; - gd_x2a = gd_x2; - gd_y2a = gd_y2; - if (is_touch_button) + gd_x1a = graphic_info[gfx_unpressed_alt].src_x; + gd_y1a = graphic_info[gfx_unpressed_alt].src_y; + gd_x2a = graphic_info[gfx_pressed_alt].src_x; + gd_y2a = graphic_info[gfx_pressed_alt].src_y; + + if (has_gfx_pressed) { gd_x2 += graphic_info[gfx_pressed].pressed_xoffset; gd_y2 += graphic_info[gfx_pressed].pressed_yoffset; @@ -9141,7 +9944,52 @@ static void CreateScreenMenubuttons(void) gd_y2a += graphic_info[gfx_pressed].active_yoffset; type = GD_TYPE_CHECK_BUTTON; - checked = *menubutton_info[i].check_value; + + if (menubutton_info[i].check_value != NULL) + checked = *menubutton_info[i].check_value; + } + + if (is_score_button) + { + // if x/y set to -1, dynamically place buttons next to title text + int title_width = getTextWidth(INFOTEXT_SCORE_ENTRY, FONT_TITLE_1); + + // special compatibility handling for "Snake Bite" graphics set + if (strPrefix(leveldir_current->identifier, "snake_bite")) + title_width = strlen(INFOTEXT_SCORE_ENTRY) * 32; + + // use "SX" here to center buttons (ignore horizontal draw offset) + if (pos->x == -1) + x = (id == SCREEN_CTRL_ID_PREV_LEVEL2 ? + SX + (SXSIZE - title_width) / 2 - width * 3 / 2 : + id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? + SX + (SXSIZE + title_width) / 2 + width / 2 : 0); + + // use "mSY" here to place buttons (respect vertical draw offset) + if (pos->y == -1) + y = (id == SCREEN_CTRL_ID_PREV_LEVEL2 || + id == SCREEN_CTRL_ID_NEXT_LEVEL2 ? mSY + MENU_TITLE1_YPOS : 0); + } + + if (id == SCREEN_CTRL_ID_LEVELSET_INFO) + { + if (pos->x == -1 && pos->y == -1) + { + // use "SX" here to place button (ignore draw offsets) + x = SX + SXSIZE - 2 * TILESIZE; + y = SY + SYSIZE - 2 * TILESIZE; + + // special compatibility handling for "BD2K3" graphics set + if (strPrefix(leveldir_current->identifier, "BD2K3")) + x = SX + TILESIZE + MINI_TILESIZE; + + // special compatibility handling for "jue0" graphics set + if (strPrefix(artwork.gfx_current_identifier, "jue0")) + { + x = SX + SXSIZE - 4 * TILESIZE; + y = SY + SYSIZE - 3 * TILESIZE; + } + } } gi = CreateGadget(GDI_CUSTOM_ID, id, @@ -9157,8 +10005,8 @@ static void CreateScreenMenubuttons(void) GDI_CHECKED, checked, GDI_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1, gd_y1, GDI_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2, gd_y2, - GDI_ALT_DESIGN_UNPRESSED, gd_bitmap_unpressed, gd_x1a, gd_y1a, - GDI_ALT_DESIGN_PRESSED, gd_bitmap_pressed, gd_x2a, gd_y2a, + GDI_ALT_DESIGN_UNPRESSED, gd_bitmap_unpressed_alt, gd_x1a, gd_y1a, + GDI_ALT_DESIGN_PRESSED, gd_bitmap_pressed_alt, gd_x2a, gd_y2a, GDI_DIRECT_DRAW, FALSE, GDI_OVERLAY_TOUCH_BUTTON, is_touch_button, GDI_EVENT_MASK, event_mask, @@ -9385,6 +10233,15 @@ void FreeScreenGadgets(void) FreeGadget(screen_gadget[i]); } +static void RedrawScreenMenuGadgets(int screen_mask) +{ + int i; + + for (i = 0; i < NUM_SCREEN_MENUBUTTONS; i++) + if (screen_mask & menubutton_info[i].screen_mask) + RedrawGadget(screen_gadget[menubutton_info[i].gadget_id]); +} + static void MapScreenMenuGadgets(int screen_mask) { int i; @@ -9435,11 +10292,54 @@ static void MapScreenGadgets(int num_entries) MapGadget(screen_gadget[scrollbar_info[i].gadget_id]); } +static void UnmapScreenGadgets() +{ + int i; + + for (i = 0; i < NUM_SCREEN_SCROLLBUTTONS; i++) + UnmapGadget(screen_gadget[scrollbutton_info[i].gadget_id]); + + for (i = 0; i < NUM_SCREEN_SCROLLBARS; i++) + UnmapGadget(screen_gadget[scrollbar_info[i].gadget_id]); +} + static void MapScreenTreeGadgets(TreeInfo *ti) { MapScreenGadgets(numTreeInfoInGroup(ti)); } +static void UnmapScreenTreeGadgets(void) +{ + UnmapScreenGadgets(); +} + +static void AdjustScoreInfoButtons_SelectScore(int x, int y1, int y2) +{ + struct GadgetInfo *gi_1 = screen_gadget[SCREEN_CTRL_ID_PREV_SCORE]; + struct GadgetInfo *gi_2 = screen_gadget[SCREEN_CTRL_ID_NEXT_SCORE]; + struct MenuPosInfo *pos_1 = menubutton_info[SCREEN_CTRL_ID_PREV_SCORE].pos; + struct MenuPosInfo *pos_2 = menubutton_info[SCREEN_CTRL_ID_NEXT_SCORE].pos; + + if (pos_1->x == -1 && pos_1->y == -1) + ModifyGadget(gi_1, GDI_X, x, GDI_Y, y1, GDI_END); + + if (pos_2->x == -1 && pos_2->y == -1) + ModifyGadget(gi_2, GDI_X, x, GDI_Y, y2, GDI_END); +} + +static void AdjustScoreInfoButtons_PlayTape(int x, int y, boolean visible) +{ + struct GadgetInfo *gi = screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]; + struct MenuPosInfo *pos = menubutton_info[SCREEN_CTRL_ID_PLAY_TAPE].pos; + + // set gadget position dynamically, pre-defined or off-screen + int xx = (visible ? (pos->x == -1 ? x : pos->x) : POS_OFFSCREEN); + int yy = (visible ? (pos->y == -1 ? y : pos->y) : POS_OFFSCREEN); + + ModifyGadget(gi, GDI_X, xx, GDI_Y, yy, GDI_END); + MapGadget(gi); // (needed if deactivated on last score page) +} + static void HandleScreenGadgets(struct GadgetInfo *gi) { int id = gi->custom_id; @@ -9458,6 +10358,26 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) HandleMainMenu_SelectLevel(step, +1, NO_DIRECT_LEVEL_SELECT); break; + case SCREEN_CTRL_ID_PREV_LEVEL2: + HandleHallOfFame_SelectLevel(step, -1); + break; + + case SCREEN_CTRL_ID_NEXT_LEVEL2: + HandleHallOfFame_SelectLevel(step, +1); + break; + + case SCREEN_CTRL_ID_PREV_SCORE: + HandleScoreInfo_SelectScore(step, -1); + break; + + case SCREEN_CTRL_ID_NEXT_SCORE: + HandleScoreInfo_SelectScore(step, +1); + break; + + case SCREEN_CTRL_ID_PLAY_TAPE: + HandleScoreInfo_PlayTape(); + break; + case SCREEN_CTRL_ID_FIRST_LEVEL: HandleMainMenu_SelectLevel(MAX_LEVELS, -1, NO_DIRECT_LEVEL_SELECT); break; @@ -9488,6 +10408,10 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) PlaySolutionTape(); break; + case SCREEN_CTRL_ID_LEVELSET_INFO: + DrawInfoScreen_FromMainMenu(INFO_MODE_LEVELSET); + break; + case SCREEN_CTRL_ID_SWITCH_ECS_AGA: setup.prefer_aga_graphics = !setup.prefer_aga_graphics; DrawMainMenu(); @@ -9502,41 +10426,47 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) case SCREEN_CTRL_ID_SCROLL_UP: if (game_status == GAME_MODE_NAMES) - HandleChoosePlayerName(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_LEVELS) - HandleChooseLevelSet(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_LEVELNR) - HandleChooseLevelNr(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_SETUP) - HandleSetupScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + HandleSetupScreen(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_INFO) - HandleInfoScreen(0,0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + HandleInfoScreen(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); + else if (game_status == GAME_MODE_SCORES) + HandleHallOfFame(0, 0, 0, -1 * SCROLL_LINE, MB_MENU_MARK); break; case SCREEN_CTRL_ID_SCROLL_DOWN: if (game_status == GAME_MODE_NAMES) - HandleChoosePlayerName(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_LEVELS) - HandleChooseLevelSet(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_LEVELNR) - HandleChooseLevelNr(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_SETUP) - HandleSetupScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + HandleSetupScreen(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); else if (game_status == GAME_MODE_INFO) - HandleInfoScreen(0,0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + HandleInfoScreen(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); + else if (game_status == GAME_MODE_SCORES) + HandleHallOfFame(0, 0, 0, +1 * SCROLL_LINE, MB_MENU_MARK); break; case SCREEN_CTRL_ID_SCROLL_VERTICAL: if (game_status == GAME_MODE_NAMES) - HandleChoosePlayerName(0,0,999,gi->event.item_position,MB_MENU_INITIALIZE); + HandleChoosePlayerName(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE); else if (game_status == GAME_MODE_LEVELS) - HandleChooseLevelSet(0,0,999,gi->event.item_position,MB_MENU_INITIALIZE); + HandleChooseLevelSet(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE); else if (game_status == GAME_MODE_LEVELNR) - HandleChooseLevelNr(0,0,999,gi->event.item_position,MB_MENU_INITIALIZE); + HandleChooseLevelNr(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE); else if (game_status == GAME_MODE_SETUP) - HandleSetupScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE); + HandleSetupScreen(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE); else if (game_status == GAME_MODE_INFO) - HandleInfoScreen(0,0, 999,gi->event.item_position,MB_MENU_INITIALIZE); + HandleInfoScreen(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE); + else if (game_status == GAME_MODE_SCORES) + HandleHallOfFame(0, 0, 999, gi->event.item_position, MB_MENU_INITIALIZE); break; case SCREEN_CTRL_ID_NETWORK_SERVER: @@ -9569,6 +10499,12 @@ static void HandleScreenGadgets(struct GadgetInfo *gi) } } +void HandleScreenGadgetKeys(Key key) +{ + if (key == setup.shortcut.tape_play) + HandleScreenGadgets(screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]); +} + void DumpScreenIdentifiers(void) { int i; @@ -9683,3 +10619,142 @@ void DrawScreenAfterAddingSet(char *tree_subdir_new, int tree_type) } } } + +static int UploadTapes(void) +{ + SetGameStatus(GAME_MODE_LOADING); + + FadeSetEnterScreen(); + FadeOut(REDRAW_ALL); + + ClearRectangle(drawto, 0, 0, WIN_XSIZE, WIN_YSIZE); + + FadeIn(REDRAW_ALL); + + DrawInitTextHead("Uploading tapes"); + + global.autoplay_mode = AUTOPLAY_MODE_UPLOAD; + global.autoplay_leveldir = "ALL"; + global.autoplay_all = TRUE; + + int num_tapes_uploaded = AutoPlayTapes(); + + global.autoplay_mode = AUTOPLAY_MODE_NONE; + global.autoplay_leveldir = NULL; + global.autoplay_all = FALSE; + + SetGameStatus(GAME_MODE_MAIN); + + DrawMainMenu(); + + return num_tapes_uploaded; +} + +static boolean OfferUploadTapes(void) +{ + if (!Request(setup.has_remaining_tapes ? + "Upload missing tapes to the high score server now?" : + "Upload all your tapes to the high score server now?", REQ_ASK)) + return FALSE; + + // when uploading tapes, make sure that high score server is enabled + runtime.use_api_server = setup.use_api_server = TRUE; + + int num_tapes_uploaded = UploadTapes(); + char message[100]; + + if (num_tapes_uploaded < 0) + { + num_tapes_uploaded = -num_tapes_uploaded - 1; + + if (num_tapes_uploaded == 0) + sprintf(message, "Upload failed! No tapes uploaded!"); + else if (num_tapes_uploaded == 1) + sprintf(message, "Upload failed! Only 1 tape uploaded!"); + else + sprintf(message, "Upload failed! Only %d tapes uploaded!", + num_tapes_uploaded); + + Request(message, REQ_CONFIRM); + + // if uploading tapes failed, add tape upload entry to setup menu + setup.provide_uploading_tapes = TRUE; + setup.has_remaining_tapes = TRUE; + + SaveSetup_ServerSetup(); + + return FALSE; + } + + if (num_tapes_uploaded == 0) + sprintf(message, "No tapes uploaded!"); + else if (num_tapes_uploaded == 1) + sprintf(message, "1 tape uploaded!"); + else + sprintf(message, "%d tapes uploaded!", num_tapes_uploaded); + + Request(message, REQ_CONFIRM); + + if (num_tapes_uploaded > 0) + Request("New scores will be visible after a few minutes!", REQ_CONFIRM); + + // after all tapes have been uploaded, remove entry from setup menu + setup.provide_uploading_tapes = FALSE; + setup.has_remaining_tapes = FALSE; + + SaveSetup_ServerSetup(); + + return TRUE; +} + +static void CheckUploadTapes(void) +{ + if (!setup.ask_for_uploading_tapes) + return; + + // after asking for uploading tapes, do not ask again + setup.ask_for_uploading_tapes = FALSE; + setup.ask_for_remaining_tapes = FALSE; + + if (directoryExists(getTapeDir(NULL))) + { + boolean tapes_uploaded = OfferUploadTapes(); + + if (!tapes_uploaded) + { + Request(setup.has_remaining_tapes ? + "You can upload missing tapes from the setup menu later!" : + "You can upload your tapes from the setup menu later!", + REQ_CONFIRM); + } + } + else + { + // if tapes directory does not exist yet, never offer uploading all tapes + setup.provide_uploading_tapes = FALSE; + } + + SaveSetup_ServerSetup(); +} + +static void UpgradePlayerUUID(void) +{ + ApiResetUUIDAsThread(getUUID()); +} + +static void CheckUpgradePlayerUUID(void) +{ + if (setup.player_version > 1) + return; + + UpgradePlayerUUID(); +} + +void CheckApiServerTasks(void) +{ + // check if the player's UUID has to be upgraded + CheckUpgradePlayerUUID(); + + // check if there are any tapes to be uploaded + CheckUploadTapes(); +} diff --git a/src/screens.h b/src/screens.h index d79745df..deba425c 100644 --- a/src/screens.h +++ b/src/screens.h @@ -22,8 +22,9 @@ void DrawMainMenuExt(int); void DrawAndFadeInMainMenu(int); void DrawMainMenu(void); -void DrawHallOfFame(int, int); +void DrawHallOfFame(int); void DrawScreenAfterAddingSet(char *, int); +void DrawInfoScreen_FromMainMenu(int); void RedrawSetupScreenAfterFullscreenToggle(void); void RedrawSetupScreenAfterScreenRotation(int); @@ -34,10 +35,12 @@ void HandleChoosePlayerName(int, int, int, int, int); void HandleChooseLevelSet(int, int, int, int, int); void HandleChooseLevelNr(int, int, int, int, int); void HandleHallOfFame(int, int, int, int, int); +void HandleScoreInfo(int, int, int, int, int); void HandleInfoScreen(int, int, int, int, int); void HandleSetupScreen(int, int, int, int, int); void HandleTypeName(Key); void HandleGameActions(void); +void HandleScreenGadgetKeys(Key); void CreateScreenGadgets(void); void FreeScreenGadgets(void); @@ -47,4 +50,6 @@ void setHideRelatedSetupEntries(void); void DumpScreenIdentifiers(void); boolean DoScreenAction(int); +void CheckApiServerTasks(void); + #endif // SCREENS_H diff --git a/src/tape.c b/src/tape.c index c1e4bcea..f16e7ec9 100644 --- a/src/tape.c +++ b/src/tape.c @@ -18,6 +18,8 @@ #include "files.h" #include "network.h" #include "anim.h" +#include "api.h" + #define DEBUG_TAPE_WHEN_PLAYING FALSE @@ -312,7 +314,8 @@ static void DrawVideoDisplay_DateTime(unsigned int state, unsigned int value) char s[MAX_DATETIME_STRING_SIZE]; int year2 = value / 10000; int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2); - int month_index = (value / 100) % 100; + int month_index_raw = (value / 100) % 100; + int month_index = month_index_raw % 12; // prevent invalid index int month = month_index + 1; int day = value % 100; @@ -470,8 +473,44 @@ void TapeDeactivateDisplayOff(boolean redraw_display) // tape logging functions // ============================================================================ +struct AutoPlayInfo +{ + LevelDirTree *leveldir; + boolean all_levelsets; + int last_level_nr; + int level_nr; + int num_levels_played; + int num_levels_solved; + int num_tapes_patched; + int num_tape_missing; + boolean level_failed[MAX_TAPES_PER_SET]; + char *tape_filename; +}; + static char tape_patch_info[MAX_OUTPUT_LINESIZE]; +static void PrintTapeReplayHeader(struct AutoPlayInfo *autoplay) +{ + PrintLine("=", 79); + + if (global.autoplay_mode == AUTOPLAY_MODE_FIX) + Print("Automatically fixing level tapes\n"); + else if (global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) + Print("Automatically uploading level tapes\n"); + else + Print("Automatically playing level tapes\n"); + + PrintLine("-", 79); + Print("Level series identifier: '%s'\n", autoplay->leveldir->identifier); + Print("Level series name: '%s'\n", autoplay->leveldir->name); + Print("Level series author: '%s'\n", autoplay->leveldir->author); + Print("Number of levels: %d\n", autoplay->leveldir->levels); + PrintLine("=", 79); + Print("\n"); + + DrawInitTextItem(autoplay->leveldir->name); +} + static void PrintTapeReplayProgress(boolean replay_finished) { static unsigned int counter_last = -1; @@ -512,6 +551,79 @@ static void PrintTapeReplayProgress(boolean replay_finished) } } +static void PrintTapeReplaySummary(struct AutoPlayInfo *autoplay) +{ + char *autoplay_status = + (autoplay->num_levels_played == autoplay->num_levels_solved && + autoplay->num_levels_played > 0 ? " OK " : "WARN"); + int autoplay_percent = + (autoplay->num_levels_played ? + autoplay->num_levels_solved * 100 / autoplay->num_levels_played : 0); + int i; + + Print("\n"); + PrintLine("=", 79); + Print("Number of levels played: %d\n", autoplay->num_levels_played); + Print("Number of levels solved: %d (%d%%)\n", autoplay->num_levels_solved, + (autoplay->num_levels_played ? + autoplay->num_levels_solved * 100 / autoplay->num_levels_played : 0)); + if (global.autoplay_mode == AUTOPLAY_MODE_FIX) + Print("Number of tapes fixed: %d\n", autoplay->num_tapes_patched); + PrintLine("-", 79); + Print("Summary (for automatic parsing by scripts):\n"); + + if (autoplay->tape_filename) + { + Print("TAPEFILE [%s] '%s', %d, %d, %d", + autoplay_status, + autoplay->leveldir->identifier, + autoplay->last_level_nr, + game.score_final, + game.score_time_final); + } + else + { + Print("LEVELDIR [%s] '%s', SOLVED %d/%d (%d%%)", + autoplay_status, + autoplay->leveldir->identifier, + autoplay->num_levels_solved, + autoplay->num_levels_played, + autoplay_percent); + + if (autoplay->num_levels_played != autoplay->num_levels_solved) + { + Print(", FAILED:"); + for (i = 0; i < MAX_TAPES_PER_SET; i++) + if (autoplay->level_failed[i]) + Print(" %03d", i); + } + } + + Print("\n"); + PrintLine("=", 79); +} + +static FILE *tape_log_file; + +static void OpenTapeLogfile(void) +{ + if (!(tape_log_file = fopen(options.tape_log_filename, MODE_WRITE))) + Warn("cannot write tape logfile '%s'", options.tape_log_filename); +} + +static void WriteTapeLogfile(byte action[MAX_TAPE_ACTIONS]) +{ + int i; + + for (i = 0; i < MAX_TAPE_ACTIONS; i++) + putFile8Bit(tape_log_file, action[i]); +} + +static void CloseTapeLogfile(void) +{ + fclose(tape_log_file); +} + // ============================================================================ // tape control functions @@ -540,6 +652,8 @@ void TapeErase(void) tape.length_frames = 0; tape.length_seconds = 0; + tape.score_tape_basename[0] = '\0'; + if (leveldir_current) { strncpy(tape.level_identifier, leveldir_current->identifier, @@ -550,6 +664,7 @@ void TapeErase(void) tape.level_nr = level_nr; tape.pos[tape.counter].delay = 0; tape.changed = TRUE; + tape.solved = FALSE; tape.random_seed = InitRND(level.random_seed); @@ -557,6 +672,8 @@ void TapeErase(void) tape.game_version = GAME_VERSION_ACTUAL; tape.engine_version = level.game_version; + tape.property_bits = TAPE_PROPERTY_NONE; + TapeSetDateFromNow(); for (i = 0; i < MAX_PLAYERS; i++) @@ -644,10 +761,13 @@ static void TapeAppendRecording(void) // start recording tape.recording = TRUE; tape.changed = TRUE; + tape.solved = FALSE; // set current delay (for last played move) tape.pos[tape.counter].delay = tape.delay_played; + tape.property_bits |= TAPE_PROPERTY_REPLAYED; + // set current date TapeSetDateFromNow(); @@ -659,7 +779,9 @@ static void TapeAppendRecording(void) void TapeHaltRecording(void) { - tape.counter++; + // only advance tape counter if any input events have been recorded + if (tape.pos[tape.counter].delay > 0) + tape.counter++; // initialize delay for next tape entry (to be able to continue recording) if (tape.counter < MAX_TAPE_LEN) @@ -714,6 +836,8 @@ boolean TapeAddAction(byte action[MAX_TAPE_ACTIONS]) tape.pos[tape.counter].delay++; } + tape.changed = TRUE; + return TRUE; } @@ -738,6 +862,12 @@ void TapeRecordAction(byte action_raw[MAX_TAPE_ACTIONS]) tape.set_centered_player = FALSE; } + if (GameFrameDelay != GAME_FRAME_DELAY) + tape.property_bits |= TAPE_PROPERTY_GAME_SPEED; + + if (setup.small_game_graphics || SCR_FIELDX >= 2 * SCR_FIELDX_DEFAULT) + tape.property_bits |= TAPE_PROPERTY_SMALL_GRAPHICS; + if (!TapeAddAction(action)) TapeStopRecording(); } @@ -759,6 +889,12 @@ void TapeTogglePause(boolean toggle_mode) if (tape.single_step && (toggle_mode & TAPE_TOGGLE_MANUAL)) tape.single_step = FALSE; + if (tape.single_step) + tape.property_bits |= TAPE_PROPERTY_SINGLE_STEP; + + if (tape.pausing) + tape.property_bits |= TAPE_PROPERTY_PAUSE_MODE; + DrawVideoDisplayCurrentState(); if (tape.deactivate_display) @@ -788,16 +924,22 @@ void TapeTogglePause(boolean toggle_mode) if (game_status == GAME_MODE_PLAYING) { - if (setup.show_snapshot_buttons && CheckEngineSnapshotList()) + if (setup.show_load_save_buttons && + setup.show_undo_redo_buttons && + CheckEngineSnapshotList()) { if (tape.pausing) MapUndoRedoButtons(); else if (!tape.single_step) - UnmapUndoRedoButtons(); + MapLoadSaveButtons(); } ModifyPauseButtons(); } + + // stop tape when leaving auto-pause after completely replaying tape + if (tape.playing && !tape.pausing && tape.counter >= tape.length) + TapeStop(); } void TapeStartPlaying(void) @@ -855,21 +997,27 @@ byte *TapePlayAction(void) if (tape.pause_before_end) // stop some seconds before end of tape { - if (TapeTime > tape.length_seconds - TAPE_PAUSE_SECONDS_BEFORE_DEATH) + if (TapeTime > (int)tape.length_seconds - TAPE_PAUSE_SECONDS_BEFORE_DEATH) { TapeStopWarpForward(); TapeTogglePause(TAPE_TOGGLE_MANUAL); + if (setup.autorecord_after_replay) + TapeAppendRecording(); + return NULL; } } if (tape.counter >= tape.length) // end of tape reached { - if (tape.warp_forward && !tape.auto_play) + if (!tape.auto_play) { TapeStopWarpForward(); TapeTogglePause(TAPE_TOGGLE_MANUAL); + + if (setup.autorecord_after_replay) + TapeAppendRecording(); } else { @@ -934,6 +1082,9 @@ byte *TapePlayAction(void) if (tape.auto_play) PrintTapeReplayProgress(FALSE); + if (options.tape_log_filename != NULL) + WriteTapeLogfile(action); + return action; } @@ -955,6 +1106,27 @@ void TapeStop(void) } } +static void TapeStopGameOrTape(boolean stop_game) +{ + if (score_info_tape_play || (!tape.playing && stop_game)) + RequestQuitGame(FALSE); + else + TapeStop(); +} + +void TapeStopGame(void) +{ + if (game_status == GAME_MODE_MAIN) + return; + + TapeStopGameOrTape(TRUE); +} + +void TapeStopTape(void) +{ + TapeStopGameOrTape(FALSE); +} + unsigned int GetTapeLengthFrames(void) { unsigned int tape_length_frames = 0; @@ -1021,26 +1193,31 @@ static void TapeSingleStep(void) void TapeQuickSave(void) { - if (game_status == GAME_MODE_MAIN) + if (game_status != GAME_MODE_PLAYING) { - Request("No game that can be saved!", REQ_CONFIRM); + Request("No game that could be saved!", REQ_CONFIRM); return; } - if (game_status != GAME_MODE_PLAYING) + if (!tape.recording) + { + Request("No recording that could be saved!", REQ_CONFIRM); + return; + } - if (tape.recording) - TapeHaltRecording(); // prepare tape for saving on-the-fly + TapeHaltRecording(); // prepare tape for saving on-the-fly if (TAPE_IS_EMPTY(tape)) { - Request("No tape that can be saved!", REQ_CONFIRM); + Request("No tape that could be saved!", REQ_CONFIRM); return; } + tape.property_bits |= TAPE_PROPERTY_SNAPSHOT; + if (SaveTapeChecked(tape.level_nr)) SaveEngineSnapshotSingle(); } @@ -1099,6 +1276,7 @@ void TapeQuickLoad(void) TapeStartWarpForward(AUTOPLAY_MODE_WARP_NO_DISPLAY); tape.quick_resume = TRUE; + tape.property_bits |= TAPE_PROPERTY_SNAPSHOT; } else // this should not happen (basically checked above) { @@ -1108,6 +1286,69 @@ void TapeQuickLoad(void) } } +static boolean checkRestartGame(char *message) +{ + if (game_status == GAME_MODE_MAIN) + return TRUE; + + if (!hasStartedNetworkGame()) + return FALSE; + + if (level_editor_test_game) + return TRUE; + + if (game.all_players_gone) + return TRUE; + + if (!setup.ask_on_quit_game) + return TRUE; + + if (Request(message, REQ_ASK | REQ_STAY_CLOSED)) + return TRUE; + + OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK); + + return FALSE; +} + +void TapeRestartGame(void) +{ + if (score_info_tape_play) + { + TapeStartGamePlaying(); + + return; + } + + if (!checkRestartGame("Restart game?")) + return; + + StartGameActions(network.enabled, setup.autorecord, level.random_seed); +} + +void TapeReplayAndPauseBeforeEnd(void) +{ + if (score_info_tape_play) + return; + + if (TAPE_IS_EMPTY(tape) && !tape.recording) + { + Request("No tape for this level!", REQ_CONFIRM); + + return; + } + + if (!checkRestartGame("Replay game and pause before end?")) + return; + + TapeStop(); + TapeStartGamePlaying(); + TapeStartWarpForward(AUTOPLAY_MODE_WARP_NO_DISPLAY); + + tape.pause_before_end = TRUE; + tape.quick_resume = TRUE; +} + boolean hasSolutionTape(void) { boolean tape_file_exists = fileExists(getSolutionTapeFilename(level_nr)); @@ -1156,6 +1397,93 @@ boolean PlaySolutionTape(void) return TRUE; } +static boolean PlayScoreTape_WaitForDownload(void) +{ + DelayCounter download_delay = { 10000 }; + + ResetDelayCounter(&download_delay); + + // wait for score tape to be successfully downloaded (and fail on timeout) + while (!server_scores.tape_downloaded) + { + if (DelayReached(&download_delay)) + return FALSE; + + UPDATE_BUSY_STATE_NOT_LOADING(); + + Delay(20); + } + + return TRUE; +} + +boolean PlayScoreTape(int entry_nr) +{ + struct ScoreEntry *entry = &scores.entry[entry_nr]; + char *tape_filename = + (entry->id == -1 ? + getScoreTapeFilename(entry->tape_basename, level_nr) : + getScoreCacheTapeFilename(entry->tape_basename, level_nr)); + boolean download_tape = (!fileExists(tape_filename)); + + if (download_tape && entry->id == -1) + { + FadeSkipNextFadeIn(); + + Request("Cannot find score tape!", REQ_CONFIRM); + + return FALSE; + } + + server_scores.tape_downloaded = FALSE; + + if (download_tape) + ApiGetScoreTapeAsThread(level_nr, entry->id, entry->tape_basename); + + SetGameStatus(GAME_MODE_PLAYING); + + FadeOut(REDRAW_FIELD); + + if (download_tape && !PlayScoreTape_WaitForDownload()) + { + SetGameStatus(GAME_MODE_SCOREINFO); + ClearField(); + + Request("Cannot download score tape from score server!", REQ_CONFIRM); + + return FALSE; + } + + if (!TAPE_IS_STOPPED(tape)) + TapeStop(); + + // if tape recorder already contains a tape, remove it without asking + TapeErase(); + + if (entry->id == -1) + LoadScoreTape(entry->tape_basename, level_nr); + else + LoadScoreCacheTape(entry->tape_basename, level_nr); + + if (TAPE_IS_EMPTY(tape)) + { + SetGameStatus(GAME_MODE_SCOREINFO); + ClearField(); + + Request("Cannot load score tape for this level!", REQ_CONFIRM); + + return FALSE; + } + + FadeSkipNextFadeOut(); + + TapeStartGamePlaying(); + + score_info_tape_play = TRUE; + + return TRUE; +} + static boolean checkTapesFromSameLevel(struct TapeInfo *t1, struct TapeInfo *t2) { return (strEqual(t1->level_identifier, t2->level_identifier) && @@ -1223,16 +1551,84 @@ void FixTape_ForceSinglePlayer(void) // tape autoplay functions // ---------------------------------------------------------------------------- -void AutoPlayTapes(void) +static TreeInfo *getNextValidAutoPlayEntry(TreeInfo *node) { - static LevelDirTree *autoplay_leveldir = NULL; - static boolean autoplay_initialized = FALSE; - static int autoplay_level_nr = -1; - static int num_levels_played = 0; - static int num_levels_solved = 0; - static int num_tapes_patched = 0; - static int num_tape_missing = 0; - static boolean level_failed[MAX_TAPES_PER_SET]; + node = getNextValidTreeInfoEntry(node); + + while (node && node->is_copy) + node = getNextValidTreeInfoEntry(node); + + return node; +} + +static TreeInfo *getFirstValidAutoPlayEntry(TreeInfo *node) +{ + node = getFirstValidTreeInfoEntry(node); + + if (node && node->is_copy) + return getNextValidAutoPlayEntry(node); + + return node; +} + +static void AutoPlayTapes_SetScoreEntry(int score, int time) +{ + char *name = (options.mytapes ? setup.player_name : options.player_name); + + // set unique basename for score tape (for uploading to score server) + strcpy(tape.score_tape_basename, getScoreTapeBasename(name)); + + // store score in first score entry + scores.last_added = 0; + + struct ScoreEntry *entry = &scores.entry[scores.last_added]; + + strncpy(entry->tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN); + strncpy(entry->name, setup.player_name, MAX_PLAYER_NAME_LEN); + + entry->score = score; + entry->time = time; + + PrintNoLog("- uploading score tape to score server ... "); + + server_scores.uploaded = FALSE; +} + +static boolean AutoPlayTapes_WaitForUpload(void) +{ + DelayCounter upload_delay = { 10000 }; + + ResetDelayCounter(&upload_delay); + + // wait for score tape to be successfully uploaded (and fail on timeout) + while (!server_scores.uploaded) + { + if (DelayReached(&upload_delay)) + { + PrintNoLog("\r"); + Print("- uploading score tape to score server - TIMEOUT.\n"); + + if (program.headless) + Fail("cannot upload score tape to score server"); + + return FALSE; + } + + UPDATE_BUSY_STATE(); + + Delay(20); + } + + PrintNoLog("\r"); + Print("- uploading score tape to score server - uploaded.\n"); + + return TRUE; +} + +static int AutoPlayTapesExt(boolean initialize) +{ + static struct AutoPlayInfo autoplay; + static int num_tapes = 0; static int patch_nr = 0; static char *patch_name[] = { @@ -1266,9 +1662,12 @@ void AutoPlayTapes(void) -1 }; + LevelDirTree *leveldir_current_last = leveldir_current; + boolean init_level_set = FALSE; + int level_nr_last = level_nr; int i; - if (autoplay_initialized) + if (!initialize) { if (global.autoplay_mode == AUTOPLAY_MODE_FIX) { @@ -1287,7 +1686,7 @@ void AutoPlayTapes(void) SaveTapeToFilename(filename); tape.auto_play_level_fixed = TRUE; - num_tapes_patched++; + autoplay.num_tapes_patched++; } // continue with next tape @@ -1309,67 +1708,221 @@ void AutoPlayTapes(void) // just finished auto-playing tape PrintTapeReplayProgress(TRUE); + if (options.tape_log_filename != NULL) + CloseTapeLogfile(); + + if (global.autoplay_mode == AUTOPLAY_MODE_SAVE && + tape.auto_play_level_solved) + { + AutoPlayTapes_SetScoreEntry(game.score_final, game.score_time_final); + + if (leveldir_current) + { + // the tape's level set identifier may differ from current level set + strncpy(tape.level_identifier, leveldir_current->identifier, + MAX_FILENAME_LEN); + tape.level_identifier[MAX_FILENAME_LEN] = '\0'; + + // the tape's level number may differ from current level number + tape.level_nr = level_nr; + } + + // save score tape to upload to server; may be required for some reasons: + // * level set identifier in solution tapes may differ from level set + // * level set identifier is missing (old-style tape without INFO chunk) + // * solution tape may have native format (like Supaplex solution files) + + SaveScoreTape(level_nr); + SaveServerScore(level_nr, TRUE); + + AutoPlayTapes_WaitForUpload(); + } + if (patch_nr == 0) - num_levels_played++; + autoplay.num_levels_played++; if (tape.auto_play_level_solved) - num_levels_solved++; + autoplay.num_levels_solved++; if (level_nr >= 0 && level_nr < MAX_TAPES_PER_SET) - level_failed[level_nr] = !tape.auto_play_level_solved; + autoplay.level_failed[level_nr] = !tape.auto_play_level_solved; } else { - DrawCompleteVideoDisplay(); + if (strEqual(global.autoplay_leveldir, "ALL")) + { + autoplay.all_levelsets = TRUE; - audio.sound_enabled = FALSE; - setup.engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_OFF); + // tape mass-uploading only allowed for private tapes + if (global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) + options.mytapes = TRUE; + } - autoplay_leveldir = getTreeInfoFromIdentifier(leveldir_first, - global.autoplay_leveldir); + if ((global.autoplay_mode == AUTOPLAY_MODE_SAVE || + global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) && + !options.mytapes && + options.player_name == NULL) + { + Fail("specify player name when uploading solution tapes"); + } - if (autoplay_leveldir == NULL) - Fail("no such level identifier: '%s'", global.autoplay_leveldir); + if (global.autoplay_mode != AUTOPLAY_MODE_UPLOAD) + DrawCompleteVideoDisplay(); - leveldir_current = autoplay_leveldir; + if (program.headless) + { + audio.sound_enabled = FALSE; + setup.engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_OFF); + } - if (autoplay_leveldir->first_level < 0) - autoplay_leveldir->first_level = 0; - if (autoplay_leveldir->last_level >= MAX_TAPES_PER_SET) - autoplay_leveldir->last_level = MAX_TAPES_PER_SET - 1; + if (strSuffix(global.autoplay_leveldir, ".tape")) + { + autoplay.tape_filename = global.autoplay_leveldir; - autoplay_level_nr = autoplay_leveldir->first_level; + if (!fileExists(autoplay.tape_filename)) + Fail("tape file '%s' does not exist", autoplay.tape_filename); - PrintLine("=", 79); - if (global.autoplay_mode == AUTOPLAY_MODE_FIX) - Print("Automatically fixing level tapes\n"); + LoadTapeFromFilename(autoplay.tape_filename); + + if (tape.no_valid_file) + Fail("cannot load tape file '%s'", autoplay.tape_filename); + + if (tape.no_info_chunk && !options.identifier) + Fail("cannot get levelset from tape file '%s'", autoplay.tape_filename); + + if (tape.no_info_chunk && !options.level_nr) + Fail("cannot get level nr from tape file '%s'", autoplay.tape_filename); + + global.autoplay_leveldir = tape.level_identifier; + + if (options.identifier != NULL) + global.autoplay_leveldir = options.identifier; + + if (options.level_nr != NULL) + tape.level_nr = atoi(options.level_nr); + + if (tape.level_nr >= 0 && tape.level_nr < MAX_TAPES_PER_SET) + global.autoplay_level[tape.level_nr] = TRUE; + + global.autoplay_all = FALSE; + options.mytapes = FALSE; + } + + if (autoplay.all_levelsets) + { + // start auto-playing first level set + autoplay.leveldir = getFirstValidAutoPlayEntry(leveldir_first); + } else - Print("Automatically playing level tapes\n"); - PrintLine("-", 79); - Print("Level series identifier: '%s'\n", autoplay_leveldir->identifier); - Print("Level series name: '%s'\n", autoplay_leveldir->name); - Print("Level series author: '%s'\n", autoplay_leveldir->author); - Print("Number of levels: %d\n", autoplay_leveldir->levels); - PrintLine("=", 79); - Print("\n"); + { + // auto-play selected level set + autoplay.leveldir = getTreeInfoFromIdentifier(leveldir_first, + global.autoplay_leveldir); + } - for (i = 0; i < MAX_TAPES_PER_SET; i++) - level_failed[i] = FALSE; + if (autoplay.leveldir == NULL) + Fail("no such level identifier: '%s'", global.autoplay_leveldir); // only private tapes may be modified if (global.autoplay_mode == AUTOPLAY_MODE_FIX) options.mytapes = TRUE; - autoplay_initialized = TRUE; + // set timestamp for batch tape upload + global.autoplay_time = time(NULL); + + num_tapes = 0; + + init_level_set = TRUE; } while (1) { + if (init_level_set) + { + leveldir_current = autoplay.leveldir; + + if (autoplay.leveldir->first_level < 0) + autoplay.leveldir->first_level = 0; + if (autoplay.leveldir->last_level >= MAX_TAPES_PER_SET) + autoplay.leveldir->last_level = MAX_TAPES_PER_SET - 1; + + autoplay.level_nr = autoplay.leveldir->first_level; + + autoplay.num_levels_played = 0; + autoplay.num_levels_solved = 0; + autoplay.num_tapes_patched = 0; + autoplay.num_tape_missing = 0; + + for (i = 0; i < MAX_TAPES_PER_SET; i++) + autoplay.level_failed[i] = FALSE; + + PrintTapeReplayHeader(&autoplay); + + init_level_set = FALSE; + } + + if (autoplay.all_levelsets && global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) + { + boolean skip_levelset = FALSE; + + if (!directoryExists(getTapeDir(autoplay.leveldir->subdir))) + { + Print("No tape directory for this level set found -- skipping.\n"); + + skip_levelset = TRUE; + } + + if (CheckTapeDirectoryUploadsComplete(autoplay.leveldir->subdir)) + { + Print("All tapes for this level set already uploaded -- skipping.\n"); + + skip_levelset = TRUE; + } + + if (skip_levelset) + { + PrintTapeReplaySummary(&autoplay); + + // continue with next level set + autoplay.leveldir = getNextValidAutoPlayEntry(autoplay.leveldir); + + // all level sets processed + if (autoplay.leveldir == NULL) + break; + + init_level_set = TRUE; + + continue; + } + } + if (global.autoplay_mode != AUTOPLAY_MODE_FIX || patch_nr == 0) - level_nr = autoplay_level_nr++; + level_nr = autoplay.level_nr++; - if (level_nr > autoplay_leveldir->last_level) - break; + UPDATE_BUSY_STATE(); + + // check if all tapes for this level set have been processed + if (level_nr > autoplay.leveldir->last_level) + { + PrintTapeReplaySummary(&autoplay); + + if (!autoplay.all_levelsets) + break; + + if (global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) + MarkTapeDirectoryUploadsAsComplete(autoplay.leveldir->subdir); + + // continue with next level set + autoplay.leveldir = getNextValidAutoPlayEntry(autoplay.leveldir); + + // all level sets processed + if (autoplay.leveldir == NULL) + break; + + init_level_set = TRUE; + + continue; + } // set patch info (required for progress output) strcpy(tape_patch_info, ""); @@ -1380,7 +1933,18 @@ void AutoPlayTapes(void) if (!global.autoplay_all && !global.autoplay_level[level_nr]) continue; + // speed things up in case of missing private tapes (skip loading level) + if (options.mytapes && !fileExists(getTapeFilename(level_nr))) + { + autoplay.num_tape_missing++; + + Print("Tape %03d: (no tape found)\n", level_nr); + + continue; + } + TapeErase(); + TapeRewind(); // needed to reset "tape.auto_play_level_solved" LoadLevel(level_nr); @@ -1397,14 +1961,16 @@ void AutoPlayTapes(void) continue; #endif - if (options.mytapes) + if (autoplay.tape_filename) + LoadTapeFromFilename(autoplay.tape_filename); + else if (options.mytapes) LoadTape(level_nr); else LoadSolutionTape(level_nr); if (tape.no_valid_file) { - num_tape_missing++; + autoplay.num_tape_missing++; Print("Tape %03d: (no tape found)\n", level_nr); @@ -1468,40 +2034,100 @@ void AutoPlayTapes(void) } } + num_tapes++; + + if (global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) + { + boolean use_temporary_tape_file = FALSE; + + Print("Tape %03d:\n", level_nr); + + AutoPlayTapes_SetScoreEntry(0, 0); + + if (autoplay.tape_filename == NULL) + { + autoplay.tape_filename = (options.mytapes ? getTapeFilename(level_nr) : + getDefaultSolutionTapeFilename(level_nr)); + + if (!fileExists(autoplay.tape_filename)) + { + // non-standard or incorrect solution tape -- save to temporary file + autoplay.tape_filename = getTemporaryTapeFilename(); + + SaveTapeToFilename(autoplay.tape_filename); + + use_temporary_tape_file = TRUE; + } + } + + SaveServerScoreFromFile(level_nr, TRUE, autoplay.tape_filename); + + boolean success = AutoPlayTapes_WaitForUpload(); + + if (use_temporary_tape_file) + unlink(autoplay.tape_filename); + + // required for uploading multiple tapes + autoplay.tape_filename = NULL; + + if (!success) + { + num_tapes = -num_tapes; + + break; + } + + continue; + } + InitCounter(); + if (options.tape_log_filename != NULL) + OpenTapeLogfile(); + TapeStartGamePlaying(); TapeStartWarpForward(global.autoplay_mode); - return; - } + autoplay.last_level_nr = level_nr; - Print("\n"); - PrintLine("=", 79); - Print("Number of levels played: %d\n", num_levels_played); - Print("Number of levels solved: %d (%d%%)\n", num_levels_solved, - (num_levels_played ? num_levels_solved * 100 / num_levels_played : 0)); - if (global.autoplay_mode == AUTOPLAY_MODE_FIX) - Print("Number of tapes fixed: %d\n", num_tapes_patched); - PrintLine("-", 79); - Print("Summary (for automatic parsing by scripts):\n"); - Print("LEVELDIR [%s] '%s', SOLVED %d/%d (%d%%)", - (num_levels_played == num_levels_solved ? " OK " : "WARN"), - autoplay_leveldir->identifier, num_levels_solved, num_levels_played, - (num_levels_played ? num_levels_solved * 100 / num_levels_played : 0)); + return num_tapes; + } - if (num_levels_played != num_levels_solved) + if (global.autoplay_mode == AUTOPLAY_MODE_UPLOAD) { - Print(", FAILED:"); - for (i = 0; i < MAX_TAPES_PER_SET; i++) - if (level_failed[i]) - Print(" %03d", i); + Print("\n"); + PrintLine("=", 79); + + if (num_tapes >= 0) + Print("SUMMARY: %d tapes uploaded.\n", num_tapes); + else + Print("SUMMARY: Uploading tapes failed.\n"); + + PrintLine("=", 79); } - Print("\n"); - PrintLine("=", 79); + // clear timestamp for batch tape upload (required after interactive upload) + global.autoplay_time = 0; - CloseAllAndExit(0); + // exit if running headless or if visually auto-playing tapes + if (program.headless || global.autoplay_mode != AUTOPLAY_MODE_UPLOAD) + CloseAllAndExit(0); + + // when running interactively, restore last selected level set and number + leveldir_current = leveldir_current_last; + level_nr = level_nr_last; + + return num_tapes; +} + +int AutoPlayTapes(void) +{ + return AutoPlayTapesExt(TRUE); +} + +int AutoPlayTapesContinue(void) +{ + return AutoPlayTapesExt(FALSE); } @@ -1905,7 +2531,7 @@ static void HandleTapeButtonsExt(int id) break; case TAPE_CTRL_ID_STOP: - TapeStop(); + TapeStopTape(); break; diff --git a/src/tape.h b/src/tape.h index a829448a..8b375ede 100644 --- a/src/tape.h +++ b/src/tape.h @@ -38,6 +38,16 @@ // values for tape properties stored in tape file #define TAPE_PROPERTY_NONE 0 #define TAPE_PROPERTY_EM_RANDOM_BUG (1 << 0) +#define TAPE_PROPERTY_GAME_SPEED (1 << 1) +#define TAPE_PROPERTY_PAUSE_MODE (1 << 2) +#define TAPE_PROPERTY_SINGLE_STEP (1 << 3) +#define TAPE_PROPERTY_SNAPSHOT (1 << 4) +#define TAPE_PROPERTY_REPLAYED (1 << 5) +#define TAPE_PROPERTY_TAS_KEYS (1 << 6) +#define TAPE_PROPERTY_SMALL_GRAPHICS (1 << 7) + +// values for score tape basename length (date, time, name hash, no extension) +#define MAX_SCORE_TAPE_BASENAME_LEN 24 // some positions in the video tape control window #define VIDEO_DISPLAY1_XPOS 5 @@ -54,39 +64,39 @@ #define VIDEO_CONTROL_YSIZE VIDEO_BUTTON_YSIZE // values for video tape control -#define VIDEO_STATE_PLAY_OFF (1 << 0) -#define VIDEO_STATE_PLAY_ON (1 << 1) -#define VIDEO_STATE_REC_OFF (1 << 2) -#define VIDEO_STATE_REC_ON (1 << 3) -#define VIDEO_STATE_PAUSE_OFF (1 << 4) -#define VIDEO_STATE_PAUSE_ON (1 << 5) -#define VIDEO_STATE_DATE_OFF (1 << 6) -#define VIDEO_STATE_DATE_ON (1 << 7) -#define VIDEO_STATE_TIME_OFF (1 << 8) -#define VIDEO_STATE_TIME_ON (1 << 9) -#define VIDEO_STATE_FRAME_OFF (1 << 10) -#define VIDEO_STATE_FRAME_ON (1 << 11) -#define VIDEO_STATE_FFWD_OFF (1 << 12) -#define VIDEO_STATE_FFWD_ON (1 << 13) -#define VIDEO_STATE_WARP_OFF (1 << 14) -#define VIDEO_STATE_WARP_ON (1 << 15) -#define VIDEO_STATE_WARP2_OFF (1 << 16) -#define VIDEO_STATE_WARP2_ON (1 << 17) -#define VIDEO_STATE_PBEND_OFF (1 << 18) -#define VIDEO_STATE_PBEND_ON (1 << 19) -#define VIDEO_STATE_1STEP_OFF (1 << 20) -#define VIDEO_STATE_1STEP_ON (1 << 21) - -#define VIDEO_PRESS_PLAY_ON (1 << 22) -#define VIDEO_PRESS_PLAY_OFF (1 << 23) -#define VIDEO_PRESS_REC_ON (1 << 24) -#define VIDEO_PRESS_REC_OFF (1 << 25) -#define VIDEO_PRESS_PAUSE_ON (1 << 26) -#define VIDEO_PRESS_PAUSE_OFF (1 << 27) -#define VIDEO_PRESS_STOP_ON (1 << 28) -#define VIDEO_PRESS_STOP_OFF (1 << 29) -#define VIDEO_PRESS_EJECT_ON (1 << 30) -#define VIDEO_PRESS_EJECT_OFF (1 << 31) +#define VIDEO_STATE_PLAY_OFF (1u << 0) +#define VIDEO_STATE_PLAY_ON (1u << 1) +#define VIDEO_STATE_REC_OFF (1u << 2) +#define VIDEO_STATE_REC_ON (1u << 3) +#define VIDEO_STATE_PAUSE_OFF (1u << 4) +#define VIDEO_STATE_PAUSE_ON (1u << 5) +#define VIDEO_STATE_DATE_OFF (1u << 6) +#define VIDEO_STATE_DATE_ON (1u << 7) +#define VIDEO_STATE_TIME_OFF (1u << 8) +#define VIDEO_STATE_TIME_ON (1u << 9) +#define VIDEO_STATE_FRAME_OFF (1u << 10) +#define VIDEO_STATE_FRAME_ON (1u << 11) +#define VIDEO_STATE_FFWD_OFF (1u << 12) +#define VIDEO_STATE_FFWD_ON (1u << 13) +#define VIDEO_STATE_WARP_OFF (1u << 14) +#define VIDEO_STATE_WARP_ON (1u << 15) +#define VIDEO_STATE_WARP2_OFF (1u << 16) +#define VIDEO_STATE_WARP2_ON (1u << 17) +#define VIDEO_STATE_PBEND_OFF (1u << 18) +#define VIDEO_STATE_PBEND_ON (1u << 19) +#define VIDEO_STATE_1STEP_OFF (1u << 20) +#define VIDEO_STATE_1STEP_ON (1u << 21) + +#define VIDEO_PRESS_PLAY_ON (1u << 22) +#define VIDEO_PRESS_PLAY_OFF (1u << 23) +#define VIDEO_PRESS_REC_ON (1u << 24) +#define VIDEO_PRESS_REC_OFF (1u << 25) +#define VIDEO_PRESS_PAUSE_ON (1u << 26) +#define VIDEO_PRESS_PAUSE_OFF (1u << 27) +#define VIDEO_PRESS_STOP_ON (1u << 28) +#define VIDEO_PRESS_STOP_OFF (1u << 29) +#define VIDEO_PRESS_EJECT_ON (1u << 30) +#define VIDEO_PRESS_EJECT_OFF (1u << 31) #define VIDEO_STATE_PLAY(x) ((x) ? VIDEO_STATE_PLAY_ON : VIDEO_STATE_PLAY_OFF) #define VIDEO_STATE_REC(x) ((x) ? VIDEO_STATE_REC_ON : VIDEO_STATE_REC_OFF) @@ -178,6 +188,7 @@ struct TapeInfo int game_version; // game release version the tape was created with int engine_version; // game engine version the tape was recorded with + char score_tape_basename[MAX_FILENAME_LEN + 1]; char level_identifier[MAX_FILENAME_LEN + 1]; int level_nr; unsigned int random_seed; @@ -199,6 +210,7 @@ struct TapeInfo boolean quick_resume; boolean single_step; boolean changed; + boolean solved; boolean player_participates[MAX_PLAYERS]; int num_participating_players; int centered_player_nr_next; @@ -228,6 +240,7 @@ struct TapeInfo boolean show_game_buttons; // show game buttons in tape viewport + boolean no_info_chunk; // used to identify old tape file format boolean no_valid_file; // set when tape file missing or invalid }; @@ -244,27 +257,33 @@ void TapeSetDateFromNow(void); void TapeStartRecording(int); void TapeHaltRecording(void); void TapeStopRecording(void); -boolean TapeAddAction(byte *); -void TapeRecordAction(byte *); +boolean TapeAddAction(byte[MAX_TAPE_ACTIONS]); +void TapeRecordAction(byte[MAX_TAPE_ACTIONS]); void TapeTogglePause(boolean); void TapeStartPlaying(void); void TapeStopPlaying(void); byte *TapePlayAction(void); void TapeStop(void); +void TapeStopGame(void); +void TapeStopTape(void); void TapeErase(void); unsigned int GetTapeLengthFrames(void); unsigned int GetTapeLengthSeconds(void); void TapeQuickSave(void); void TapeQuickLoad(void); +void TapeRestartGame(void); +void TapeReplayAndPauseBeforeEnd(void); boolean hasSolutionTape(void); boolean InsertSolutionTape(void); boolean PlaySolutionTape(void); +boolean PlayScoreTape(int); void UndoTape(void); void FixTape_ForceSinglePlayer(void); -void AutoPlayTapes(void); +int AutoPlayTapes(void); +int AutoPlayTapesContinue(void); void PatchTapes(void); void CreateTapeButtons(void); diff --git a/src/tools.c b/src/tools.c index b0df3440..4083e77e 100644 --- a/src/tools.c +++ b/src/tools.c @@ -165,8 +165,17 @@ static struct DoorPartControlInfo door_part_controls[] = } }; +static struct XY xy_topdown[] = +{ + { 0, -1 }, + { -1, 0 }, + { +1, 0 }, + { 0, +1 } +}; + // forward declaration for internal use +static void MapToolButtons(unsigned int); static void UnmapToolButtons(void); static void HandleToolButtons(struct GadgetInfo *); static int el_act_dir2crm(int, int, int); @@ -512,6 +521,10 @@ static void DrawMaskedBorderExt_Rect(int x, int y, int width, int height, Bitmap *src_bitmap = getGlobalBorderBitmapFromStatus(global.border_status); Bitmap *dst_bitmap = gfx.masked_border_bitmap_ptr; + // may happen for "border.draw_masked.*" with undefined "global.border.*" + if (src_bitmap == NULL) + return; + if (x == -1 && y == -1) return; @@ -622,6 +635,10 @@ void DrawMaskedBorderToTarget(int draw_target) gfx.masked_border_bitmap_ptr = gfx.fade_bitmap_target; } + // always use global border for PLAYING when restarting the game + if (global.border_status == GAME_MODE_PSEUDO_RESTARTING) + global.border_status = GAME_MODE_PLAYING; + DrawMaskedBorderExt(REDRAW_ALL, draw_target); global.border_status = last_border_status; @@ -629,9 +646,11 @@ void DrawMaskedBorderToTarget(int draw_target) } } -void DrawTileCursor(int draw_target) +void DrawTileCursor(int draw_target, int drawing_stage) { - DrawTileCursor_MM(draw_target, game_status == GAME_MODE_PLAYING); + int tile_cursor_active = (game_status == GAME_MODE_PLAYING); + + DrawTileCursor_MM(draw_target, drawing_stage, tile_cursor_active); } void BlitScreenToBitmapExt_RND(Bitmap *target_bitmap, int fx, int fy) @@ -763,7 +782,7 @@ void BackToFront(void) DrawFramesPerSecond(); // remove playfield redraw before potentially merging with doors redraw - if (DrawingDeactivated(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE)) + if (DrawingDeactivated(REAL_SX, REAL_SY)) redraw_mask &= ~REDRAW_FIELD; // redraw complete window if both playfield and (some) doors need redraw @@ -964,6 +983,10 @@ static void SetScreenStates_BeforeFadingOut(void) static void SetScreenStates_AfterFadingOut(void) { global.border_status = game_status; + + // always use global border for PLAYING when restarting the game + if (global.border_status == GAME_MODE_PSEUDO_RESTARTING) + global.border_status = GAME_MODE_PLAYING; } void FadeIn(int fade_mask) @@ -1091,82 +1114,108 @@ void FadeSkipNextFadeOut(void) FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP); } -static Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic) +static int getGlobalGameStatus(int status) +{ + return (status == GAME_MODE_PSEUDO_TYPENAME ? GAME_MODE_MAIN : + status == GAME_MODE_SCOREINFO ? GAME_MODE_SCORES : + status); +} + +int getImageFromGraphicOrDefault(int graphic, int default_graphic) { if (graphic == IMG_UNDEFINED) - return NULL; + return IMG_UNDEFINED; boolean redefined = getImageListEntryFromImageID(graphic)->redefined; return (graphic_info[graphic].bitmap != NULL || redefined ? - graphic_info[graphic].bitmap : - graphic_info[default_graphic].bitmap); + graphic : default_graphic); } -static Bitmap *getBackgroundBitmap(int graphic) +static int getBackgroundImage(int graphic) { - return getBitmapFromGraphicOrDefault(graphic, IMG_BACKGROUND); + return getImageFromGraphicOrDefault(graphic, IMG_BACKGROUND); } -static Bitmap *getGlobalBorderBitmap(int graphic) +static int getGlobalBorderImage(int graphic) { - return getBitmapFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER); + return getImageFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER); } -Bitmap *getGlobalBorderBitmapFromStatus(int status) +Bitmap *getGlobalBorderBitmapFromStatus(int status_raw) { + int status = getGlobalGameStatus(status_raw); int graphic = - (status == GAME_MODE_MAIN || - status == GAME_MODE_PSEUDO_TYPENAME ? IMG_GLOBAL_BORDER_MAIN : - status == GAME_MODE_SCORES ? IMG_GLOBAL_BORDER_SCORES : - status == GAME_MODE_EDITOR ? IMG_GLOBAL_BORDER_EDITOR : - status == GAME_MODE_PLAYING ? IMG_GLOBAL_BORDER_PLAYING : + (status == GAME_MODE_MAIN ? IMG_GLOBAL_BORDER_MAIN : + status == GAME_MODE_SCORES ? IMG_GLOBAL_BORDER_SCORES : + status == GAME_MODE_EDITOR ? IMG_GLOBAL_BORDER_EDITOR : + status == GAME_MODE_PLAYING ? IMG_GLOBAL_BORDER_PLAYING : IMG_GLOBAL_BORDER); + int graphic_final = getGlobalBorderImage(graphic); - return getGlobalBorderBitmap(graphic); + return graphic_info[graphic_final].bitmap; +} + +void SetBackgroundImage(int graphic, int redraw_mask) +{ + struct GraphicInfo *g = &graphic_info[graphic]; + struct GraphicInfo g_undefined = { 0 }; + + if (graphic == IMG_UNDEFINED) + g = &g_undefined; + + // always use original size bitmap for backgrounds, if existing + Bitmap *bitmap = (g->bitmaps != NULL && + g->bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL ? + g->bitmaps[IMG_BITMAP_PTR_ORIGINAL] : g->bitmap); + + // remove every mask before setting mask for window, and + // remove window area mask before setting mask for main or door area + int remove_mask = (redraw_mask == REDRAW_ALL ? 0xffff : REDRAW_ALL); + + // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) + SetBackgroundBitmap(NULL, remove_mask, 0, 0, 0, 0); // !!! FIX THIS !!! + SetBackgroundBitmap(bitmap, redraw_mask, + g->src_x, g->src_y, + g->width, g->height); } void SetWindowBackgroundImageIfDefined(int graphic) { if (graphic_info[graphic].bitmap) - SetWindowBackgroundBitmap(graphic_info[graphic].bitmap); + SetBackgroundImage(graphic, REDRAW_ALL); } void SetMainBackgroundImageIfDefined(int graphic) { if (graphic_info[graphic].bitmap) - SetMainBackgroundBitmap(graphic_info[graphic].bitmap); + SetBackgroundImage(graphic, REDRAW_FIELD); } void SetDoorBackgroundImageIfDefined(int graphic) { if (graphic_info[graphic].bitmap) - SetDoorBackgroundBitmap(graphic_info[graphic].bitmap); + SetBackgroundImage(graphic, REDRAW_DOOR_1); } void SetWindowBackgroundImage(int graphic) { - SetWindowBackgroundBitmap(getBackgroundBitmap(graphic)); + SetBackgroundImage(getBackgroundImage(graphic), REDRAW_ALL); } void SetMainBackgroundImage(int graphic) { - SetMainBackgroundBitmap(getBackgroundBitmap(graphic)); + SetBackgroundImage(getBackgroundImage(graphic), REDRAW_FIELD); } void SetDoorBackgroundImage(int graphic) { - SetDoorBackgroundBitmap(getBackgroundBitmap(graphic)); + SetBackgroundImage(getBackgroundImage(graphic), REDRAW_DOOR_1); } void SetPanelBackground(void) { - struct GraphicInfo *gfx = &graphic_info[IMG_BACKGROUND_PANEL]; - - BlitBitmapTiled(gfx->bitmap, bitmap_db_panel, gfx->src_x, gfx->src_y, - gfx->width, gfx->height, 0, 0, DXSIZE, DYSIZE); - - SetDoorBackgroundBitmap(bitmap_db_panel); + SetDoorBackgroundImage(IMG_BACKGROUND_PANEL); } void DrawBackground(int x, int y, int width, int height) @@ -1416,39 +1465,42 @@ void SetBorderElement(void) } } -void FloodFillLevelExt(int from_x, int from_y, int fill_element, +void FloodFillLevelExt(int start_x, int start_y, int fill_element, int max_array_fieldx, int max_array_fieldy, short field[max_array_fieldx][max_array_fieldy], int max_fieldx, int max_fieldy) { - int i,x,y; - int old_element; - static int check[4][2] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } }; - static int safety = 0; + static struct XY stack_buffer[MAX_LEV_FIELDX * MAX_LEV_FIELDY]; + struct XY *check = xy_topdown; + int old_element = field[start_x][start_y]; + int stack_pos = 0; - // check if starting field still has the desired content - if (field[from_x][from_y] == fill_element) + // do nothing if start field already has the desired content + if (old_element == fill_element) return; - safety++; + stack_buffer[stack_pos++] = (struct XY){ start_x, start_y }; - if (safety > max_fieldx * max_fieldy) - Fail("Something went wrong in 'FloodFill()'. Please debug."); + while (stack_pos > 0) + { + struct XY current = stack_buffer[--stack_pos]; + int i; - old_element = field[from_x][from_y]; - field[from_x][from_y] = fill_element; + field[current.x][current.y] = fill_element; - for (i = 0; i < 4; i++) - { - x = from_x + check[i][0]; - y = from_y + check[i][1]; + for (i = 0; i < 4; i++) + { + int x = current.x + check[i].x; + int y = current.y + check[i].y; - if (IN_FIELD(x, y, max_fieldx, max_fieldy) && field[x][y] == old_element) - FloodFillLevelExt(x, y, fill_element, max_array_fieldx, max_array_fieldy, - field, max_fieldx, max_fieldy); - } + // check for stack buffer overflow (should not happen) + if (stack_pos >= MAX_LEV_FIELDX * MAX_LEV_FIELDY) + Fail("Stack buffer overflow in 'FloodFillLevelExt()'. Please debug."); - safety--; + if (IN_FIELD(x, y, max_fieldx, max_fieldy) && field[x][y] == old_element) + stack_buffer[stack_pos++] = (struct XY){ x, y }; + } + } } void FloodFillLevel(int from_x, int from_y, int fill_element, @@ -1465,11 +1517,18 @@ void SetRandomAnimationValue(int x, int y) gfx.anim_random_frame = GfxRandom[x][y]; } +void SetAnimationFirstLevel(int first_level) +{ + gfx.anim_first_level = first_level; +} + int getGraphicAnimationFrame(int graphic, int sync_frame) { // animation synchronized with global frame counter, not move position if (graphic_info[graphic].anim_global_sync || sync_frame < 0) sync_frame = FrameCounter; + else if (graphic_info[graphic].anim_global_anim_sync) + sync_frame = getGlobalAnimSyncFrame(); return getAnimationFrame(graphic_info[graphic].anim_frames, graphic_info[graphic].anim_delay, @@ -1478,6 +1537,40 @@ int getGraphicAnimationFrame(int graphic, int sync_frame) sync_frame); } +int getGraphicAnimationFrameXY(int graphic, int lx, int ly) +{ + if (graphic_info[graphic].anim_mode & ANIM_TILED) + { + struct GraphicInfo *g = &graphic_info[graphic]; + int xsize = MAX(1, g->anim_frames_per_line); + int ysize = MAX(1, g->anim_frames / xsize); + int xoffset = g->anim_start_frame % xsize; + int yoffset = g->anim_start_frame % ysize; + // may be needed if screen field is significantly larger than playfield + int x = (lx + xoffset + SCR_FIELDX * xsize) % xsize; + int y = (ly + yoffset + SCR_FIELDY * ysize) % ysize; + int sync_frame = y * xsize + x; + + return sync_frame % g->anim_frames; + } + else if (graphic_info[graphic].anim_mode & ANIM_RANDOM_STATIC) + { + struct GraphicInfo *g = &graphic_info[graphic]; + // may be needed if screen field is significantly larger than playfield + int x = (lx + SCR_FIELDX * lev_fieldx) % lev_fieldx; + int y = (ly + SCR_FIELDY * lev_fieldy) % lev_fieldy; + int sync_frame = GfxRandomStatic[x][y]; + + return sync_frame % g->anim_frames; + } + else + { + int sync_frame = (IN_LEV_FIELD(lx, ly) ? GfxFrame[lx][ly] : -1); + + return getGraphicAnimationFrame(graphic, sync_frame); + } +} + void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap) { struct GraphicInfo *g = &graphic_info[graphic]; @@ -1486,7 +1579,7 @@ void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap) if (tilesize == gfx.standard_tile_size) *bitmap = g->bitmaps[IMG_BITMAP_STANDARD]; else if (tilesize == game.tile_size) - *bitmap = g->bitmaps[IMG_BITMAP_GAME]; + *bitmap = g->bitmaps[IMG_BITMAP_PTR_GAME]; else *bitmap = g->bitmaps[IMG_BITMAP_1x1 - log_2(tilesize_capped)]; } @@ -1532,7 +1625,7 @@ void getSizedGraphicSourceExt(int graphic, int frame, int tilesize, *g = graphic_info[IMG_CHAR_EXCLAM]; // if no in-game graphics defined, always use standard graphic size - if (g->bitmaps[IMG_BITMAP_GAME] == NULL) + if (g->bitmaps[IMG_BITMAP_PTR_GAME] == NULL) tilesize = TILESIZE; getGraphicSourceBitmap(graphic, tilesize, bitmap); @@ -1559,6 +1652,24 @@ void getMiniGraphicSource(int graphic, Bitmap **bitmap, int *x, int *y) getSizedGraphicSource(graphic, 0, MINI_TILESIZE, bitmap, x, y); } +void getGlobalAnimGraphicSource(int graphic, int frame, + Bitmap **bitmap, int *x, int *y) +{ + struct GraphicInfo *g = &graphic_info[graphic]; + + // if no graphics defined at all, use fallback graphics + if (g->bitmaps == NULL) + *g = graphic_info[IMG_CHAR_EXCLAM]; + + // use original size graphics, if existing, else use standard size graphics + if (g->bitmaps[IMG_BITMAP_PTR_ORIGINAL]) + *bitmap = g->bitmaps[IMG_BITMAP_PTR_ORIGINAL]; + else + *bitmap = g->bitmaps[IMG_BITMAP_STANDARD]; + + getGraphicSourceXY(graphic, frame, x, y, FALSE); +} + static void getGraphicSourceExt(int graphic, int frame, Bitmap **bitmap, int *x, int *y, boolean get_backside) { @@ -1633,7 +1744,7 @@ void DrawGraphicThruMask(int x, int y, int graphic, int frame) #if DEBUG if (!IN_SCR_FIELD(x, y)) { - Debug("draw:DrawGraphicThruMask", "x = %d,y = %d, graphic = %d", + Debug("draw:DrawGraphicThruMask", "x = %d, y = %d, graphic = %d", x, y, graphic); Debug("draw:DrawGraphicThruMask", "This should never happen!"); @@ -1652,7 +1763,7 @@ void DrawFixedGraphicThruMask(int x, int y, int graphic, int frame) #if DEBUG if (!IN_SCR_FIELD(x, y)) { - Debug("draw:DrawFixedGraphicThruMask", "x = %d,y = %d, graphic = %d", + Debug("draw:DrawFixedGraphicThruMask", "x = %d, y = %d, graphic = %d", x, y, graphic); Debug("draw:DrawFixedGraphicThruMask", "This should never happen!"); @@ -1726,7 +1837,7 @@ void DrawSizedGraphicThruMaskExt(DrawBuffer *d, int x, int y, int graphic, void DrawMiniGraphic(int x, int y, int graphic) { - DrawMiniGraphicExt(drawto, SX + x * MINI_TILEX,SY + y * MINI_TILEY, graphic); + DrawMiniGraphicExt(drawto, SX + x * MINI_TILEX, SY + y * MINI_TILEY, graphic); MarkTileDirty(x / 2, y / 2); } @@ -1929,9 +2040,9 @@ static void DrawGraphicShifted(int x, int y, int dx, int dy, } if (graphic_info[graphic].double_movement) // EM style movement images - DrawGraphicShiftedDouble(x, y, dx, dy, graphic, frame, cut_mode,mask_mode); + DrawGraphicShiftedDouble(x, y, dx, dy, graphic, frame, cut_mode, mask_mode); else - DrawGraphicShiftedNormal(x, y, dx, dy, graphic, frame, cut_mode,mask_mode); + DrawGraphicShiftedNormal(x, y, dx, dy, graphic, frame, cut_mode, mask_mode); } static void DrawGraphicShiftedThruMask(int x, int y, int dx, int dy, @@ -1949,16 +2060,19 @@ void DrawScreenElementExt(int x, int y, int dx, int dy, int element, if (IN_LEV_FIELD(lx, ly)) { + if (element == EL_EMPTY) + element = GfxElementEmpty[lx][ly]; + SetRandomAnimationValue(lx, ly); graphic = el_act_dir2img(element, GfxAction[lx][ly], GfxDir[lx][ly]); - frame = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]); + frame = getGraphicAnimationFrameXY(graphic, lx, ly); // do not use double (EM style) movement graphic when not moving if (graphic_info[graphic].double_movement && !dx && !dy) { graphic = el_act_dir2img(element, ACTION_DEFAULT, GfxDir[lx][ly]); - frame = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]); + frame = getGraphicAnimationFrameXY(graphic, lx, ly); } if (game.use_masked_elements && (dx || dy)) @@ -1967,7 +2081,7 @@ void DrawScreenElementExt(int x, int y, int dx, int dy, int element, else // border element { graphic = el2img(element); - frame = getGraphicAnimationFrame(graphic, -1); + frame = getGraphicAnimationFrameXY(graphic, lx, ly); } if (element == EL_EXPANDABLE_WALL) @@ -2084,7 +2198,7 @@ static void DrawLevelFieldCrumbledInnerCorners(int x, int y, int dx, int dy, if (game.use_masked_elements) { int graphic0 = el2img(EL_EMPTY); - int frame0 = getGraphicAnimationFrame(graphic0, GfxFrame[x][y]); + int frame0 = getGraphicAnimationFrameXY(graphic0, x, y); Bitmap *src_bitmap0; int src_x0, src_y0; @@ -2122,7 +2236,7 @@ static void DrawLevelFieldCrumbledBorders(int x, int y, int graphic, int frame, // only needed when using masked elements int graphic0 = el2img(EL_EMPTY); - int frame0 = getGraphicAnimationFrame(graphic0, GfxFrame[x][y]); + int frame0 = getGraphicAnimationFrameXY(graphic0, x, y); Bitmap *src_bitmap0; int src_x0, src_y0; @@ -2224,13 +2338,7 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame) int sx = SCREENX(x), sy = SCREENY(y); int element; int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (!IN_LEV_FIELD(x, y)) return; @@ -2245,8 +2353,8 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame) // crumble field borders towards direct neighbour fields for (i = 0; i < 4; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) : BorderElement); @@ -2280,10 +2388,10 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame) // crumble field borders of direct neighbour fields for (i = 0; i < 4; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; - int sxx = sx + xy[i][0]; - int syy = sy + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int sxx = sx + xy[i].x; + int syy = sy + xy[i].y; if (!IN_LEV_FIELD(xx, yy) || !IN_SCR_FIELD(sxx, syy)) @@ -2376,22 +2484,16 @@ void DrawLevelFieldCrumbledDigging(int x, int y, int direction, void DrawLevelFieldCrumbledNeighbours(int x, int y) { int sx = SCREENX(x), sy = SCREENY(y); - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; int i; // crumble direct neighbour fields (required for field borders) for (i = 0; i < 4; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; - int sxx = sx + xy[i][0]; - int syy = sy + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int sxx = sx + xy[i].x; + int syy = sy + xy[i].y; if (!IN_LEV_FIELD(xx, yy) || !IN_SCR_FIELD(sxx, syy) || @@ -2465,6 +2567,11 @@ void DrawScreenGraphic(int x, int y, int graphic, int frame) } } +void DrawLevelGraphic(int x, int y, int graphic, int frame) +{ + DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame); +} + void DrawScreenElement(int x, int y, int element) { int mask_mode = NO_MASKING; @@ -2535,6 +2642,16 @@ void DrawScreenField(int x, int y) else DrawScreenElement(x, y, EL_EMPTY); + if (cut_mode != CUT_BELOW && game.use_masked_elements) + { + int dir = MovDir[lx][ly]; + int newx = x + (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0); + int newy = y + (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0); + + if (IN_SCR_FIELD(newx, newy)) + DrawScreenElement(newx, newy, EL_EMPTY); + } + if (horiz_move) DrawScreenElementShifted(x, y, MovPos[lx][ly], 0, element, NO_CUTTING); else if (cut_mode == NO_CUTTING) @@ -2610,7 +2727,7 @@ void DrawLevelField(int x, int y) DrawScreenField(SCREENX(x), SCREENY(y)); else if (IS_MOVING(x, y)) { - int newx,newy; + int newx, newy; Moving2Blocked(x, y, &newx, &newy); if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy))) @@ -2802,9 +2919,9 @@ static void AnimateEnvelope(int envelope_nr, int anim_mode, int action) int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND); boolean ffwd_delay = (tape.playing && tape.fast_forward); boolean no_delay = (tape.warp_forward); - unsigned int anim_delay = 0; int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay); int anim_delay_value = MAX(1, (no_delay ? 0 : frame_delay_value) / 2); + DelayCounter anim_delay = { anim_delay_value }; int font_nr = FONT_ENVELOPE_1 + envelope_nr; int font_width = getFontWidth(font_nr); int font_height = getFontHeight(font_nr); @@ -2841,16 +2958,16 @@ static void AnimateEnvelope(int envelope_nr, int anim_mode, int action) for (xx = 0; xx < xsize; xx++) DrawEnvelopeBackground(graphic, sx, sy, xx, yy, xsize, ysize, font_nr); - DrawTextBuffer(sx + font_width, sy + font_height, - level.envelope[envelope_nr].text, font_nr, max_xsize, - xsize - 2, ysize - 2, 0, mask_mode, - level.envelope[envelope_nr].autowrap, - level.envelope[envelope_nr].centered, FALSE); + DrawTextArea(sx + font_width, sy + font_height, + level.envelope[envelope_nr].text, font_nr, max_xsize, + xsize - 2, ysize - 2, 0, mask_mode, + level.envelope[envelope_nr].autowrap, + level.envelope[envelope_nr].centered, FALSE); redraw_mask |= REDRAW_FIELD; BackToFront(); - SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame); + SkipUntilDelayReached(&anim_delay, &i, last_frame); } ClearAutoRepeatKeyEvents(); @@ -2910,8 +3027,7 @@ void ShowEnvelope(int envelope_nr) static void PrepareEnvelopeRequestToScreen(Bitmap *bitmap, int sx, int sy, int xsize, int ysize) { - if (!global.use_envelope_request || - request.sort_priority <= 0) + if (!global.use_envelope_request) return; if (request.bitmap == NULL || @@ -2931,9 +3047,22 @@ static void PrepareEnvelopeRequestToScreen(Bitmap *bitmap, int sx, int sy, BlitBitmap(bitmap, request.bitmap, sx, sy, xsize, ysize, 0, 0); + // create masked surface for request bitmap, if needed + if (graphic_info[IMG_BACKGROUND_REQUEST].draw_masked) + { + SDL_Surface *surface = request.bitmap->surface; + SDL_Surface *surface_masked = request.bitmap->surface_masked; + + SDLBlitSurface(surface, surface_masked, 0, 0, xsize, ysize, 0, 0); + SDL_SetColorKey(surface_masked, SET_TRANSPARENT_PIXEL, + SDL_MapRGB(surface_masked->format, 0x00, 0x00, 0x00)); + } + SDLFreeBitmapTextures(request.bitmap); SDLCreateBitmapTextures(request.bitmap); + ResetBitmapAlpha(request.bitmap); + // set envelope request run-time values request.sx = sx; request.sy = sy; @@ -2941,16 +3070,22 @@ static void PrepareEnvelopeRequestToScreen(Bitmap *bitmap, int sx, int sy, request.ysize = ysize; } -void DrawEnvelopeRequestToScreen(int drawing_target, int drawing_stage) +void DrawEnvelopeRequestToScreen(int drawing_target) { if (global.use_envelope_request && - game.request_active_or_moving && - request.sort_priority > 0 && - drawing_target == DRAW_TO_SCREEN && - drawing_stage == DRAW_GLOBAL_ANIM_STAGE_2) + game.request_active && + drawing_target == DRAW_TO_SCREEN) { - BlitToScreen(request.bitmap, 0, 0, request.xsize, request.ysize, - request.sx, request.sy); + struct GraphicInfo *g = &graphic_info[IMG_BACKGROUND_REQUEST]; + + SetBitmapAlphaNextBlit(request.bitmap, g->alpha); + + if (g->draw_masked) + BlitToScreenMasked(request.bitmap, 0, 0, request.xsize, request.ysize, + request.sx, request.sy); + else + BlitToScreen(request.bitmap, 0, 0, request.xsize, request.ysize, + request.sx, request.sy); } } @@ -3021,7 +3156,7 @@ static void setRequestPosition(int *x, int *y, boolean add_border_size) setRequestPositionExt(x, y, request.width, request.height, add_border_size); } -static void DrawEnvelopeRequest(char *text) +static void DrawEnvelopeRequestText(int sx, int sy, char *text) { char *text_final = text; char *text_door_style = NULL; @@ -3039,15 +3174,11 @@ static void DrawEnvelopeRequest(char *text) int line_length = max_text_width / font_width; int max_lines = max_text_height / line_height; int text_width = line_length * font_width; - int width = request.width; - int height = request.height; - int tile_size = MAX(request.step_offset, 1); - int x_steps = width / tile_size; - int y_steps = height / tile_size; int sx_offset = border_size; int sy_offset = border_size; - int sx, sy; - int i, x, y; + + // force DOOR font inside door area + SetFontStatus(GAME_MODE_PSEUDO_DOOR); if (request.centered) sx_offset = (request.width - text_width) / 2; @@ -3056,6 +3187,13 @@ static void DrawEnvelopeRequest(char *text) { char *src_text_ptr, *dst_text_ptr; + if (maxWordLengthInRequestString(text) > line_length) + { + font_nr = FONT_REQUEST_NARROW; + font_width = getFontWidth(font_nr); + line_length = max_text_width / font_width; + } + text_door_style = checked_malloc(2 * strlen(text) + 1); src_text_ptr = text; @@ -3079,9 +3217,34 @@ static void DrawEnvelopeRequest(char *text) text_final = text_door_style; } + DrawTextBuffer(sx + sx_offset, sy + sy_offset, text_final, font_nr, + line_length, -1, max_lines, line_spacing, mask_mode, + request.autowrap, request.centered, FALSE); + + if (text_door_style) + free(text_door_style); + + ResetFontStatus(); +} + +static void DrawEnvelopeRequest(char *text, unsigned int req_state) +{ + DrawBuffer *drawto_last = drawto; + int graphic = IMG_BACKGROUND_REQUEST; + int width = request.width; + int height = request.height; + int tile_size = MAX(request.step_offset, 1); + int x_steps = width / tile_size; + int y_steps = height / tile_size; + int sx, sy; + int x, y; + setRequestPosition(&sx, &sy, FALSE); - ClearRectangle(backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE); + // draw complete envelope request to temporary bitmap + drawto = bitmap_db_store_1; + + ClearRectangle(drawto, sx, sy, width, height); for (y = 0; y < y_steps; y++) for (x = 0; x < x_steps; x++) @@ -3089,38 +3252,28 @@ static void DrawEnvelopeRequest(char *text) x, y, x_steps, y_steps, tile_size, tile_size); - // force DOOR font inside door area - SetFontStatus(GAME_MODE_PSEUDO_DOOR); - - DrawTextBuffer(sx + sx_offset, sy + sy_offset, text_final, font_nr, - line_length, -1, max_lines, line_spacing, mask_mode, - request.autowrap, request.centered, FALSE); - - ResetFontStatus(); - - for (i = 0; i < NUM_TOOL_BUTTONS; i++) - RedrawGadget(tool_gadget[i]); + // write text for request + DrawEnvelopeRequestText(sx, sy, text); - // store readily prepared envelope request for later use when animating - BlitBitmap(backbuffer, bitmap_db_store_2, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0); + MapToolButtons(req_state); - PrepareEnvelopeRequestToScreen(bitmap_db_store_2, sx, sy, width, height); + // restore pointer to drawing buffer + drawto = drawto_last; - if (text_door_style) - free(text_door_style); + // prepare complete envelope request from temporary bitmap + PrepareEnvelopeRequestToScreen(bitmap_db_store_1, sx, sy, width, height); } static void AnimateEnvelopeRequest(int anim_mode, int action) { - int graphic = IMG_BACKGROUND_REQUEST; - boolean draw_masked = graphic_info[graphic].draw_masked; + boolean game_ended = (game_status == GAME_MODE_PLAYING && checkGameEnded()); int delay_value_normal = request.step_delay; int delay_value_fast = delay_value_normal / 2; boolean ffwd_delay = (tape.playing && tape.fast_forward); boolean no_delay = (tape.warp_forward); int delay_value = (ffwd_delay ? delay_value_fast : delay_value_normal); - int anim_delay_value = MAX(1, (no_delay ? 0 : delay_value + 500 * 0) / 2); - unsigned int anim_delay = 0; + int anim_delay_value = MAX(1, (no_delay ? 0 : delay_value) / 2); + DelayCounter anim_delay = { anim_delay_value }; int tile_size = MAX(request.step_offset, 1); int max_xsize = request.width / tile_size; @@ -3162,11 +3315,12 @@ static void AnimateEnvelopeRequest(int anim_mode, int action) int dst_x, dst_y; int xx, yy; + if (game_ended) + HandleGameActions(); + setRequestPosition(&src_x, &src_y, FALSE); setRequestPositionExt(&dst_x, &dst_y, width, height, FALSE); - BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0); - for (yy = 0; yy < 2; yy++) { for (xx = 0; xx < 2; xx++) @@ -3178,22 +3332,21 @@ static void AnimateEnvelopeRequest(int anim_mode, int action) int xx_size = (xx ? tile_size : xsize_size_left); int yy_size = (yy ? tile_size : ysize_size_top); - if (draw_masked) - BlitBitmapMasked(bitmap_db_store_2, backbuffer, - src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy); - else - BlitBitmap(bitmap_db_store_2, backbuffer, - src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy); + // draw partial (animated) envelope request to temporary bitmap + BlitBitmap(bitmap_db_store_1, bitmap_db_store_2, + src_xx, src_yy, xx_size, yy_size, dst_xx, dst_yy); } } - PrepareEnvelopeRequestToScreen(backbuffer, dst_x, dst_y, width, height); + // prepare partial (animated) envelope request from temporary bitmap + PrepareEnvelopeRequestToScreen(bitmap_db_store_2, dst_x, dst_y, + width, height); redraw_mask |= REDRAW_FIELD; BackToFront(); - SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame); + SkipUntilDelayReached(&anim_delay, &i, last_frame); } ClearAutoRepeatKeyEvents(); @@ -3210,40 +3363,6 @@ static void ShowEnvelopeRequest(char *text, unsigned int req_state, int action) int main_anim_mode = (anim_mode == ANIM_NONE ? ANIM_VERTICAL|ANIM_HORIZONTAL: anim_mode == ANIM_DEFAULT ? ANIM_VERTICAL : anim_mode); - if (game_status == GAME_MODE_PLAYING) - BlitScreenToBitmap(backbuffer); - - SetDrawtoField(DRAW_TO_BACKBUFFER); - - // SetDrawBackgroundMask(REDRAW_NONE); - - if (action == ACTION_OPENING) - { - BlitBitmap(backbuffer, bitmap_db_store_1, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0); - - if (req_state & REQ_ASK) - { - MapGadget(tool_gadget[TOOL_CTRL_ID_YES]); - MapGadget(tool_gadget[TOOL_CTRL_ID_NO]); - MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_YES]); - MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_NO]); - } - else if (req_state & REQ_CONFIRM) - { - MapGadget(tool_gadget[TOOL_CTRL_ID_CONFIRM]); - MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_CONFIRM]); - } - else if (req_state & REQ_PLAYER) - { - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_1]); - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_2]); - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_3]); - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_4]); - } - - DrawEnvelopeRequest(text); - } - game.envelope_active = TRUE; // needed for RedrawPlayfield() events if (action == ACTION_OPENING) @@ -3267,20 +3386,6 @@ static void ShowEnvelopeRequest(char *text, unsigned int req_state, int action) } game.envelope_active = FALSE; - - if (action == ACTION_CLOSING) - BlitBitmap(bitmap_db_store_1, backbuffer, 0, 0, WIN_XSIZE, WIN_YSIZE, 0, 0); - - // SetDrawBackgroundMask(last_draw_background_mask); - - redraw_mask |= REDRAW_FIELD; - - BackToFront(); - - if (action == ACTION_CLOSING && - game_status == GAME_MODE_PLAYING && - level.game_engine_type == GAME_ENGINE_TYPE_RND) - SetDrawtoField(DRAW_TO_FIELDBUFFER); } static void DrawPreviewElement(int dst_x, int dst_y, int element, int tilesize) @@ -3303,7 +3408,7 @@ static void DrawPreviewElement(int dst_x, int dst_y, int element, int tilesize) void DrawLevel(int draw_background_mask) { - int x,y; + int x, y; SetMainBackgroundImage(IMG_BACKGROUND_PLAYING); SetDrawBackgroundMask(draw_background_mask); @@ -3320,7 +3425,7 @@ void DrawLevel(int draw_background_mask) void DrawSizedLevel(int size_x, int size_y, int scroll_x, int scroll_y, int tilesize) { - int x,y; + int x, y; for (x = 0; x < size_x; x++) for (y = 0; y < size_y; y++) @@ -3331,7 +3436,7 @@ void DrawSizedLevel(int size_x, int size_y, int scroll_x, int scroll_y, void DrawMiniLevel(int size_x, int size_y, int scroll_x, int scroll_y) { - int x,y; + int x, y; for (x = 0; x < size_x; x++) for (y = 0; y < size_y; y++) @@ -3464,15 +3569,17 @@ static void DrawPreviewLevelInfo(int mode) static void DrawPreviewLevelExt(boolean restart) { - static unsigned int scroll_delay = 0; - static unsigned int label_delay = 0; + static DelayCounter scroll_delay = { 0 }; + static DelayCounter label_delay = { 0 }; static int from_x, from_y, scroll_direction; static int label_state, label_counter; - unsigned int scroll_delay_value = preview.step_delay; boolean show_level_border = (BorderElement != EL_EMPTY); int level_xsize = lev_fieldx + (show_level_border ? 2 : 0); int level_ysize = lev_fieldy + (show_level_border ? 2 : 0); + scroll_delay.value = preview.step_delay; + label_delay.value = MICROLEVEL_LABEL_DELAY; + if (restart) { from_x = 0; @@ -3500,8 +3607,8 @@ static void DrawPreviewLevelExt(boolean restart) DrawPreviewLevelInfo(MICROLABEL_LEVEL_AUTHOR); // initialize delay counters - DelayReached(&scroll_delay, 0); - DelayReached(&label_delay, 0); + ResetDelayCounter(&scroll_delay); + ResetDelayCounter(&label_delay); if (leveldir_current->name) { @@ -3526,7 +3633,7 @@ static void DrawPreviewLevelExt(boolean restart) // scroll preview level, if needed if (preview.anim_mode != ANIM_NONE && (level_xsize > preview.xsize || level_ysize > preview.ysize) && - DelayReached(&scroll_delay, scroll_delay_value)) + DelayReached(&scroll_delay)) { switch (scroll_direction) { @@ -3584,7 +3691,7 @@ static void DrawPreviewLevelExt(boolean restart) if (!strEqual(level.name, NAMELESS_LEVEL_NAME) && !strEqual(level.author, ANONYMOUS_NAME) && !strEqual(level.author, leveldir_current->name) && - DelayReached(&label_delay, MICROLEVEL_LABEL_DELAY)) + DelayReached(&label_delay)) { int max_label_counter = 23; @@ -3644,7 +3751,7 @@ void DrawPreviewPlayers(void) { int element = level.field[x][y]; - if (ELEM_IS_PLAYER(element)) + if (IS_PLAYER_ELEMENT(element)) { int player_nr = GET_PLAYER_NR(element); @@ -3809,10 +3916,10 @@ void ClearNetworkPlayers(void) } static void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y, - int graphic, int sync_frame, + int graphic, int lx, int ly, int mask_mode) { - int frame = getGraphicAnimationFrame(graphic, sync_frame); + int frame = getGraphicAnimationFrameXY(graphic, lx, ly); if (mask_mode == USE_MASKING) DrawGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame); @@ -3831,6 +3938,18 @@ void DrawFixedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y, DrawFixedGraphicExt(dst_bitmap, x, y, graphic, frame); } +void DrawSizedGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y, + int graphic, int sync_frame, int tilesize, + int mask_mode) +{ + int frame = getGraphicAnimationFrame(graphic, sync_frame); + + if (mask_mode == USE_MASKING) + DrawSizedGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame, tilesize); + else + DrawSizedGraphicExt(dst_bitmap, x, y, graphic, frame, tilesize); +} + static void DrawGraphicAnimation(int x, int y, int graphic) { int lx = LEVELX(x), ly = LEVELY(y); @@ -3850,7 +3969,7 @@ static void DrawGraphicAnimation(int x, int y, int graphic) } DrawGraphicAnimationExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR, - graphic, GfxFrame[lx][ly], mask_mode); + graphic, lx, ly, mask_mode); MarkTileDirty(x, y); } @@ -3874,7 +3993,7 @@ void DrawFixedGraphicAnimation(int x, int y, int graphic) } DrawGraphicAnimationExt(drawto_field, FX + x * TILEX, FY + y * TILEY, - graphic, GfxFrame[lx][ly], mask_mode); + graphic, lx, ly, mask_mode); MarkTileDirty(x, y); } @@ -3898,9 +4017,15 @@ void DrawLevelGraphicAnimationIfNeeded(int x, int y, int graphic) if (!IN_LEV_FIELD(x, y) || !IN_SCR_FIELD(sx, sy)) return; + if (Tile[x][y] == EL_EMPTY) + graphic = el2img(GfxElementEmpty[x][y]); + if (!IS_NEW_FRAME(GfxFrame[x][y], graphic)) return; + if (ANIM_MODE(graphic) & (ANIM_TILED | ANIM_RANDOM_STATIC)) + return; + DrawGraphicAnimation(sx, sy, graphic); #if 1 @@ -3952,7 +4077,7 @@ static int getPlayerGraphic(struct PlayerInfo *player, int move_dir) return graphic; } else - return el_act_dir2img(player->artwork_element, player->GfxAction,move_dir); + return el_act_dir2img(player->artwork_element, player->GfxAction, move_dir); } static boolean equalGraphics(int graphic1, int graphic2) @@ -4101,9 +4226,6 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage) DrawDynamite(last_jx, last_jy); else DrawLevelField(last_jx, last_jy); - - if (player->is_pushing && IN_SCR_FIELD(SCREENX(next_jx), SCREENY(next_jy))) - DrawLevelElement(next_jx, next_jy, EL_EMPTY); } else if (drawing_stage == DRAW_PLAYER_STAGE_FIELD_UNDER_PLAYER) { @@ -4161,6 +4283,9 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage) if (!player->is_pushing || !player->is_moving) return; + if (Tile[next_jx][next_jy] == EL_EXPLOSION) + return; + int gfx_frame = GfxFrame[jx][jy]; if (!IS_MOVING(jx, jy)) // push movement already finished @@ -4182,14 +4307,12 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage) DrawLevelElement(jx, jy, Back[jx][jy]); else DrawLevelElement(jx, jy, EL_EMPTY); - - if (Back[next_jx][next_jy]) - DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]); - else - DrawLevelElement(next_jx, next_jy, EL_EMPTY); } - else if (Back[next_jx][next_jy]) + + if (Back[next_jx][next_jy]) DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]); + else + DrawLevelElement(next_jx, next_jy, EL_EMPTY); int px = SCREENX(jx), py = SCREENY(jy); int pxx = (TILEX - ABS(sxx)) * dx; @@ -4246,7 +4369,7 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage) if (IS_ACTIVE_BOMB(element)) { int graphic = el2img(element); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[jx][jy]); + int frame = getGraphicAnimationFrameXY(graphic, jx, jy); if (game.emulation == EMU_SUPAPLEX) DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame); @@ -4380,26 +4503,15 @@ void WaitForEventToContinue(void) } } -#define MAX_REQUEST_LINES 13 -#define MAX_REQUEST_LINE_FONT1_LEN 7 -#define MAX_REQUEST_LINE_FONT2_LEN 10 - static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) { - boolean game_just_ended = (game_status == GAME_MODE_PLAYING && - checkGameEnded()); + boolean game_ended = (game_status == GAME_MODE_PLAYING && checkGameEnded()); int draw_buffer_last = GetDrawtoField(); int width = request.width; int height = request.height; int sx, sy; int result; - // when showing request dialog after game ended, deactivate game panel - if (game_just_ended) - game.panel.active = FALSE; - - game.request_active = TRUE; - setRequestPosition(&sx, &sy, FALSE); button_status = MB_RELEASED; @@ -4409,21 +4521,13 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) while (result < 0) { - boolean event_handled = FALSE; - - if (game_just_ended) + if (game_ended) { SetDrawtoField(draw_buffer_game); HandleGameActions(); SetDrawtoField(DRAW_TO_BACKBUFFER); - - if (global.use_envelope_request) - { - // copy current state of request area to middle of playfield area - BlitBitmap(bitmap_db_store_2, drawto, sx, sy, width, height, sx, sy); - } } if (PendingEvent()) @@ -4432,14 +4536,13 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) while (NextValidEvent(&event)) { - event_handled = TRUE; - switch (event.type) { case EVENT_BUTTONPRESS: case EVENT_BUTTONRELEASE: case EVENT_MOTIONNOTIFY: { + DrawBuffer *drawto_last = drawto; int mx, my; if (event.type == EVENT_MOTIONNOTIFY) @@ -4462,9 +4565,25 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) button_status = MB_RELEASED; } + if (global.use_envelope_request) + { + // draw changed button states to temporary bitmap + drawto = bitmap_db_store_1; + } + // this sets 'request_gadget_id' HandleGadgets(mx, my, button_status); + if (global.use_envelope_request) + { + // restore pointer to drawing buffer + drawto = drawto_last; + + // prepare complete envelope request from temporary bitmap + PrepareEnvelopeRequestToScreen(bitmap_db_store_1, sx, sy, + width, height); + } + switch (request_gadget_id) { case TOOL_CTRL_ID_YES: @@ -4494,11 +4613,12 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) break; default: - // only check clickable animations if no request gadget clicked - HandleGlobalAnimClicks(mx, my, button_status, FALSE); break; } + // only needed to handle clickable pointer animations here + HandleGlobalAnimClicks(mx, my, button_status, FALSE); + break; } @@ -4515,7 +4635,7 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) case EVENT_KEYPRESS: { - Key key = GetEventKey((KeyEvent *)&event, TRUE); + Key key = GetEventKey((KeyEvent *)&event); switch (key) { @@ -4688,45 +4808,21 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game) } } - if (event_handled) - { - if (game_just_ended) - { - if (global.use_envelope_request) - { - // copy back current state of pressed buttons inside request area - BlitBitmap(drawto, bitmap_db_store_2, sx, sy, width, height, sx, sy); - } - } - - PrepareEnvelopeRequestToScreen(drawto, sx, sy, width, height); - } - BackToFront(); } SetDrawtoField(draw_buffer_last); - game.request_active = FALSE; - return result; } -static boolean RequestDoor(char *text, unsigned int req_state) +static void DoRequestBefore(void) { - int draw_buffer_last = GetDrawtoField(); - unsigned int old_door_state; - int max_request_line_len = MAX_REQUEST_LINE_FONT1_LEN; - int font_nr = FONT_TEXT_2; - char *text_ptr; - int result; - int ty; + boolean game_ended = (game_status == GAME_MODE_PLAYING && checkGameEnded()); - if (maxWordLengthInRequestString(text) > MAX_REQUEST_LINE_FONT1_LEN) - { - max_request_line_len = MAX_REQUEST_LINE_FONT2_LEN; - font_nr = FONT_TEXT_1; - } + // when showing request dialog after game ended, deactivate game panel + if (game_ended) + game.panel.active = FALSE; if (game_status == GAME_MODE_PLAYING) BlitScreenToBitmap(backbuffer); @@ -4740,41 +4836,89 @@ static boolean RequestDoor(char *text, unsigned int req_state) // pause network game while waiting for request to answer if (network.enabled && game_status == GAME_MODE_PLAYING && - !game.all_players_gone && - req_state & REQUEST_WAIT_FOR_INPUT) + !game.all_players_gone) SendToServer_PausePlaying(); - old_door_state = GetDoorState(); - // simulate releasing mouse button over last gadget, if still pressed if (button_status) HandleGadgets(-1, -1, 0); UnmapAllGadgets(); +} - // draw released gadget before proceeding - // BackToFront(); +static void DoRequestAfter(void) +{ + RemapAllGadgets(); - if (old_door_state & DOOR_OPEN_1) + if (game_status == GAME_MODE_PLAYING) { - CloseDoor(DOOR_CLOSE_1); + SetPanelBackground(); + SetDrawBackgroundMask(REDRAW_DOOR_1); + } + else + { + SetDrawBackgroundMask(REDRAW_FIELD); + } - // save old door content - BlitBitmap(bitmap_db_door_1, bitmap_db_door_1, - 0 * DXSIZE, 0, DXSIZE, DYSIZE, 1 * DXSIZE, 0); + // continue network game after request + if (network.enabled && + game_status == GAME_MODE_PLAYING && + !game.all_players_gone) + SendToServer_ContinuePlaying(); + + // restore deactivated drawing when quick-loading level tape recording + if (tape.playing && tape.deactivate_display) + TapeDeactivateDisplayOn(); +} + +static void setRequestDoorTextProperties(char *text, + int text_spacing, + int line_spacing, + int *set_font_nr, + int *set_max_lines, + int *set_max_line_length) +{ + struct RectWithBorder *vp_door_1 = &viewport.door_1[game_status]; + struct TextPosInfo *pos = &request.button.confirm; + int button_ypos = pos->y; + int font_nr = FONT_TEXT_2; + int font_width = getFontWidth(font_nr); + int font_height = getFontHeight(font_nr); + int line_height = font_height + line_spacing; + int max_text_width = vp_door_1->width; + int max_text_height = button_ypos - 2 * text_spacing; + int max_line_length = max_text_width / font_width; + int max_lines = max_text_height / line_height; + + if (maxWordLengthInRequestString(text) > max_line_length) + { + font_nr = FONT_TEXT_1; + font_width = getFontWidth(font_nr); + max_line_length = max_text_width / font_width; } - SetDoorBackgroundImage(IMG_BACKGROUND_DOOR); - SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1); + *set_font_nr = font_nr; + *set_max_lines = max_lines; + *set_max_line_length = max_line_length; +} - // clear door drawing field - DrawBackground(DX, DY, DXSIZE, DYSIZE); +static void DrawRequestDoorText(char *text) +{ + char *text_ptr = text; + int text_spacing = 8; + int line_spacing = 2; + int max_request_lines; + int max_request_line_len; + int font_nr; + int ty; // force DOOR font inside door area SetFontStatus(GAME_MODE_PSEUDO_DOOR); - // write text for request - for (text_ptr = text, ty = 0; ty < MAX_REQUEST_LINES; ty++) + setRequestDoorTextProperties(text, text_spacing, line_spacing, &font_nr, + &max_request_lines, &max_request_line_len); + + for (text_ptr = text, ty = 0; ty < max_request_lines; ty++) { char text_line[max_request_line_len + 1]; int tx, tl, tc = 0; @@ -4785,7 +4929,6 @@ static boolean RequestDoor(char *text, unsigned int req_state) for (tl = 0, tx = 0; tx < max_request_line_len; tl++, tx++) { tc = *(text_ptr + tx); - // if (!tc || tc == ' ') if (!tc || tc == ' ' || tc == '?' || tc == '!') break; } @@ -4800,61 +4943,50 @@ static boolean RequestDoor(char *text, unsigned int req_state) continue; } - strncpy(text_line, text_ptr, tl); - text_line[tl] = 0; - - DrawText(DX + (DXSIZE - tl * getFontWidth(font_nr)) / 2, - DY + 8 + ty * (getFontHeight(font_nr) + 2), - text_line, font_nr); - - text_ptr += tl + (tc == ' ' ? 1 : 0); - // text_ptr += tl + (tc == ' ' || tc == '?' || tc == '!' ? 1 : 0); - } - - ResetFontStatus(); - - if (req_state & REQ_ASK) - { - MapGadget(tool_gadget[TOOL_CTRL_ID_YES]); - MapGadget(tool_gadget[TOOL_CTRL_ID_NO]); - MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_YES]); - MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_NO]); - } - else if (req_state & REQ_CONFIRM) - { - MapGadget(tool_gadget[TOOL_CTRL_ID_CONFIRM]); - MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_CONFIRM]); - } - else if (req_state & REQ_PLAYER) - { - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_1]); - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_2]); - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_3]); - MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_4]); + strncpy(text_line, text_ptr, tl); + text_line[tl] = 0; + + DrawText(DX + (DXSIZE - tl * getFontWidth(font_nr)) / 2, + DY + text_spacing + ty * (getFontHeight(font_nr) + line_spacing), + text_line, font_nr); + + text_ptr += tl + (tc == ' ' ? 1 : 0); } - // copy request gadgets to door backbuffer - BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0); + ResetFontStatus(); +} - OpenDoor(DOOR_OPEN_1); +static int RequestDoor(char *text, unsigned int req_state) +{ + unsigned int old_door_state = GetDoorState(); + int draw_buffer_last = GetDrawtoField(); + int result; - if (!(req_state & REQUEST_WAIT_FOR_INPUT)) + if (old_door_state & DOOR_OPEN_1) { - if (game_status == GAME_MODE_PLAYING) - { - SetPanelBackground(); - SetDrawBackgroundMask(REDRAW_DOOR_1); - } - else - { - SetDrawBackgroundMask(REDRAW_FIELD); - } + CloseDoor(DOOR_CLOSE_1); - return FALSE; + // save old door content + BlitBitmap(bitmap_db_door_1, bitmap_db_door_1, + 0, 0, DXSIZE, DYSIZE, DXSIZE, 0); } + SetDoorBackgroundImage(IMG_BACKGROUND_DOOR); SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1); + // clear door drawing field + DrawBackground(DX, DY, DXSIZE, DYSIZE); + + // write text for request + DrawRequestDoorText(text); + + MapToolButtons(req_state); + + // copy request gadgets to door backbuffer + BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0); + + OpenDoor(DOOR_OPEN_1); + // ---------- handle request buttons ---------- result = RequestHandleEvents(req_state, draw_buffer_last); @@ -4869,85 +5001,17 @@ static boolean RequestDoor(char *text, unsigned int req_state) OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK); } - RemapAllGadgets(); - - if (game_status == GAME_MODE_PLAYING) - { - SetPanelBackground(); - SetDrawBackgroundMask(REDRAW_DOOR_1); - } - else - { - SetDrawBackgroundMask(REDRAW_FIELD); - } - - // continue network game after request - if (network.enabled && - game_status == GAME_MODE_PLAYING && - !game.all_players_gone && - req_state & REQUEST_WAIT_FOR_INPUT) - SendToServer_ContinuePlaying(); - - // restore deactivated drawing when quick-loading level tape recording - if (tape.playing && tape.deactivate_display) - TapeDeactivateDisplayOn(); - return result; } -static boolean RequestEnvelope(char *text, unsigned int req_state) +static int RequestEnvelope(char *text, unsigned int req_state) { int draw_buffer_last = GetDrawtoField(); int result; - if (game_status == GAME_MODE_PLAYING) - BlitScreenToBitmap(backbuffer); - - // disable deactivated drawing when quick-loading level tape recording - if (tape.playing && tape.deactivate_display) - TapeDeactivateDisplayOff(TRUE); - - SetMouseCursor(CURSOR_DEFAULT); - - // pause network game while waiting for request to answer - if (network.enabled && - game_status == GAME_MODE_PLAYING && - !game.all_players_gone && - req_state & REQUEST_WAIT_FOR_INPUT) - SendToServer_PausePlaying(); - - // simulate releasing mouse button over last gadget, if still pressed - if (button_status) - HandleGadgets(-1, -1, 0); - - UnmapAllGadgets(); - - // (replace with setting corresponding request background) - // SetDoorBackgroundImage(IMG_BACKGROUND_DOOR); - // SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1); - - // clear door drawing field - // DrawBackground(DX, DY, DXSIZE, DYSIZE); - + DrawEnvelopeRequest(text, req_state); ShowEnvelopeRequest(text, req_state, ACTION_OPENING); - if (!(req_state & REQUEST_WAIT_FOR_INPUT)) - { - if (game_status == GAME_MODE_PLAYING) - { - SetPanelBackground(); - SetDrawBackgroundMask(REDRAW_DOOR_1); - } - else - { - SetDrawBackgroundMask(REDRAW_FIELD); - } - - return FALSE; - } - - SetDrawBackgroundMask(REDRAW_FIELD | REDRAW_DOOR_1); - // ---------- handle request buttons ---------- result = RequestHandleEvents(req_state, draw_buffer_last); @@ -4955,49 +5019,30 @@ static boolean RequestEnvelope(char *text, unsigned int req_state) ShowEnvelopeRequest(text, req_state, ACTION_CLOSING); - RemapAllGadgets(); - - if (game_status == GAME_MODE_PLAYING) - { - SetPanelBackground(); - SetDrawBackgroundMask(REDRAW_DOOR_1); - } - else - { - SetDrawBackgroundMask(REDRAW_FIELD); - } - - // continue network game after request - if (network.enabled && - game_status == GAME_MODE_PLAYING && - !game.all_players_gone && - req_state & REQUEST_WAIT_FOR_INPUT) - SendToServer_ContinuePlaying(); - - // restore deactivated drawing when quick-loading level tape recording - if (tape.playing && tape.deactivate_display) - TapeDeactivateDisplayOn(); - return result; } -boolean Request(char *text, unsigned int req_state) +int Request(char *text, unsigned int req_state) { boolean overlay_enabled = GetOverlayEnabled(); - boolean result; + int result; - game.request_active_or_moving = TRUE; + game.request_active = TRUE; SetOverlayEnabled(FALSE); + DoRequestBefore(); + if (global.use_envelope_request) result = RequestEnvelope(text, req_state); else result = RequestDoor(text, req_state); + DoRequestAfter(); + SetOverlayEnabled(overlay_enabled); - game.request_active_or_moving = FALSE; + game.request_active = FALSE; return result; } @@ -5262,8 +5307,7 @@ unsigned int MoveDoor(unsigned int door_state) }; static int door1 = DOOR_CLOSE_1; static int door2 = DOOR_CLOSE_2; - unsigned int door_delay = 0; - unsigned int door_delay_value; + DelayCounter door_delay = { 0 }; int i; if (door_state == DOOR_GET_STATE) @@ -5302,6 +5346,7 @@ unsigned int MoveDoor(unsigned int door_state) if (door_state & DOOR_ACTION) { + boolean game_ended = (game_status == GAME_MODE_PLAYING && checkGameEnded()); boolean door_panel_drawn[NUM_DOORS]; boolean panel_has_doors[NUM_DOORS]; boolean door_part_skip[MAX_DOOR_PARTS]; @@ -5313,7 +5358,6 @@ unsigned int MoveDoor(unsigned int door_state) int num_move_steps = 0; // number of animation steps for all doors int max_move_delay_doors_only = 0; // delay for doors only (no panel) int num_move_steps_doors_only = 0; // steps for doors only (no panel) - int current_move_delay = 0; int start = 0; int k; @@ -5379,7 +5423,7 @@ unsigned int MoveDoor(unsigned int door_state) num_move_steps = max_move_delay / max_step_delay; num_move_steps_doors_only = max_move_delay_doors_only / max_step_delay; - door_delay_value = max_step_delay; + door_delay.value = max_step_delay; if ((door_state & DOOR_NO_DELAY) || setup.quick_doors) { @@ -5462,7 +5506,7 @@ unsigned int MoveDoor(unsigned int door_state) { int k2_door = (door_opening ? k : num_move_steps_doors_only - k - 1); int kk_door = MAX(0, k2_door); - int sync_frame = kk_door * door_delay_value; + int sync_frame = kk_door * door_delay.value; int frame = getGraphicAnimationFrame(dpc->graphic, sync_frame); getFixedGraphicSource(dpc->graphic, frame, &bitmap, @@ -5569,11 +5613,12 @@ unsigned int MoveDoor(unsigned int door_state) if (!(door_state & DOOR_NO_DELAY)) { - BackToFront(); + if (game_ended) + HandleGameActions(); - SkipUntilDelayReached(&door_delay, door_delay_value, &k, last_frame); + BackToFront(); - current_move_delay += max_step_delay; + SkipUntilDelayReached(&door_delay, &k, last_frame); // prevent OS (Windows) from complaining about program not responding CheckQuitEvent(); @@ -5587,14 +5632,19 @@ unsigned int MoveDoor(unsigned int door_state) { // wait for specified door action post delay if (door_state & DOOR_ACTION_1 && door_state & DOOR_ACTION_2) - door_delay_value = MAX(door_1.post_delay, door_2.post_delay); + door_delay.value = MAX(door_1.post_delay, door_2.post_delay); else if (door_state & DOOR_ACTION_1) - door_delay_value = door_1.post_delay; + door_delay.value = door_1.post_delay; else if (door_state & DOOR_ACTION_2) - door_delay_value = door_2.post_delay; + door_delay.value = door_2.post_delay; + + while (!DelayReached(&door_delay)) + { + if (game_ended) + HandleGameActions(); - while (!DelayReached(&door_delay, door_delay_value)) BackToFront(); + } } } @@ -5770,6 +5820,10 @@ void CreateToolButtons(void) int y = pos->y; int id = i; + // do not use touch buttons if overlay touch buttons are disabled + if (is_touch_button && !setup.touch.overlay_buttons) + continue; + if (global.use_envelope_request && !is_touch_button) { setRequestPosition(&base_x, &base_y, TRUE); @@ -5806,7 +5860,8 @@ void CreateToolButtons(void) } } - if (id >= TOOL_CTRL_ID_PLAYER_1 && id <= TOOL_CTRL_ID_PLAYER_4) + if (id >= TOOL_CTRL_ID_PLAYER_1 && id <= TOOL_CTRL_ID_PLAYER_4 && + pos->draw_player) { int player_nr = id - TOOL_CTRL_ID_PLAYER_1; @@ -5852,6 +5907,29 @@ void FreeToolButtons(void) FreeGadget(tool_gadget[i]); } +static void MapToolButtons(unsigned int req_state) +{ + if (req_state & REQ_ASK) + { + MapGadget(tool_gadget[TOOL_CTRL_ID_YES]); + MapGadget(tool_gadget[TOOL_CTRL_ID_NO]); + MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_YES]); + MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_NO]); + } + else if (req_state & REQ_CONFIRM) + { + MapGadget(tool_gadget[TOOL_CTRL_ID_CONFIRM]); + MapGadget(tool_gadget[TOOL_CTRL_ID_TOUCH_CONFIRM]); + } + else if (req_state & REQ_PLAYER) + { + MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_1]); + MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_2]); + MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_3]); + MapGadget(tool_gadget[TOOL_CTRL_ID_PLAYER_4]); + } +} + static void UnmapToolButtons(void) { int i; @@ -8107,6 +8185,10 @@ int map_element_RND_to_MM(int element_rnd) element_rnd <= EL_MM_END_2 ? EL_MM_START_2_NATIVE + element_rnd - EL_MM_START_2 : + element_rnd >= EL_MM_START_3 && + element_rnd <= EL_MM_END_3 ? + EL_MM_START_3_NATIVE + element_rnd - EL_MM_START_3 : + element_rnd >= EL_CHAR_START && element_rnd <= EL_CHAR_END ? EL_MM_CHAR_START_NATIVE + element_rnd - EL_CHAR_START : @@ -8115,10 +8197,6 @@ int map_element_RND_to_MM(int element_rnd) element_rnd <= EL_MM_RUNTIME_END ? EL_MM_RUNTIME_START_NATIVE + element_rnd - EL_MM_RUNTIME_START : - element_rnd >= EL_MM_DUMMY_START && - element_rnd <= EL_MM_DUMMY_END ? - EL_MM_DUMMY_START_NATIVE + element_rnd - EL_MM_DUMMY_START : - EL_MM_EMPTY_NATIVE); } @@ -8136,6 +8214,10 @@ int map_element_MM_to_RND(int element_mm) element_mm <= EL_MM_END_2_NATIVE ? EL_MM_START_2 + element_mm - EL_MM_START_2_NATIVE : + element_mm >= EL_MM_START_3_NATIVE && + element_mm <= EL_MM_END_3_NATIVE ? + EL_MM_START_3 + element_mm - EL_MM_START_3_NATIVE : + element_mm >= EL_MM_CHAR_START_NATIVE && element_mm <= EL_MM_CHAR_END_NATIVE ? EL_CHAR_START + element_mm - EL_MM_CHAR_START_NATIVE : @@ -8144,10 +8226,6 @@ int map_element_MM_to_RND(int element_mm) element_mm <= EL_MM_RUNTIME_END_NATIVE ? EL_MM_RUNTIME_START + element_mm - EL_MM_RUNTIME_START_NATIVE : - element_mm >= EL_MM_DUMMY_START_NATIVE && - element_mm <= EL_MM_DUMMY_END_NATIVE ? - EL_MM_DUMMY_START + element_mm - EL_MM_DUMMY_START_NATIVE : - EL_EMPTY); } @@ -8241,6 +8319,11 @@ int el2img_mm(int element_mm) return el2img(map_element_MM_to_RND(element_mm)); } +int el_act2img_mm(int element_mm, int action) +{ + return el_act2img(map_element_MM_to_RND(element_mm), action); +} + int el_act_dir2img(int element, int action, int direction) { element = GFX_ELEMENT(element); @@ -8698,6 +8781,8 @@ void SetGfxAnimation_EM(struct GraphicInfo_EM *g_em, if (graphic_info[graphic].anim_global_sync) sync_frame = FrameCounter; + else if (graphic_info[graphic].anim_global_anim_sync) + sync_frame = getGlobalAnimSyncFrame(); else if (IN_FIELD(x, y, MAX_LEV_FIELDX, MAX_LEV_FIELDY)) sync_frame = GfxFrame[x][y]; else @@ -8757,6 +8842,8 @@ void getGraphicSourceObjectExt_EM(struct GraphicInfo_EM *g_em, if (graphic_info[graphic].anim_global_sync) sync_frame = FrameCounter; + else if (graphic_info[graphic].anim_global_anim_sync) + sync_frame = getGlobalAnimSyncFrame(); else if (IN_FIELD(x, y, MAX_LEV_FIELDX, MAX_LEV_FIELDY)) sync_frame = GfxFrame[x][y]; else @@ -9220,7 +9307,7 @@ void InitGraphicInfo_EM(void) } } -static void CheckSaveEngineSnapshot_EM(byte action[MAX_PLAYERS], int frame, +static void CheckSaveEngineSnapshot_EM(int frame, boolean any_player_moving, boolean any_player_snapping, boolean any_player_dropping) @@ -9277,7 +9364,7 @@ static void CheckSaveEngineSnapshot_MM(boolean element_clicked, } } -boolean CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame, +boolean CheckSingleStepMode_EM(int frame, boolean any_player_moving, boolean any_player_snapping, boolean any_player_dropping) @@ -9286,7 +9373,7 @@ boolean CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame, if (frame == 7 && !any_player_dropping && FrameCounter > 6) TapeTogglePause(TAPE_TOGGLE_AUTOMATIC); - CheckSaveEngineSnapshot_EM(action, frame, any_player_moving, + CheckSaveEngineSnapshot_EM(frame, any_player_moving, any_player_snapping, any_player_dropping); return tape.pausing; @@ -9320,7 +9407,7 @@ void CheckSingleStepMode_MM(boolean element_clicked, } void getGraphicSource_SP(struct GraphicInfo_SP *g_sp, - int graphic, int sync_frame, int x, int y) + int graphic, int sync_frame) { int frame = getGraphicAnimationFrame(graphic, sync_frame); @@ -9337,6 +9424,17 @@ int getGraphicInfo_Delay(int graphic) return graphic_info[graphic].anim_delay; } +boolean getGraphicInfo_NewFrame(int x, int y, int graphic) +{ + if (!IS_NEW_FRAME(GfxFrame[x][y], graphic)) + return FALSE; + + if (ANIM_MODE(graphic) & (ANIM_TILED | ANIM_RANDOM_STATIC)) + return FALSE; + + return TRUE; +} + void PlayMenuSoundExt(int sound) { if (sound == SND_UNDEFINED) @@ -9659,9 +9757,9 @@ void ChangeViewportPropertiesIfNeeded(void) { boolean use_mini_tilesize = (level.game_engine_type == GAME_ENGINE_TYPE_MM ? FALSE : setup.small_game_graphics); - int gfx_game_mode = game_status; - int gfx_game_mode2 = (game_status == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT : - game_status); + int gfx_game_mode = getGlobalGameStatus(game_status); + int gfx_game_mode2 = (gfx_game_mode == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT : + gfx_game_mode); struct RectWithBorder *vp_window = &viewport.window[gfx_game_mode]; struct RectWithBorder *vp_playfield = &viewport.playfield[gfx_game_mode]; struct RectWithBorder *vp_door_1 = &viewport.door_1[gfx_game_mode]; @@ -9873,3 +9971,212 @@ void ChangeViewportPropertiesIfNeeded(void) InitGraphicInfo_EM(); } } + +void OpenURL(char *url) +{ +#if SDL_VERSION_ATLEAST(2,0,14) + SDL_OpenURL(url); +#else + Warn("SDL_OpenURL(\"%s\") not supported by SDL %d.%d.%d!", + url, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL); + Warn("Please upgrade to at least SDL 2.0.14 for URL support!"); +#endif +} + +void OpenURLFromHash(SetupFileHash *hash, int hash_key) +{ + OpenURL(getHashEntry(hash, int2str(hash_key, 0))); +} + + +// ============================================================================ +// tests +// ============================================================================ + +#if defined(PLATFORM_WINDOWS) +/* FILETIME of Jan 1 1970 00:00:00. */ +static const unsigned __int64 epoch = ((unsigned __int64) 116444736000000000ULL); + +/* + * timezone information is stored outside the kernel so tzp isn't used anymore. + * + * Note: this function is not for Win32 high precision timing purpose. See + * elapsed_time(). + */ +static int gettimeofday_windows(struct timeval * tp, struct timezone * tzp) +{ + FILETIME file_time; + SYSTEMTIME system_time; + ULARGE_INTEGER ularge; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + ularge.LowPart = file_time.dwLowDateTime; + ularge.HighPart = file_time.dwHighDateTime; + + tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + + return 0; +} +#endif + +static char *test_init_uuid_random_function_simple(void) +{ + static char seed_text[100]; + unsigned int seed = InitSimpleRandom(NEW_RANDOMIZE); + + sprintf(seed_text, "%d", seed); + + return seed_text; +} + +static char *test_init_uuid_random_function_better(void) +{ + static char seed_text[100]; + struct timeval current_time; + + gettimeofday(¤t_time, NULL); + + prng_seed_bytes(¤t_time, sizeof(current_time)); + + sprintf(seed_text, "%ld.%ld", + (long)current_time.tv_sec, + (long)current_time.tv_usec); + + return seed_text; +} + +#if defined(PLATFORM_WINDOWS) +static char *test_init_uuid_random_function_better_windows(void) +{ + static char seed_text[100]; + struct timeval current_time; + + gettimeofday_windows(¤t_time, NULL); + + prng_seed_bytes(¤t_time, sizeof(current_time)); + + sprintf(seed_text, "%ld.%ld", + (long)current_time.tv_sec, + (long)current_time.tv_usec); + + return seed_text; +} +#endif + +static unsigned int test_uuid_random_function_simple(int max) +{ + return GetSimpleRandom(max); +} + +static unsigned int test_uuid_random_function_better(int max) +{ + return (max > 0 ? prng_get_uint() % max : 0); +} + +#if defined(PLATFORM_WINDOWS) +#define NUM_UUID_TESTS 3 +#else +#define NUM_UUID_TESTS 2 +#endif + +static void TestGeneratingUUIDs_RunTest(int nr, int always_seed, int num_uuids) +{ + struct hashtable *hash_seeds = + create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal); + struct hashtable *hash_uuids = + create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal); + static char message[100]; + int i; + + char *random_name = (nr == 0 ? "simple" : "better"); + char *random_type = (always_seed ? "always" : "only once"); + char *(*init_random_function)(void) = + (nr == 0 ? + test_init_uuid_random_function_simple : + test_init_uuid_random_function_better); + unsigned int (*random_function)(int) = + (nr == 0 ? + test_uuid_random_function_simple : + test_uuid_random_function_better); + int xpos = 40; + +#if defined(PLATFORM_WINDOWS) + if (nr == 2) + { + random_name = "windows"; + init_random_function = test_init_uuid_random_function_better_windows; + } +#endif + + ClearField(); + + DrawTextF(xpos, 40, FC_GREEN, "Test: Generating UUIDs"); + DrawTextF(xpos, 80, FC_YELLOW, "Test %d.%d:", nr + 1, always_seed + 1); + + DrawTextF(xpos, 100, FC_YELLOW, "Random Generator Name: %s", random_name); + DrawTextF(xpos, 120, FC_YELLOW, "Seeding Random Generator: %s", random_type); + DrawTextF(xpos, 140, FC_YELLOW, "Number of UUIDs generated: %d", num_uuids); + + DrawTextF(xpos, 180, FC_GREEN, "Please wait ..."); + + BackToFront(); + + // always initialize random number generator at least once + init_random_function(); + + unsigned int time_start = SDL_GetTicks(); + + for (i = 0; i < num_uuids; i++) + { + if (always_seed) + { + char *seed = getStringCopy(init_random_function()); + + hashtable_remove(hash_seeds, seed); + hashtable_insert(hash_seeds, seed, "1"); + } + + char *uuid = getStringCopy(getUUIDExt(random_function)); + + hashtable_remove(hash_uuids, uuid); + hashtable_insert(hash_uuids, uuid, "1"); + } + + int num_unique_seeds = hashtable_count(hash_seeds); + int num_unique_uuids = hashtable_count(hash_uuids); + + unsigned int time_needed = SDL_GetTicks() - time_start; + + DrawTextF(xpos, 220, FC_YELLOW, "Time needed: %d ms", time_needed); + + DrawTextF(xpos, 240, FC_YELLOW, "Number of unique UUIDs: %d", num_unique_uuids); + + if (always_seed) + DrawTextF(xpos, 260, FC_YELLOW, "Number of unique seeds: %d", num_unique_seeds); + + if (nr == NUM_UUID_TESTS - 1 && always_seed) + DrawTextF(xpos, 300, FC_GREEN, "All tests done!"); + else + DrawTextF(xpos, 300, FC_GREEN, "Confirm dialog for next test ..."); + + sprintf(message, "Test %d.%d finished!", nr + 1, always_seed + 1); + + Request(message, REQ_CONFIRM); + + hashtable_destroy(hash_seeds, 0); + hashtable_destroy(hash_uuids, 0); +} + +void TestGeneratingUUIDs(void) +{ + int num_uuids = 1000000; + int i, j; + + for (i = 0; i < NUM_UUID_TESTS; i++) + for (j = 0; j < 2; j++) + TestGeneratingUUIDs_RunTest(i, j, num_uuids); + + CloseAllAndExit(0); +} diff --git a/src/tools.h b/src/tools.h index cb8c9343..1ccfcea3 100644 --- a/src/tools.h +++ b/src/tools.h @@ -63,8 +63,6 @@ #define REQ_STAY_CLOSED (1 << 4) #define REQ_REOPEN (1 << 5) -#define REQUEST_WAIT_FOR_INPUT (REQ_ASK | REQ_CONFIRM | REQ_PLAYER) - int getFieldbufferOffsetX_RND(int, int); int getFieldbufferOffsetY_RND(int, int); @@ -85,7 +83,7 @@ void DrawMaskedBorder_DOOR_3(void); void DrawMaskedBorder_ALL(void); void DrawMaskedBorder(int); void DrawMaskedBorderToTarget(int); -void DrawTileCursor(int); +void DrawTileCursor(int, int); void SetDrawtoField(int); int GetDrawtoField(void); @@ -108,9 +106,12 @@ void FadeSetDisabled(void); void FadeSkipNextFadeIn(void); void FadeSkipNextFadeOut(void); +int getImageFromGraphicOrDefault(int, int); Bitmap *getGlobalBorderBitmapFromStatus(int); void ClearField(void); + +void SetBackgroundImage(int, int); void SetWindowBackgroundImageIfDefined(int); void SetMainBackgroundImageIfDefined(int); void SetDoorBackgroundImageIfDefined(int); @@ -126,14 +127,17 @@ void RedrawGlobalBorder(void); void MarkTileDirty(int, int); void SetBorderElement(void); -void FloodFillLevel(int, int, int, short[][MAX_LEV_FIELDY], int, int); -void FloodFillLevelExt(int, int, int, int, int y, short field[][y], int, int); +void FloodFillLevel(int, int, int, short[MAX_LEV_FIELDX][MAX_LEV_FIELDY], int, int); +void FloodFillLevelExt(int, int, int, int x, int y, short field[x][y], int, int); void SetRandomAnimationValue(int, int); +void SetAnimationFirstLevel(int); int getGraphicAnimationFrame(int, int); +int getGraphicAnimationFrameXY(int, int, int); void DrawFixedGraphicAnimation(int, int, int); void DrawFixedGraphicAnimationExt(DrawBuffer *, int, int, int, int, int); +void DrawSizedGraphicAnimationExt(DrawBuffer *, int, int, int, int, int, int); void DrawLevelGraphicAnimation(int, int, int); void DrawLevelElementAnimation(int, int, int); @@ -150,6 +154,7 @@ void getSizedGraphicSourceExt(int, int, int, Bitmap **, int *, int *, boolean); void getSizedGraphicSource(int, int, int, Bitmap **, int *, int *); void getFixedGraphicSource(int, int, Bitmap **, int *, int *); void getMiniGraphicSource(int, Bitmap **, int *, int *); +void getGlobalAnimGraphicSource(int, int, Bitmap **, int *, int *); void getGraphicSource(int, int, Bitmap **, int *, int *); void DrawGraphic(int, int, int, int); @@ -181,6 +186,7 @@ void DrawLevelFieldCrumbled(int, int); void DrawLevelFieldCrumbledDigging(int, int, int, int); void DrawLevelFieldCrumbledNeighbours(int, int); void DrawScreenGraphic(int, int, int, int); +void DrawLevelGraphic(int, int, int, int); void DrawScreenElement(int, int, int); void DrawLevelElement(int, int, int); void DrawScreenField(int, int); @@ -196,7 +202,7 @@ void DrawMiniElementOrWall(int, int, int, int); void ShowEnvelope(int); void ShowEnvelopeDoor(char *, int); -void DrawEnvelopeRequestToScreen(int, int); +void DrawEnvelopeRequestToScreen(int); void DrawLevel(int); void DrawSizedLevel(int, int, int, int, int); @@ -209,7 +215,7 @@ void DrawNetworkPlayers(void); void ClearNetworkPlayers(void); void WaitForEventToContinue(void); -boolean Request(char *, unsigned int); +int Request(char *, unsigned int); void InitGraphicCompatibilityInfo_Doors(void); void InitDoors(void); unsigned int OpenDoor(unsigned int); @@ -297,4 +303,9 @@ void ChangeViewportPropertiesIfNeeded(void); boolean CheckIfAllViewportsHaveChanged(void); boolean CheckFadeAll(void); +void OpenURL(char *); +void OpenURLFromHash(SetupFileHash *, int); + +void TestGeneratingUUIDs(void); + #endif // TOOLS_H