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
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.
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.
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.
# (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
# development targets
# -----------------------------------------------------------------------------
-MAKE_ENGINETEST = ./Scripts/make_enginetest.sh
+MAKE_ENGINETEST = ./tests/enginetest/enginetest.sh
MAKE_LEVELSKETCH = ./Scripts/make_levelsketch_images.sh
auto-conf:
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)
dist-package-android:
$(MAKE_DIST) package android
+dist-package-emscripten:
+ $(MAKE_DIST) package emscripten
+
dist-copy-package-linux:
$(MAKE_DIST) copy-package linux
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
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
$(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
$(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
upload-all: dist-upload-all
+deploy-all: dist-deploy-all
+
release-all: dist-release-all
-SDL2-2.0.12
+SDL2-2.0.20
SDL2_image-2.0.5
SDL2_mixer-2.0.4
SDL2_net-2.0.1
}
android {
- compileSdkVersion 26
+ compileSdkVersion 31
defaultConfig {
if (buildAsApplication) {
}
minSdkVersion 17
- targetSdkVersion 26
+ targetSdkVersion 31
versionCode __VERSION_CODE__
versionName "__VERSION_NAME__"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true">
- <activity android:name="RocksNDiamonds"
+ <activity android:name="rocksndiamonds"
android:label="@string/app_name"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance"
android:configChanges="keyboardHidden|orientation|screenSize"
+ android:screenOrientation="fullUser"
+ android:preferMinimalPostProcessing="true"
+ android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+++ /dev/null
-
-package org.artsoft.rocksndiamonds;
-
-import org.libsdl.app.SDLActivity;
-
-public class RocksNDiamonds extends SDLActivity { }
--- /dev/null
+
+package org.artsoft.rocksndiamonds;
+
+import org.libsdl.app.SDLActivity;
+
+public class rocksndiamonds extends SDLActivity { }
return "Steam Controller";
}
- @Override
+ @Override
public UsbDevice getDevice() {
- return null;
- }
+ return null;
+ }
@Override
public boolean open() {
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;
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);
{
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
}
-
- initializeUSB();
- initializeBluetooth();
}
public Context getContext() {
private void initializeUSB() {
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
+ if (mUsbManager == null) {
+ return;
+ }
/*
// Logging
0x15e4, // Numark
0x162e, // Joytech
0x1689, // Razer Onza
+ 0x1949, // Lab126, Inc.
0x1bad, // Harmonix
0x24c6, // PowerA
};
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);
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) {
////////// 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);
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);
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 = "";
import android.content.Context;
-import java.lang.reflect.*;
+import java.lang.Class;
+import java.lang.reflect.Method;
/**
SDL library initialization
// 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);
catch (final SecurityException se) {
throw se;
}
- }
+ }
}
protected static Context mContext;
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
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;
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 {
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;
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();
*/
protected String[] getLibraries() {
return new String[] {
- "hidapi",
"SDL2",
"SDL2_image",
"SDL2_mixer",
mCursors = new Hashtable<Integer, PointerIcon>();
mLastCursorID = 0;
mSDLThread = null;
- mBrokenLibraries = false;
mIsResumedCalled = false;
mHasFocus = true;
mNextNativeState = NativeState.INIT;
String errorMsgBrokenLib = "";
try {
loadLibraries();
+ mBrokenLibraries = false; /* success */
} catch(UnsatisfiedLinkError e) {
System.err.println(e.getMessage());
mBrokenLibraries = true;
mSingleton = this;
SDL.setContext(this);
- mClipboardHandler = new SDLClipboardHandler_API11();
+ mClipboardHandler = new SDLClipboardHandler();
mHIDDeviceManager = HIDDeviceManager.acquire(this);
// 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);
}
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;
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()");
// 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;
}
mSDLThread.start();
// No nativeResume(), don't signal Android_ResumeSem
- mSurface.handleResume();
} else {
nativeResume();
- mSurface.handleResume();
}
+ mSurface.handleResume();
mCurrentNativeState = mNextNativeState;
}
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;
}
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:
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);
}
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);
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();
+ }
}
}
}
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);
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.
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);
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);
}
}
- 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);
}
*/
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.
//
if (mSingleton == null) {
return false;
}
- return mSingleton.sendCommand(command, Integer.valueOf(param));
+ return mSingleton.sendCommand(command, param);
}
/**
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);
}
/**
}
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) {
*/
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) {
/* environment variables set! */
return true;
} catch (Exception e) {
- Log.v("SDL", "exception " + e.toString());
+ Log.v(TAG, "exception " + e.toString());
}
return false;
}
// 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 {
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
/**
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.
runOnUiThread(new Runnable() {
@Override
public void run() {
- showDialog(dialogs++, args);
+ messageboxCreateAndShow(args);
}
});
return messageboxSelection[0];
}
- @Override
- protected Dialog onCreateDialog(int ignore, Bundle args) {
+ protected void messageboxCreateAndShow(Bundle args) {
// TODO set values from "flags" to messagebox dialog
// 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() {
// 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) {
}
});
- 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);
+ }
}
};
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.
*/
@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;
}
}
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
+
}
}
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;
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.
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,
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;
}
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
}
}
- 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;
}
@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
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
// 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()) {
y = -event.values[1];
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
+ case Surface.ROTATION_0:
default:
x = event.values[0];
y = event.values[1];
// 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;
}
}
}
-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);
}
public void onPrimaryClipChanged() {
SDLActivity.onNativeClipboardChanged();
}
-
}
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;
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) {
results[0] = mAudioRecord.getSampleRate();
results[1] = mAudioRecord.getAudioFormat();
results[2] = mAudioRecord.getChannelCount();
- results[3] = desiredFrames;
} else {
if (mAudioTrack == null) {
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");
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
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.");
}
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) ||
}
}
- private ArrayList<SDLJoystick> mJoysticks;
+ private final ArrayList<SDLJoystick> mJoysticks;
public SDLJoystickHandler_API16() {
@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<InputDevice.MotionRange>();
List<InputDevice.MotionRange> 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<Integer> removedDevices = new ArrayList<Integer>();
- 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<Integer> 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<Integer>();
+ }
+ 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;
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;
class SDLHapticHandler {
- class SDLHaptic {
+ static class SDLHaptic {
public int device_id;
public String name;
public Vibrator vib;
}
- private ArrayList<SDLHaptic> mHaptics;
+ private final ArrayList<SDLHaptic> mHaptics;
public SDLHapticHandler() {
mHaptics = new ArrayList<SDLHaptic>();
}
/* Check removed devices */
- ArrayList<Integer> removedDevices = new ArrayList<Integer>();
- 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<Integer> 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<Integer>();
+ }
+ 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;
public float getEventX(MotionEvent event) {
if (mRelativeModeEnabled) {
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
- }
- else {
+ } else {
return event.getX(0);
}
}
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;
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;
}
}
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
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"
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
allprojects {
repositories {
- jcenter()
+ mavenCentral()
google()
}
}
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
<head>
<meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Loading Rocks'n'Diamonds</title>
+<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32">
+<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16">
+<style>
+body {
+ background: black;
+ text-align: center;
+ vertical-align: middle;
+}
+#loading {
+ color: white;
+ font-size: 120%;
+ font-family: sans-serif;
+}
+#canvas {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ margin: 0px;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ display: block;
+}
+</style>
</head>
-<body style="background:black;text-align:center;vertical-align:middle;">
+<body>
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
+<div id="loading">
+<img src="loading.svg" width="200px" height="200px">
+<br>
+Loading Rocks'n'Diamonds ...
+</div>
<script type='text/javascript'>
var Module = {
arguments: [],
preRun: [
function() {}
],
- postRun: [],
+ postRun: [
+ function() { loading.style.display = 'none'; }
+ ],
print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
alert("An error occurred, see console.");
document.title = "Rocks'n'Diamonds (aborted)";
};
- </script>
+</script>
+<script async type="text/javascript" src="rocksndiamonds.data.js"></script>
<script async type="text/javascript" src="rocksndiamonds.js"></script>
</body>
</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto; animation-play-state: running; animation-delay: 0s;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
+<g transform="rotate(0 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(30 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(60 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(90 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(120 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(150 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(180 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(210 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(240 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(270 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(300 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g><g transform="rotate(330 50 50)" style="animation-play-state: running; animation-delay: 0s;">
+ <rect x="48.5" y="24.5" rx="1.5" ry="1.98" width="3" height="11" fill="#ffffff" style="animation-play-state: running; animation-delay: 0s;">
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite" style="animation-play-state: running; animation-delay: 0s;"></animate>
+ </rect>
+</g>
+<!-- [ldio] generated by https://loading.io/ --></svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>rocksndiamonds</string>
+ <key>CFBundleIconFile</key>
+ <string>rocksndiamonds.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.artsoft.rocksndiamonds</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>Rocks'n'Diamonds</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>__VERSION__</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright (c) 1995-__YEAR__ by Artsoft Entertainment</string>
+</dict>
+</plist>
--- /dev/null
+APPL????
\ No newline at end of file
--- /dev/null
+[InternetShortcut]
+URL=https://www.artsoft.org/rocksndiamonds/
--- /dev/null
+; =============================================================================\r
+; template.iss\r
+; -----------------------------------------------------------------------------\r
+; configuration template for Inno Setup installation project\r
+;\r
+; 2020-06-30 info@artsoft.org\r
+; =============================================================================\r
+\r
+[Setup]\r
+AppName=_PRG_NAME_\r
+AppVerName=_PRG_NAME_ _PRG_VERSION_\r
+AppPublisher=Artsoft Entertainment\r
+AppPublisherURL=https://www.artsoft.org/\r
+AppSupportURL=https://www.artsoft.org/_PRG_BASENAME_/\r
+AppUpdatesURL=https://www.artsoft.org/_PRG_BASENAME_/\r
+\r
+ArchitecturesInstallIn64BitMode=_PRG_ARCH_\r
+ArchitecturesAllowed=_PRG_ARCH_\r
+\r
+DefaultDirName={pf}\_PRG_NAME_\r
+DefaultGroupName=_PRG_NAME_\r
+;LicenseFile="_PRG_DIR_\COPYING.txt"\r
+;InfoBeforeFile="_PRG_DIR_\INSTALL.txt"\r
+;InfoAfterFile="_PRG_DIR_\README.txt"\r
+UninstallDisplayIcon={app}\_PRG_EXE_\r
+Compression=lzma\r
+SolidCompression=yes\r
+\r
+OutputBaseFilename=_SETUP_EXE_\r
+OutputDir=.\r
+\r
+[Files]\r
+Source: "_PRG_DIR_\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs ignoreversion\r
+\r
+[Tasks]\r
+Name: "desktopicon"; Description: "Create a &Desktop icon"; GroupDescription: "Additional icons:"\r
+Name: "quicklaunchicon"; Description: "Create a &Quick Launch icon"; GroupDescription: "Additional icons:"\r
+\r
+[Icons]\r
+Name: "{group}\_PRG_NAME_"; Filename: "{app}\_PRG_EXE_"\r
+Name: "{group}\_PRG_NAME_ on the Web"; Filename: "{app}\_PRG_BASENAME_.url"\r
+Name: "{userdesktop}\_PRG_NAME_"; Filename: "{app}\_PRG_EXE_"; Tasks: desktopicon\r
+Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\_PRG_NAME_"; Filename: "{app}\_PRG_EXE_"; Tasks: quicklaunchicon\r
+\r
+; This dynamically generates a Windows internet shortcut file. Unfortunately,\r
+; this file is not removed when the package is uninstalled, leaving an empty\r
+; program directory with just that internet shortcut file. Using a static file\r
+; does not cause this problem.\r
+;[INI]\r
+;Filename: "{app}\_PRG_BASENAME_.url"; Section: "InternetShortcut"; Key: "URL"; String: "https://www.artsoft.org/_PRG_BASENAME_/"\r
+\r
+[Run]\r
+Filename: "{app}\_PRG_EXE_"; Description: "Launch _PRG_NAME_"; Flags: nowait postinstall skipifsilent\r
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';
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';
my $num_custom_elements = 256;
my $num_group_elements = 32;
+my $num_empty_elements = 16;
my $char_skip = '---[SKIP]---';
}
}
+ 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;
$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";
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";
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 =
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 = ();
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";
$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)
{
$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/;
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";
{
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();
--- /dev/null
+# .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
--- /dev/null
+# .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
--- /dev/null
+# .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
--- /dev/null
+# .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
--- /dev/null
+# .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
--- /dev/null
+# .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"
--- /dev/null
+# .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
--- /dev/null
+# .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
--- /dev/null
+# .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
-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.
-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.
--- /dev/null
+This fixed mirror reflects the laser beam into one direction only.
--- /dev/null
+Steel slopes reflect the laser beam similar to steel walls.
-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.
--- /dev/null
+Envelopes can be configured to contain a text message for the player.
+All envelopes of the same color contain the same message.
-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.
-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.
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).
-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.
--- /dev/null
+# .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!
+
+:-)
--- /dev/null
+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
--- /dev/null
+name: ncrtorial\r
+\r
+titlescreen_1: ncrtorial_title_screen.png\r
+titlescreen_1.scale_up_factor: 2\r
+background.PANEL:RocksTooMuchPanel.png\r
+background.PANEL.x:0\r
+background.PANEL.y:0\r
+background.PANEL.width:100\r
+background.PANEL.height:280\r
+game.panel.level_number.x:50\r
+game.panel.level_number.y:4\r
+game.panel.gems.y:31\r
+game.panel.inventory_count.y:58\r
+game.panel.inventory_last_1.x:5\r
+game.panel.inventory_last_1.y:57\r
+game.panel.inventory_last_1.draw_order:-2\r
+game.panel.inventory_last_1.tile_size:16\r
+game.panel.inventory_last_2.x:79\r
+game.panel.inventory_last_2.y:57\r
+game.panel.inventory_last_2.draw_order:-2\r
+game.panel.inventory_last_2.tile_size:16\r
+game.panel.graphic_1.x:3\r
+game.panel.graphic_1.y:64\r
+game.panel.graphic_1.draw_order:-1\r
+game.panel.graphic_1.draw_masked:true\r
+game.panel.graphic_2.x:77\r
+game.panel.graphic_2.y:64\r
+game.panel.graphic_2.draw_order:-1\r
+game.panel.graphic_2.draw_masked:true\r
+game.panel.element_1_count.x:25\r
+game.panel.element_1_count.y:85\r
+game.panel.element_1_count.align:left\r
+game.panel.element_1_count.digits:2\r
+game.panel.element_1_count.draw_order:-1\r
+game.panel.element_1_count.font:font.level_number\r
+game.panel.element_1_count.element:dynabomb_player_1.active\r
+game.panel.dynabomb_number.x:55\r
+game.panel.dynabomb_number.y:85\r
+game.panel.dynabomb_number.align:left\r
+game.panel.dynabomb_number.digits:2\r
+game.panel.dynabomb_number.draw_order:-1\r
+game.panel.dynabomb_number.font:font.level_number\r
+game.panel.dynabomb_size.x:50\r
+game.panel.dynabomb_size.y:103\r
+game.panel.dynabomb_size.align:center\r
+game.panel.dynabomb_size.draw_order:-1\r
+game.panel.dynabomb_size.digits:3\r
+game.panel.dynabomb_power.x:5\r
+game.panel.dynabomb_power.y:84\r
+game.panel.dynabomb_power.draw_order:-1\r
+game.panel.dynabomb_power.tile_size:16\r
+game.panel.time_anim.x:3\r
+game.panel.time_anim.y:46\r
+game.panel.health_anim.x:3\r
+game.panel.health_anim.y:82\r
+game.panel.key_1.x:3\r
+game.panel.key_1.y:129\r
+game.panel.key_1.draw_masked:true\r
+game.panel.key_2.x:18\r
+game.panel.key_2.y:129\r
+game.panel.key_2.draw_masked:true\r
+game.panel.key_3.x:33\r
+game.panel.key_3.y:129\r
+game.panel.key_3.draw_masked:true\r
+game.panel.key_4.x:48\r
+game.panel.key_4.y:129\r
+game.panel.key_4.draw_masked:true\r
+game.panel.key_5.x:64\r
+game.panel.key_5.y:129\r
+game.panel.key_5.draw_masked:true\r
+game.panel.key_6.x:80\r
+game.panel.key_6.y:129\r
+game.panel.key_6.draw_masked:true\r
+game.panel.key_7.x:4\r
+game.panel.key_7.y:142\r
+game.panel.key_7.draw_masked:true\r
+game.panel.key_8.x:20\r
+game.panel.key_8.y:142\r
+game.panel.key_8.draw_masked:true\r
+game.panel.key_white.x:80\r
+game.panel.key_white.y:142\r
+game.panel.key_white.draw_masked:true\r
+game.panel.key_white_count.x:79\r
+game.panel.key_white_count.y:143\r
+game.panel.key_white_count.align:right\r
+game.panel.key_white_count.digits:-1\r
+game.panel.key_white_count.font:font.level_number\r
+game.panel.score.y:170\r
+game.panel.time.y:197\r
+dynabomb_increase_power.PANEL:RocksTooMuchPanel.png\r
+dynabomb_increase_power.PANEL.xoffset:6\r
+dynabomb_increase_power.PANEL.yoffset:216\r
+dynabomb_increase_power.PANEL.frames:2\r
+dynabomb_increase_power.PANEL.anim_mode:linear\r
+dynabomb_increase_power.PANEL.start_frame:1\r
+#dynabomb_player_1.PANEL:RocksTooMuchPanel.png\r
+#dynabomb_player_1.PANEL.xoffset:38\r
+#dynabomb_player_1.PANEL.yoffset:216\r
+#dynabomb_player_1.PANEL.frames:2\r
+#dynabomb_player_1.PANEL.anim_mode:linear\r
+#dynabomb_player_1.PANEL.start_frame:1\r
+emc_key_5.PANEL:RocksTooMuchPanel.png\r
+emc_key_5.PANEL.xoffset:38\r
+emc_key_5.PANEL.yoffset:216\r
+emc_key_5.PANEL.frames:2\r
+emc_key_5.PANEL.anim_mode:linear\r
+emc_key_5.PANEL.start_frame:1\r
+emc_key_6.PANEL:RocksTooMuchPanel.png\r
+emc_key_6.PANEL.xoffset:38\r
+emc_key_6.PANEL.yoffset:242\r
+emc_key_6.PANEL.frames:2\r
+emc_key_6.PANEL.anim_mode:linear\r
+emc_key_6.PANEL.start_frame:1\r
+emc_key_7.PANEL:RocksTooMuchPanel.png\r
+emc_key_7.PANEL.xpos:0\r
+emc_key_7.PANEL.ypos:9\r
+emc_key_7.PANEL.frames:1\r
+emc_key_8.PANEL:RocksTooMuchPanel.png\r
+emc_key_8.PANEL.xpos:1\r
+emc_key_8.PANEL.ypos:9\r
+emc_key_8.PANEL.frames:1\r
+dc_key_white.PANEL:RocksTooMuchPanel.png\r
+dc_key_white.PANEL.xpos:2\r
+dc_key_white.PANEL.ypos:9\r
+dc_key_white.PANEL.frames:1\r
+graphic_1:RocksTooMuchPanel.png\r
+graphic_1.x:5\r
+graphic_1.y:265\r
+graphic_1.width:8\r
+graphic_1.height:10\r
+graphic_1.frames:1\r
+graphic_2:RocksTooMuchPanel.png\r
+graphic_2.x:13\r
+graphic_2.y:265\r
+graphic_2.width:8\r
+graphic_2.height:10\r
+graphic_2.frames:1\r
+gfx.game.panel.time_anim:RocksTooMuchBars.png\r
+gfx.game.panel.time_anim.x:0\r
+gfx.game.panel.time_anim.y:0\r
+gfx.game.panel.time_anim.width:94\r
+gfx.game.panel.time_anim.height:36\r
+gfx.game.panel.time_anim.frames:1\r
+gfx.game.panel.time_anim.active:RocksTooMuchBars.png\r
+gfx.game.panel.time_anim.active.x:94\r
+gfx.game.panel.time_anim.active.y:0\r
+gfx.game.panel.time_anim.active.width:94\r
+gfx.game.panel.time_anim.active.height:36\r
+gfx.game.panel.time_anim.active.frames:1\r
+gfx.game.panel.health_anim:RocksTooMuchBars.png\r
+gfx.game.panel.health_anim.x:0\r
+gfx.game.panel.health_anim.y:36\r
+gfx.game.panel.health_anim.width:94\r
+gfx.game.panel.health_anim.height:38\r
+gfx.game.panel.health_anim.frames:1\r
+gfx.game.panel.health_anim.active:RocksTooMuchBars.png\r
+gfx.game.panel.health_anim.active.x:94\r
+gfx.game.panel.health_anim.active.y:36\r
+gfx.game.panel.health_anim.active.width:94\r
+gfx.game.panel.health_anim.active.height:38\r
+gfx.game.panel.health_anim.active.frames:1
\ No newline at end of file
--- /dev/null
+# =============================================================================
+# levelinfo.conf
+# =============================================================================
+
+name: Ncrecc's Tutorial - ncrtorial
+author: ncrecc
+
+levels: 8
+first_level: 0
+
+sort_priority: 20
+# .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 <flummi@semisane.de>
+# .font: font.info.levelset
+Niko Böhm <flummi@semisane.de>
--- /dev/null
+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 <flummi@semisane.de>
--- /dev/null
+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 <flummi@semisane.de>
libgame/image.c \
libgame/random.c \
libgame/hash.c \
+ libgame/http.c \
+ libgame/base64.c \
libgame/setup.c \
libgame/misc.c \
libgame/sdl.c \
files.c \
tape.c \
anim.c \
+ api.c \
network.c \
netserv.c
PROGBASE = rocksndiamonds
PROGNAME = ../$(PROGBASE)
-EDITION ?= default
-
# -----------------------------------------------------------------------------
# configuring platform
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)
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
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
# 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 \
files.c \
tape.c \
anim.c \
+ api.c \
network.c \
netserv.c
files.o \
tape.o \
anim.o \
+ api.o \
network.o \
netserv.o
conf_cus.h \
conf_grp.c \
conf_grp.h \
+ conf_emp.c \
+ conf_emp.h \
conf_e2g.c \
conf_esg.c \
conf_e2s.c \
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
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)
@echo '#define SOURCE_HASH_STRING "$(SOURCE_HASH_STRING)"' \
> $(COMMIT_HASH_FILE)
+config.o: config.c $(TIMESTAMP_FILE)
+
$(TIMESTAMP_FILE): $(SRCS) $(RNDLIBS)
@$(MAKE) conf-time
@$(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
#include "files.h"
#include "events.h"
#include "screens.h"
+#include "tape.h"
#define DEBUG_ANIM_DELAY 0
#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)
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 | \
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)
struct GraphicInfo graphic_info;
struct GraphicInfo control_info;
+ boolean class_playfield_or_door;
+
int viewport_x;
int viewport_y;
int viewport_width;
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;
{ 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 },
{ 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 },
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;
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;
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)
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;
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;
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;
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;
}
}
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;
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 &&
}
}
+ // 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)
ResetGlobalAnim_Clicked();
}
- DrawEnvelopeRequestToScreen(drawing_target, drawing_stage);
-
if (gfx.cursor_mode_override != last_cursor_mode_override)
SetMouseCursor(gfx.cursor_mode);
}
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"))
{
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)
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"))
{
viewport_width = VXSIZE;
viewport_height = VYSIZE;
}
+
+ part->class_playfield_or_door = TRUE;
}
else // default: "playfield"
{
viewport_y = REAL_SY;
viewport_width = FULL_SXSIZE;
viewport_height = FULL_SYSIZE;
+
+ part->class_playfield_or_door = TRUE;
}
if (viewport_x != part->viewport_x ||
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
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);
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);
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,
{
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))
}
}
+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)
{
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;
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;
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;
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)
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
}
#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;
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)
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)
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;
}
}
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");
}
}
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;
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);
+}
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
--- /dev/null
+// ============================================================================
+// 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");
+}
--- /dev/null
+// ============================================================================
+// 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
{ ".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 },
{ ".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 }
};
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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 },
#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
// ==========================================================================
{ "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 },
{ "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 },
{ "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" },
{ "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 },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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 },
{ "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 },
{ "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" },
{ "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" },
{ "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 },
{ "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 },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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" },
{ "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 },
{ "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" },
// 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" },
{ "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" },
{ "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 },
return program.version_string;
}
+char *getProgramPlatformString(void)
+{
+ return PLATFORM_STRING;
+}
+
char *getProgramInitString(void)
{
static char *program_init_string = NULL;
char *getProgramTitleString(void);
char *getProgramRealVersionString(void);
char *getProgramVersionString(void);
+char *getProgramPlatformString(void);
char *getProgramInitString(void);
char *getConfigProgramTitleString(void);
char *getConfigProgramCopyrightString(void);
GADGET_ID_PICK_ELEMENT,
GADGET_ID_UNDO,
- GADGET_ID_INFO,
+ GADGET_ID_CONF,
GADGET_ID_SAVE,
GADGET_ID_CLEAR,
GADGET_ID_TEST,
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,
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,
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,
// 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,
// 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,
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,
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,
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,
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,
// 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,
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
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,
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,
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,
};
#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
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,
// 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
"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,
"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,
&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) ---------------
int gadget_id;
int xsize, ysize;
char *value;
- char *text_above, *infotext;
+ char *text_above, *text_above_cropped, *infotext;
} textarea_info[ED_NUM_TEXTAREAS] =
{
{
GADGET_ID_ENVELOPE_INFO,
MAX_ENVELOPE_XSIZE, MAX_ENVELOPE_YSIZE,
NULL,
- "Envelope Content:", "Envelope Content"
+ "Envelope Content:", "Envelope Content (cropped):", "Envelope Content"
}
};
{ 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 }
};
{ 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 }
};
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 }
{ ANIM_LINEAR, "linear" },
{ ANIM_PINGPONG, "pingpong" },
{ ANIM_PINGPONG2, "pingpong 2" },
+ { ANIM_LEVEL_NR, "level number" },
{ -1, NULL }
};
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,
&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) ---------------
{
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) ---------------------------------------
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,
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,
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) ---------------
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 ------------------------------------------------
-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"
},
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;
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);
static int getTabulatorBarWidth(void);
static int getTabulatorBarHeight(void);
static Pixel getTabulatorBarColor(void);
+static int numHiresTiles(int);
static int num_editor_gadgets = 0; // dynamically determined
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;
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;
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,
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;
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,
&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,
}
};
+static struct XY xy_directions[] =
+{
+ { -1, 0 },
+ { +1, 0 },
+ { 0, -1 },
+ { 0, +1 }
+};
+
// ----------------------------------------------------------------------------
// functions
static char *getElementDescriptionFilenameExt(char *basename)
{
- char *elements_subdir = "elements";
+ char *elements_subdir = ELEMENTS_DIRECTORY;
static char *elements_subdir2 = NULL;
static char *filename = NULL;
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;
}
// 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;
*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
// 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
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 ?
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]);
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;
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;
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';
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';
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
{
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] +
{
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);
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);
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';
static void CreateGraphicbuttonGadgets(void)
{
struct GadgetInfo *gi;
- unsigned int event_mask;
int i;
// create buttons for scrolling of drawing area and element list
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)
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)
{
}
}
- event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
-
gi = CreateGadget(GDI_CUSTOM_ID, id,
GDI_CUSTOM_TYPE_ID, i,
GDI_IMAGE_ID, graphic,
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;
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)
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);
use_permanent_palette = !editor.palette.show_as_separate_screen;
+ InitGadgetScreenBorders(-1, INFOTEXT_YPOS);
+
ReinitializeElementList();
CreateControlButtons();
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);
// 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;
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);
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)
return TRUE;
}
-static void ModifyLevelInfoForSavingIntoPersonalLevelSet(char *former_name)
+static void ModifyLevelConfigForSavingIntoPersonalLevelSet(char *former_name)
{
static char *filename_levelinfo = NULL, *mod_name = NULL;
FILE *file;
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;
// 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 :
// 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 :
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 :
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);
CopyCustomElementPropertiesToEditor(element);
else if (IS_GROUP_ELEMENT(element))
CopyGroupElementPropertiesToEditor(element);
+ else if (IS_EMPTY_ELEMENT(element))
+ CopyEmptyElementPropertiesToEditor(element);
else
CopyClassicElementPropertiesToEditor(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;
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;
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;
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]);
CopyCustomElementPropertiesToGame(element);
else if (IS_GROUP_ELEMENT(element))
CopyGroupElementPropertiesToGame(element);
+ else if (IS_EMPTY_ELEMENT(element))
+ CopyEmptyElementPropertiesToGame(element);
else
CopyClassicElementPropertiesToGame(element);
}
// 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();
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();
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
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;
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);
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
// 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;
TRUE, FALSE, FALSE);
}
-static void DrawLevelInfoLevel(void)
+static void DrawLevelConfigLevel(void)
{
int i;
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);
PrintInfoText(directory_name, font2_nr, x, y);
}
-static void DrawLevelInfoLevelSet(void)
+static void DrawLevelConfigLevelSet(void)
{
boolean artwork_exists = checkIfCustomArtworkExistsForCurrentLevelSet();
boolean template_exists = fileExists(getLocalLevelTemplateFilename());
MapTextbuttonGadget(ED_TEXTBUTTON_ID_SAVE_LEVELSET);
// draw info text
- DrawLevelInfoLevelSet_DirectoryInfo();
+ DrawLevelConfigLevelSet_DirectoryInfo();
}
-static void DrawLevelInfoEditor(void)
+static void DrawLevelConfigEditor(void)
{
int i;
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;
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)
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;
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;
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;
{ -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;
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;
// ----- 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
#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"
{ 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 },
{ 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 },
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) ||
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);
// 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);
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))
{
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;
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);
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)
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);
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;
UnmapLevelEditorToolboxDrawingGadgets();
UnmapLevelEditorToolboxCustomGadgets();
- if (IS_CUSTOM_ELEMENT(properties_element) ||
- IS_GROUP_ELEMENT(properties_element))
- MapLevelEditorToolboxCustomGadgets();
+ MapLevelEditorToolboxCustomGadgetsIfNeeded();
SetMainBackgroundImage(IMG_BACKGROUND_EDITOR);
ClearField();
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;
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);
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);
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);
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;
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);
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;
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);
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;
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);
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;
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);
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;
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]))
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]))
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]))
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]))
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]))
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]))
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))
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,
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,
}
}
- 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;
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;
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 =
{
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);
}
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
#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
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);
{
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))
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)
{
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);
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
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);
{
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);
+ }
}
}
}
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;
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);
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;
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 &&
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);
if (edit_mode == ED_MODE_DRAWING && draw_with_brush &&
!inside_drawing_area)
DeleteBrushFromCursor();
- }
- if (!button || button_release_event)
break;
+ }
if (draw_with_brush)
{
{
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++)
{
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 &&
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);
+ }
}
}
}
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;
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;
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);
if (type_id == ED_SELECTBOX_ID_LEVELSET_SAVE_MODE)
{
- DrawLevelInfoWindow();
+ DrawLevelConfigWindow();
}
else if (type_id == ED_SELECTBOX_ID_SELECT_CHANGE_PAGE)
{
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)
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;
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
{
Request("Save this level and kill the old?", REQ_ASK))
{
if (leveldir_former->readonly)
- ModifyLevelInfoForSavingIntoPersonalLevelSet(leveldir_former->name);
+ ModifyLevelConfigForSavingIntoPersonalLevelSet(leveldir_former->name);
SetAutomaticNumberOfGemsNeeded();
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))
{
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)
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)
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++)
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)
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)
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)
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;
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
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;
vp_door_2->height == VYSIZE)
CloseDoor(DOOR_CLOSE_ALL | DOOR_NO_DELAY);
else
- SetDoorState(DOOR_CLOSE_2);
+ SetDoorState(DOOR_CLOSE_ALL);
BackToFront();
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);
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);
// ============================================================================
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
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
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
{
SetMouseCursor(CURSOR_DEFAULT);
- DelayReached(&special_cursor_delay, 0);
+ ResetDelayCounter(&special_cursor_delay);
cursor_mode_last = CURSOR_DEFAULT;
}
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);
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
// 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);
}
// 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)
// 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;
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
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",
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));
}
#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))
// 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);
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)
{
// 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)));
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)
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:
{
key_action |= key_info[i].action | JOY_BUTTON_SNAP;
key_snap_action |= key_info[i].action;
+
+ tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
}
}
}
// 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
// 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;
}
}
// 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))
{
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);
HandleSoundButtonKeys(key);
}
+ if (game_status == GAME_MODE_SCOREINFO)
+ {
+ HandleScreenGadgetKeys(key);
+ }
+
if (game_status == GAME_MODE_PLAYING && !network_playing)
{
int centered_player_nr_next = -999;
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:
case GAME_MODE_SETUP:
case GAME_MODE_INFO:
case GAME_MODE_SCORES:
+ case GAME_MODE_SCOREINFO:
if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
break;
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:
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:
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:
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:
{
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);
}
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;
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;
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;
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);
}
case GAME_MODE_SETUP:
case GAME_MODE_INFO:
case GAME_MODE_SCORES:
+ case GAME_MODE_SCOREINFO:
{
if (anyTextGadgetActive())
break;
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;
}
{
Key key = (Key)(-keysym);
+ is_global_anim_event = TRUE;
+
HandleKey(key, KEY_PRESSED);
HandleKey(key, KEY_RELEASED);
+ is_global_anim_event = FALSE;
+
return TRUE;
}
#define USEREVENT_GADGET_PRESSED 3
+void SetPlayfieldMouseCursorEnabled(boolean);
+
int FilterMouseMotionEvents(void *, Event *);
boolean NextValidEvent(Event *);
void StopProcessingEvents(void);
#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
// (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)
#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)
// 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
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 + \
&li.time_score_base, 1
},
+ {
+ -1, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(13),
+ &li.rate_time_over_score, FALSE
+ },
+
{
-1, -1,
-1, -1,
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)
{
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),
}
};
+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)
{
{
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)
setElementChangeInfoToDefaults(ei->change);
if (IS_CUSTOM_ELEMENT(element) ||
- IS_GROUP_ELEMENT(element) ||
- IS_INTERNAL_ELEMENT(element))
+ IS_GROUP_ELEMENT(element))
{
setElementDescriptionToDefault(ei);
*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;
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));
// 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));
// ... 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;
}
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;
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
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
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)
{ "NOTE", -1, LoadLevel_NOTE },
{ "CUSX", -1, LoadLevel_CUSX },
{ "GRPX", -1, LoadLevel_GRPX },
+ { "EMPX", -1, LoadLevel_EMPX },
{ NULL, 0, NULL }
};
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
{
Warn("wrong size (%d) of chunk '%s' in level file '%s'",
chunk_size, chunk_name, filename);
+
+ break;
}
}
}
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);
// 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]);
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++)
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);
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);
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] =
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);
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);
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]);
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;
}
}
- LoadLevelFromFileStream_DC(file, level, level_file_info->nr);
+ LoadLevelFromFileStream_DC(file, level);
closeFile(file);
}
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)
}
// 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;
}
}
// 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)
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)
LoadLevel_InitVersion(&level_template);
LoadLevel_InitElements(&level_template);
+ LoadLevel_InitSettings(&level_template);
ActivateLevelTemplate();
}
LoadLevel_InitVersion(&level);
LoadLevel_InitElements(&level);
LoadLevel_InitPlayfield(&level);
+ LoadLevel_InitSettings(&level);
LoadLevel_InitNativeEngines(&level);
}
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);
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);
}
}
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)
{
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);
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
tape.level_nr = level_nr;
tape.counter = 0;
tape.changed = FALSE;
+ tape.solved = FALSE;
tape.recording = FALSE;
tape.playing = FALSE;
tape.scr_fieldx = SCR_FIELDX_DEFAULT;
tape.scr_fieldy = SCR_FIELDY_DEFAULT;
+ tape.no_info_chunk = TRUE;
tape.no_valid_file = FALSE;
}
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)
int level_identifier_size;
int i;
+ tape->no_info_chunk = FALSE;
+
level_identifier_size = getFile16BitBE(file);
level_identifier = checked_malloc(level_identifier_size);
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
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);
}
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;
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)
{
}
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;
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;
}
TYPE_SWITCH,
&setup.toons, "toons"
},
+ {
+ TYPE_SWITCH,
+ &setup.global_animations, "global_animations"
+ },
{
TYPE_SWITCH,
&setup.scroll_delay, "scroll_delay"
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"
},
{
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,
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[] =
},
};
+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[] =
{
{
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"
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"
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"
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[] =
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)
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;
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;
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);
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;
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;
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;
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);
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;
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;
token_info[token_nr].text);
}
-static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
+static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash)
{
int i, pnr;
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;
if (setup_file_hash)
{
- decodeSetupFileHash(setup_file_hash);
+ decodeSetupFileHash_Default(setup_file_hash);
freeSetupFileHash(setup_file_hash);
}
MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
}
-void LoadSetup(void)
+void LoadSetup_Default(void)
{
char *filename;
// 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);
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);
free(filename);
}
+void LoadSetup(void)
+{
+ LoadSetup_Default();
+ LoadSetup_AutoSetup();
+ LoadSetup_ServerSetup();
+ LoadSetup_EditorCascade();
+}
+
static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
char *mapping_line)
{
fclose(file);
}
-void SaveSetup(void)
+void SaveSetup_Default(void)
{
char *filename = getSetupFilename();
FILE *file;
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;
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);
free(filename);
}
+void SaveSetup(void)
+{
+ SaveSetup_Default();
+ SaveSetup_AutoSetup();
+ SaveSetup_ServerSetup();
+ SaveSetup_EditorCascade();
+}
+
static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
char *filename)
{
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
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[] =
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]);
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;
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"))
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 :
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"))
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 :
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 =
}
}
+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;
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
}
}
+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;
{
{ "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++)
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] },
}
}
- // 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);
}
InitMenuDesignSettings_Static();
InitMenuDesignSettings_SpecialPreProcessing();
+ InitMenuDesignSettings_PreviewPlayers();
if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS))
{
{ "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 },
};
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)
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);
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);
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,
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);
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);
}
+// ----------------------------------------------------------------------------
+// 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)
// ----------------------------------------------------------------------------
void SaveLevelTemplate(void);
void SaveNativeLevel(struct LevelInfo *);
void DumpLevel(struct LevelInfo *);
+void DumpLevels(void);
boolean SaveLevelChecked(int);
void CopyNativeLevel_RND_to_Native(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 *);
boolean hideSetupEntry(void *);
void LoadCustomElementDescriptions(void);
+void InitMenuDesignSettings_FromHash(SetupFileHash *, boolean);
void InitMenuDesignSettings_Static(void);
void LoadMenuDesignSettings(void);
void LoadMenuDesignSettings_AfterGraphics(void);
void ConvertLevels(void);
void CreateLevelSketchImages(void);
+void CreateCollectElementImages(void);
void CreateCustomElementImages(char *);
void FreeGlobalAnimEventInfo(void);
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]))
#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
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);
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) \
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);
{ 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])
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];
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;
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;
}
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 ?
}
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;
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)
{
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)
{
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;
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));
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++)
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;
}
}
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))
{
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++)
{
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;
}
}
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;
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();
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();
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;
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;
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;
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;
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;
{
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;
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];
}
game.emulation = (emulate_bd ? EMU_BOULDERDASH :
- emulate_sb ? EMU_SOKOBAN :
emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
// initialize type of slippery elements
{
// 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)))
{
// 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)))
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)))
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)
{
}
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();
if (setup.sound_music)
PlayLevelMusic();
}
+
+ SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
}
void UpdateEngineValues(int actual_scroll_x, int actual_scroll_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;
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;
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;
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)
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);
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);
{
// 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);
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)
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);
}
}
}
-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)
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)
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;
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];
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);
*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();
{
// 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)
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);
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;
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
// !!! 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];
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;
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;
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)
}
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);
}
}
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))
{
{
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]))
}
}
-static void ToggleSwitchgateSwitch(int x, int y)
+static void ToggleSwitchgateSwitch(void)
{
int xx, yy;
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)
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 ||
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;
}
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
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))
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;
if (IN_SCR_FIELD(sx, sy))
{
TEST_DrawLevelFieldCrumbled(xx, yy);
- DrawGraphic(sx, sy, flame_graphic, frame);
+ DrawScreenGraphic(sx, sy, flame_graphic, frame);
}
}
else
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 &&
}
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;
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]);
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
{
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);
}
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);
}
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;
{
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;
}
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;
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;
}
}
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])
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
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])
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)
{
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;
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;
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;
}
}
-static void MauerWaechst(int x, int y)
+static void WallGrowing(int x, int y)
{
int delay = 6;
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])
}
}
-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);
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);
{
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))
{
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;
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))
{
DisplayGameControlValues();
- if (!TimeLeft && setup.time_limit)
+ if (!TimeLeft && game.time_limit)
for (i = 0; i < MAX_PLAYERS; i++)
KillPlayer(&stored_player[i]);
}
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));
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
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
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);
(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;
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);
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);
}
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)
{
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
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)
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)
}
}
+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;
{
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
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;
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)
}
}
+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)
{
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();
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
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
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++)
{
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)
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();
}
TEST_DrawLevelField(x, y);
TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
+
+ if (IS_ENVELOPE(element))
+ local_player->show_envelope = element;
}
}
#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)
{
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])
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);
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)
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);
}
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 ||
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;
}
!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;
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) &&
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)
}
}
- 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 ||
LevelSolved();
}
+ player->last_jx = jx;
+ player->last_jy = jy;
+
// this breaks one level: "machine", level 000
{
int move_direction = player->MovDir;
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);
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
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)
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)
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
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;
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
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;
}
}
-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
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;
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];
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];
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);
}
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,
{
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;
{
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,
{
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;
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;
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;
return;
PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
- PlayLevelSound(jx, jy, SND_GAME_LOSING);
RemovePlayer(player);
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);
}
return MP_NO_ACTION;
}
}
-
if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
old_element = Back[jx][jy];
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)
}
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 ||
}
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)
{
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
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)
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;
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)
{
// 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)
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);
}
}
-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)
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
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));
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,
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,
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,
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;
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;
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);
}
}
-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)
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[] =
};
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);
}
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();
DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
+ ModifyPauseButtons();
+
BackToFront();
}
if (!CheckEngineSnapshotList())
return;
+ int tape_property_bits = tape.property_bits;
+
LoadEngineSnapshot_Undo(steps);
+ tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
+
GameUndoRedoExt();
}
if (!CheckEngineSnapshotList())
return;
+ int tape_property_bits = tape.property_bits;
+
LoadEngineSnapshot_Redo(steps);
+ tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
+
GameUndoRedoExt();
}
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;
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)
#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
struct XY pause2;
struct XY load;
+ struct XY restart;
+
struct XY sound_music;
struct XY sound_loops;
struct XY sound_simple;
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
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;
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;
// 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;
int LevelSolved_CountingTime;
int LevelSolved_CountingScore;
int LevelSolved_CountingHealth;
+
+ boolean RestartGameRequested;
};
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;
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);
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);
void CreateGameButtons(void);
void FreeGameButtons(void);
+void MapLoadSaveButtons(void);
void MapUndoRedoButtons(void);
-void UnmapUndoRedoButtons(void);
void ModifyPauseButtons(void);
void MapGameButtons(void);
void UnmapGameButtons(void);
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)
{
// - 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;
boolean use_old_push_elements;
boolean use_old_push_into_acid;
boolean use_wrap_around;
+ boolean use_push_delay;
};
struct LevelInfo_EM
void InitGfxBuffers_EM(void);
void InitGameEngine_EM(void);
-void GameActions_EM(byte *, boolean);
+void GameActions_EM(byte[MAX_PLAYERS]);
unsigned int InitEngineRandom_EM(int);
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;
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
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)
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++)
{
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 ||
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])
if (dy)
break;
+ if (game_em.use_push_delay && RANDOM(32) < 16)
+ goto stone_push_anim;
+
switch (cave[x+dx][y])
{
case Xblank:
break;
}
+ stone_push_anim:
+
ply->anim = PLY_push_n + anim;
break;
if (dy)
break;
+ if (game_em.use_push_delay && RANDOM(32) < 22)
+ goto bomb_push_anim;
+
switch (cave[x+dx][y])
{
case Xblank:
break;
}
+ bomb_push_anim:
+
ply->anim = PLY_push_n + anim;
break;
if (dy)
break;
+ if (game_em.use_push_delay && RANDOM(32) < 19)
+ goto nut_push_anim;
+
switch (cave[x+dx][y])
{
case Xblank:
break;
}
+ nut_push_anim:
+
ply->anim = PLY_push_n + anim;
break;
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;
{
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)
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
#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
#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
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;
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;
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];
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];
};
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;
};
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);
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);
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);
#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
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';
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;
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);
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);
// 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
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;
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] =
{
{
" ",
" 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",
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;
}
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
{
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];
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;
}
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;
Tile[x][y] = next_element;
- DrawField_MM(x, y);
game_mm.cycle[i].steps -= step;
}
}
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)
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
(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;
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;
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);
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);
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)
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);
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;
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)
}
#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))
}
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);
}
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;
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
hit_mask, LX, LY, ELX, ELY);
#endif
+ last_element = element;
+
element = Tile[ELX][ELY];
laser.dest_element = element;
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)
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
#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;
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");
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);
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;
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)
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)
{
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));
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)
(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);
}
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;
}
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)
{
DeletePacMan(ELX, ELY);
}
+ else if (IS_ENVELOPE(element))
+ {
+ Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
+ }
RaiseScoreElement_MM(element);
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
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))
}
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;
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);
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);
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);
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 &&
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 ||
}
}
- if (!HitOnlyAnEdge(element, hit_mask))
+ if (!HitOnlyAnEdge(hit_mask))
{
laser.overloaded = TRUE;
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 &&
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))
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;
}
}
}
-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
{
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);
}
}
}
{
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))
{
{
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;
}
}
+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))
{
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;
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);
}
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];
// 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]) &&
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;
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;
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);
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
}
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;
}
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)
{
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
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)
{
int x, y;
- if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
+ if (!FrameReached(&rotate_delay))
return;
for (x = 0; x < lev_fieldx; x++)
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;
return FALSE;
}
-void DeletePacMan(int px, int py)
+static void DeletePacMan(int px, int py)
{
int i, j;
}
}
-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;
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();
CT = FrameCounter;
- if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
+ if (game_mm.num_pacman && FrameReached(&pacman_delay))
{
MovePacMen();
}
}
- 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;
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 &&
!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++;
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);
}
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;
}
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;
}
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;
}
{
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))
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;
}
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;
}
DrawField_MM(nx, ny);
- BackToFront();
+ BackToFront_MM();
if (!laser.fuse_off)
{
}
}
-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);
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];
{
int oldx, oldy;
- Blocked2Moving_MM(x, y, &oldx, &oldy);
+ Blocked2Moving(x, y, &oldx, &oldy);
return Tile[oldx][oldy];
}
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;
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;
}
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;
// Mirror Magic game engine snapshot handling functions
// ----------------------------------------------------------------------------
-void SaveEngineSnapshotValues_MM(ListNode **buffers)
+void SaveEngineSnapshotValues_MM(void)
{
int x, y;
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];
}
}
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];
}
}
#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
#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);
void InitElementProperties_MM(void)
{
- int i,j;
+ int i, j;
static int ep_grid[] =
{
};
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,
EP_BIT_INACTIVE,
EP_BIT_WALL,
EP_BIT_PACMAN,
+ EP_BIT_ENVELOPE,
};
static int *ep_array[] =
{
ep_inactive,
ep_wall,
ep_pacman,
+ ep_envelope,
};
static int *ep_num[] =
{
&ep_inactive_num,
&ep_wall_num,
&ep_pacman_num,
+ &ep_envelope_num,
};
static int num_properties = sizeof(ep_num) / sizeof(int *);
{
InitElementProperties_MM();
}
-
-void mm_close_all(void)
-{
-}
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;
#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)
#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)
(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 && \
(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
#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;
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;
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;
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
#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)
#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)
#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
#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
#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"
#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
#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
#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
#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)
PROGRAM_VERSION_MINOR, \
PROGRAM_VERSION_PATCH)
-// sound control
-
-#define ST(x) (((x) - 8) * 16)
-
#endif // MM_MAIN_H
// 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)
{
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);
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);
}
#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);
}
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!");
}
#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;
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;
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;
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);
#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,
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);
}
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];
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)
void DrawLevel_MM(void)
{
- int x,y;
+ int x, y;
ClearWindow();
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);
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);
}
}
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
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;
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;
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());
debug = TRUE;
active = FALSE;
- DelayReached(&check_delay, 0);
+ ResetDelayCounter(&check_delay);
setup.debug.xsn_mode = (debug_value > 0);
tile_cursor.xsn_debug = FALSE;
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;
}
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 ?
}
}
-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;
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 ||
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))
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))
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) ||
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;
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();
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);
}
void SetDrawtoField_MM(int);
-void BackToFront(void);
+void BackToFront_MM(void);
+
void FadeToFront(void);
void ClearWindow(void);
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);
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);
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);
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
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);
}
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 -----------------------------
extern int LeadOutCounter;
void subMainGameLoop_Init(void);
-void subMainGameLoop_Main(byte, boolean);
+void subMainGameLoop_Main(byte);
void subCalculateScreenScrollPos(void);
#endif // MAINGAMELOOP_H
void InitGfxBuffers_SP(void);
void InitGameEngine_SP(void);
-void GameActions_SP(byte *, boolean);
+void GameActions_SP(byte[MAX_PLAYERS]);
unsigned int InitEngineRandom_SP(int);
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;
UpdateEngineValues(mScrollX / TILEX, mScrollY / TILEY,
MurphyScreenXPos / TILEX, MurphyScreenYPos / TILEY);
- subMainGameLoop_Main(single_player_action, warp_mode);
+ subMainGameLoop_Main(single_player_action);
RedrawPlayfield_SP(FALSE);
#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] =
{
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;
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;
CreateImageTextures(texture_graphics[i]);
}
-static int getFontBitmapID(int font_nr)
+static int getFontSpecialSuffix(void)
{
int special = -1;
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
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;
if (graphic_info == NULL) // still at startup phase
{
InitFontInfo(font_initial, NUM_INITIAL_FONTS,
- getFontBitmapID, getFontFromToken);
+ getFontBitmapID, getFontFromToken, getTokenFromFont);
return;
}
}
InitFontInfo(font_bitmap_info, num_font_bitmaps,
- getFontBitmapID, getFontFromToken);
+ getFontBitmapID, getFontFromToken, getTokenFromFont);
}
static void InitGlobalAnimGraphicInfo(void)
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;
}
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;
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
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)
{
// 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];
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;
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)
IMG_BACKGROUND_REQUEST,
IMG_BACKGROUND,
+ IMG_BACKGROUND_LOADING_INITIAL,
+ IMG_BACKGROUND_LOADING,
IMG_BACKGROUND_TITLE_INITIAL,
IMG_BACKGROUND_TITLE,
IMG_BACKGROUND_MAIN,
IMG_BACKGROUND_LEVELS,
IMG_BACKGROUND_LEVELNR,
IMG_BACKGROUND_SCORES,
+ IMG_BACKGROUND_SCOREINFO,
IMG_BACKGROUND_EDITOR,
IMG_BACKGROUND_INFO,
IMG_BACKGROUND_INFO_ELEMENTS,
// 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);
}
}
+ // 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();
}
}
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);
}
}
+
+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");
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");
}
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,
-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,
EL_BD_AMOEBA,
EL_EMC_MAGIC_BALL,
EL_EMC_ANDROID,
+ EL_MM_GRAY_BALL,
-1
};
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,
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,
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,
{ 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 },
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)));
// ---------- 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 --------------------------------------------------
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++)
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;
{
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);
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 "))
{
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;
}
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)
}
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);
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++)
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)
{
InitGfxCustomArtworkInfo();
InitGfxOtherSettings();
+ InitGfxTileSizeInfo(TILESIZE, TILESIZE);
+
bitmap_font_initial = LoadCustomImage(filename_font_initial);
for (j = 0; j < NUM_INITIAL_FONTS; j++)
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);
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;
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();
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();
#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);
// !!! 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
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;
// ---------- 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)
// (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 -------------------------
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
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 ||
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
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");
}
SetGameStatus(last_game_status); // restore current game status
- init_last = init; // switch to new busy animation
-
FadeOut(REDRAW_ALL);
RedrawGlobalBorder();
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);
BackToFront();
- // deactivate toons on error message screen
- setup.toons = FALSE;
+ // deactivate toons and global animations on error message screen
+ setup.global_animations = FALSE;
WaitForEventToContinue();
}
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);
InitMixer();
print_timestamp_time("[init setup/config stuff (6)]");
- InitRND(NEW_RANDOMIZE);
- InitSimpleRandom(NEW_RANDOMIZE);
-
InitJoysticks();
print_timestamp_time("[init setup/config stuff]");
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();
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();
print_timestamp_done("OpenAll");
+ if (setup.ask_for_remaining_tapes)
+ setup.ask_for_uploading_tapes = TRUE;
+
DrawMainMenu();
#if 0
#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();
image.c \
random.c \
hash.c \
+ http.c \
+ base64.c \
setup.c \
misc.c \
sdl.c \
image.o \
random.o \
hash.o \
+ http.o \
+ base64.o \
setup.o \
misc.o \
sdl.o \
--- /dev/null
+// ============================================================================
+// 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 <stdio.h>
+#include <stdlib.h>
+
+#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);
+ }
+}
--- /dev/null
+// ============================================================================
+// 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
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;
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;
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)
{
{
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,
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);
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);
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,
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 : ' ');
// 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);
}
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);
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,
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,
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,
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;
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,
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,
{
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);
{
int tag = first_tag;
- if (gi == NULL || gi->deactivated)
+ if (gi == NULL)
return;
while (tag != GDI_END)
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;
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)
{
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;
}
}
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;
(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);
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);
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)
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 ||
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);
}
}
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);
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);
}
}
#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)
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
void InitGadgetsSoundCallback(void (*activating_function)(void),
void (*selecting_function)(void));
+void InitGadgetScreenBorders(int, int);
struct GadgetInfo *CreateGadget(int, ...);
void FreeGadget(struct GadgetInfo *);
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;
return NULL;
}
- for (i=0; i < size; i++)
+ for (i = 0; i < size; i++)
h->table[i] = NULL;
h->tablelength = size;
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;
}
{
for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE)
{
- index = indexFor(newsize,e->h);
+ index = indexFor(newsize, e->h);
if (index == i)
{
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];
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)
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)
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;
}
/*****************************************************************************/
-/* 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)
int
hashtable_iterator_advance(struct hashtable_itr *itr)
{
- unsigned int j,tablelength;
+ unsigned int j, tablelength;
struct entry **table;
struct entry *next;
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
#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); \
}
/*****************************************************************************
#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); \
}
/*****************************************************************************
#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)); \
}
/*****************************************************************************
#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)); \
}
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);
--- /dev/null
+// ============================================================================
+// Artsoft Retro-Game Library
+// ----------------------------------------------------------------------------
+// (c) 1995-2021 by Artsoft Entertainment
+// Holger Schemel
+// info@artsoft.org
+// https://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// http.c
+// ============================================================================
+
+#include <sys/stat.h>
+
+#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;
+}
--- /dev/null
+// ============================================================================
+// 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
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;
return;
if (zoom_factor != 1)
+ {
ScaleBitmap(img_info->bitmaps, zoom_factor);
- img_info->scaled_up = TRUE;
+ img_info->scaled_up = TRUE;
+ }
}
void FreeAllImages(void)
#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
#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 : \
int getOriginalImageWidthFromImageID(int);
int getOriginalImageHeightFromImageID(int);
char *getTokenFromImageID(int);
-char *getFilenameFromImageID(int);
int getImageIDFromToken(char *);
char *getImageConfigFilename(void);
int getImageListPropertyMappingSize(void);
// 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;
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];
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;
-}
#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
#include "image.h"
#include "setup.h"
#include "misc.h"
+#include "http.h"
+#include "base64.h"
#include "zip/miniunz.h"
#endif // LIBGAME_H
// 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)
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);
}
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);
}
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);
}
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);
}
+// ----------------------------------------------------------------------------
+// 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
// ----------------------------------------------------------------------------
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)
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;
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
// 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;
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);
}
{
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;
{
static char *real_name = NULL;
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
if (real_name == NULL)
{
static char buffer[MAX_USERNAME_LEN + 1];
// 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;
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;
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
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
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));
}
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 :
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 :
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 :
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 :
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 :
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 :
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);
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);
return match;
}
+boolean isURL(const char *s)
+{
+ while (*s && *s >= 'a' && *s <= 'z')
+ s++;
+
+ return strPrefix(s, "://");
+}
+
// ----------------------------------------------------------------------------
// command line option handling functions
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];
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;
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)
{
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;
// 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
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;
*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
}
+// ----------------------------------------------------------------------------
+// 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
// ----------------------------------------------------------------------------
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);
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
// functions for checking files and filenames
// ----------------------------------------------------------------------------
-boolean directoryExists(char *dir_name)
+boolean directoryExists(const char *dir_name)
{
if (dir_name == NULL)
return FALSE;
return success;
}
-boolean fileExists(char *filename)
+boolean fileExists(const char *filename)
{
if (filename == NULL)
return FALSE;
}
#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;
}
#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;
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++)
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);
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)
{
// (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;
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
#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
#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
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);
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),
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);
#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 *);
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 *);
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);
// 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
#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__)
#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__)
// random.c
// ============================================================================
+#include "random.h"
+
+
/*
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
#include <limits.h>
#include <stdlib.h>
-#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
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 <blp@cs.stanford.edu>.
+ * 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 <assert.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <time.h>
+
+/* 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;
+}
void srandom_linux_libc(int, unsigned int);
int random_linux_libc(int);
+
+// ============================================================================
+
+#include <stddef.h>
+
+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
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);
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)
// 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++;
// (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;
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);
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;
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;
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);
{
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;
}
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)
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 ?
dst_rect.w = width;
dst_rect.h = height;
+ SDLSetBitmapAlpha(bitmap, TRUE, mask_mode == BLIT_MASKED);
+
SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
}
draw_border_function();
UpdateScreen_WithFrameDelay(&dst_rect2);
+
+ if (PendingEscapeKeyEvent())
+ break;
}
}
}
// 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
// only update the region of the screen that is affected from fading
UpdateScreen_WithFrameDelay(&dst_rect);
+
+ if (PendingEscapeKeyEvent())
+ break;
}
}
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;
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;
}
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)
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;
}
}
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;
}
}
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;
// 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)
#include <SDL_mixer.h>
#include <SDL_net.h>
#include <SDL_thread.h>
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
#include <SDL_syswm.h>
#endif
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;
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);
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);
#include "platform.h"
#include "setup.h"
+#include "sound.h"
#include "joystick.h"
#include "text.h"
#include "misc.h"
#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
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;
// 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;
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;
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);
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 };
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;
return new_level_subdir;
}
-static char *getTapeDir(char *level_subdir)
+char *getTapeDir(char *level_subdir)
{
static char *tape_dir = NULL;
char *data_dir = getUserGameDataDir();
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;
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);
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);
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;
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];
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;
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;
return program.config_filename;
}
+char *getPlatformSetupFilename(void)
+{
+ return getPlatformConfigFilename(program.config_filename);
+}
+
char *getEditorSetupFilename(void)
{
static char *filename = NULL;
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",
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;
{
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)
{
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)
{
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)
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;
{
// 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);
{
// 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);
{
// 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);
// 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);
{
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();
+ }
}
}
{
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");
}
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)
*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);
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);
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)
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
*/
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,
// some stuff from "files.c"
// ============================================================================
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
#ifndef S_IRGRP
#define S_IRGRP S_IRUSR
#endif
#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)
{
static char *dir = NULL;
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
if (dir == NULL)
{
dir = checked_malloc(MAX_PATH + 1);
strcpy(dir, ".");
}
#elif defined(PLATFORM_EMSCRIPTEN)
- dir = "/persistent";
+ dir = PERSISTENT_DIRECTORY;
#elif defined(PLATFORM_UNIX)
if (dir == NULL)
{
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
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);
#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);
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");
}
}
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 = "# ";
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));
}
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");
// ============================================================================
#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"
#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;
{ 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[] =
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;
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;
}
}
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;
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;
}
}
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;
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);
}
}
+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
(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);
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)
{
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));
{
LoadArtworkInfoCache();
- DrawInitText("Looking for custom artwork", 120, FC_GREEN);
+ DrawInitTextHead("Looking for custom artwork");
LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
options.graphics_directory,
setArtworkInfoCacheEntry(artwork_new, level_node, type);
}
- DrawInitText(level_node->name, 150, FC_YELLOW);
+ DrawInitTextItem(level_node->name);
if (level_node->node_group != NULL)
{
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");
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;
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);
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)
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];
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);
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 *);
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 *);
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);
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 *);
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);
void UpdateLastPlayedLevels_TreeInfo(void);
void StoreLastPlayedLevels(TreeInfo *);
+void ForcedStoreLastPlayedLevels(TreeInfo *);
void RestoreLastPlayedLevels(TreeInfo **);
+boolean CheckLastPlayedLevels(void);
void LoadLevelSetup_LastSeries(void);
void SaveLevelSetup_LastSeries(void);
#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)
#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) : \
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))
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
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
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();
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
{
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);
closeDirectory(dir);
+ // sort music files by filename
+ qsort(Music_NoConf, num_music_noconf, sizeof(MusicInfo *), compareMusicInfo);
+
draw_init_text = FALSE;
}
music_info->num_dynamic_file_list_entries);
}
+int getMusicListSize_NoConf(void)
+{
+ return num_music_noconf;
+}
+
struct FileInfo *getSoundListEntry(int pos)
{
int num_sounds = getSoundListSize();
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);
LoadCustomMusic_NoConf();
}
-void InitReloadCustomSounds(char *set_identifier)
+void InitReloadCustomSounds(void)
{
if (!audio.sound_available)
return;
ReloadCustomSounds();
}
-void InitReloadCustomMusic(char *set_identifier)
+void InitReloadCustomMusic(void)
{
if (!audio.music_available)
return;
#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
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);
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);
// 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;
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,
#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)
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;
void ClosePlatformDependentStuff(void)
{
- CloseLogFiles();
+ CloseLogFile();
}
void InitGfxFieldInfo(int sx, int sy, int sxsize, int sysize,
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;
}
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;
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;
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
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);
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)
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;
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,
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)
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) ||
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))
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,
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)
{
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;
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)
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);
}
{
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;
}
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,
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;
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();
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)
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;
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
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
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
{
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
+}
#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"
#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"
#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)
#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
// 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
#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
// 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
#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
#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)
#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)
#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)
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"
// 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"
#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"
#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"
#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 "/"
#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
#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 || \
#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 : \
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)
#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); \
}
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;
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;
};
struct RuntimeInfo
{
boolean uses_touch_device;
+
+ boolean use_api_server;
};
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;
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;
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;
};
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;
boolean draw_pressed;
boolean grid_initialized;
+
+ boolean overlay_buttons;
};
struct SetupInputInfo
boolean el_steel_chars;
boolean el_ce;
boolean el_ge;
+ boolean el_es;
boolean el_ref;
boolean el_user;
boolean el_dynamic;
{
Key save_game;
Key load_game;
+ Key restart_game;
+ Key pause_before_end;
Key toggle_pause;
Key focus_player[MAX_PLAYERS];
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;
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
struct SetupInfo
{
char *player_name;
+ char *player_uuid;
+ int player_version;
boolean multiple_users;
boolean sound_music;
boolean sound_simple;
boolean toons;
+ boolean global_animations;
boolean scroll_delay;
boolean forced_scroll_delay;
int scroll_delay_value;
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;
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;
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;
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
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
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;
void InitNetworkInfo(boolean, boolean, boolean, char *, int);
void InitRuntimeInfo(void);
-void InitScoresInfo(void);
void SetWindowTitle(void);
void InitWindowTitleFunction(char *(*window_title_function)(void));
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);
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);
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);
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);
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);
Bitmap *LoadCustomImage(char *);
void ReloadCustomImage(Bitmap *, char *);
+Bitmap *ZoomBitmap(Bitmap *, int, int);
void ReCreateGameTileSizeBitmap(Bitmap **);
void CreateBitmapWithSmallBitmaps(Bitmap **, int, int);
void CreateBitmapTextures(Bitmap **);
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
#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)
// 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 &&
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];
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;
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;
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);
closeFile(file);
+ if (getTextEncoding(buffer) == TEXT_ENCODING_UTF_8)
+ {
+ char *body_latin1 = getLatin1FromUTF8(buffer);
+
+ checked_free(buffer);
+
+ buffer = body_latin1;
+ }
+
return 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++;
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;
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;
// 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
// 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++;
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++;
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++;
}
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,
// 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);
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,
#include <sys/types.h>
-#if !defined(PLATFORM_WIN32)
+#if !defined(PLATFORM_WINDOWS)
typedef int boolean;
typedef unsigned char byte;
#endif
#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
};
typedef struct ListNode ListNode;
+struct DelayCounter
+{
+ unsigned int value;
+ unsigned int count;
+};
+typedef struct DelayCounter DelayCounter;
+
#endif // TYPES_H
{
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)
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);
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;
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;
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;
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;
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;
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 */
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
}
# 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);
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 ;
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;
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;
#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;
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;
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;
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
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
}
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;
}
#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;
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;
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];
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;
SetupFileHash *graphic_token_hash = NULL;
SetupFileHash *font_token_hash = NULL;
SetupFileHash *hide_setup_hash = NULL;
+SetupFileHash *anim_url_hash = NULL;
// ----------------------------------------------------------------------------
{
"mm_kettle",
"mm_kettle",
- "magic kettle"
+ "magic cauldron"
},
{
"mm_bomb",
{
"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",
"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",
{
"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",
"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
"mm_exit",
"-"
},
+ {
+ "mm_gray_ball.active",
+ "mm_gray_ball",
+ "-",
+ },
{
"mm_gray_ball.opening",
"mm_gray_ball",
"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
"-",
"-"
},
- {
- "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",
"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",
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, },
{ ".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, },
{ ".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, },
{ "font.envelope_2" },
{ "font.envelope_3" },
{ "font.envelope_4" },
+ { "font.request_narrow" },
{ "font.request" },
{ "font.input_1.active" },
{ "font.input_2.active" },
"\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"
" \"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);
}
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 &&
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
InitProgramInfo(command_filename,
config_filename,
userdata_subdir,
- program_title,
+ program_basename,
program_title,
program_icon_file,
COOKIE_PREFIX,
// 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
#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)))
#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)
#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
#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)
#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)
#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)
#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 || \
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 && \
(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)
#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))
#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)
#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)
#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
#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
#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
#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)
#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
#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
#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)
#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
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,
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,
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,
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
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,
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
};
FONT_ENVELOPE_2,
FONT_ENVELOPE_3,
FONT_ENVELOPE_4,
+ FONT_REQUEST_NARROW,
FONT_REQUEST,
FONT_INPUT_1_ACTIVE,
FONT_INPUT_2_ACTIVE,
// 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
#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
#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
#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
// 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"
// 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
#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
#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
struct MenuPosInfo insert_solution;
struct MenuPosInfo play_solution;
+ struct MenuPosInfo levelset_info;
struct MenuPosInfo switch_ecs_aga;
};
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;
struct InitInfo
{
+ struct MenuPosInfo busy_initial;
struct MenuPosInfo busy;
+ struct MenuPosInfo busy_playfield;
};
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];
struct MenuMainInfo main;
struct MenuSetupInfo setup;
+ struct MenuScoresInfo scores;
};
struct DoorInfo
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
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];
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
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
{
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;
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;
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
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];
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
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
};
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
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;
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;
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];
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;
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[];
#include "network.h"
#include "init.h"
#include "config.h"
+#include "api.h"
#define DEBUG_JOYSTICKS 0
#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"
#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"
#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
#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)
// 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
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);
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;
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;
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 %" },
{ 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" },
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 ? \
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))
struct TitleControlInfo title_controls[MAX_NUM_TITLE_SCREENS];
+
// main menu display and control definitions
#define MAIN_CONTROL_NAME 0
};
+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;
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)
// 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)
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);
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;
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);
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;
// 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);
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);
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)
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,
{
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)
{
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];
}
}
+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)
{
{
// 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();
}
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
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
{
SetGameStatus(GAME_MODE_SCORES);
- DrawHallOfFame(level_nr, -1);
+ DrawHallOfFame(level_nr);
}
else if (pos == MAIN_CONTROL_EDITOR)
{
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
}
}
}
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 }
};
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)
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;
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++)
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);
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;
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;
}
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;
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++;
}
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);
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;
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();
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)
}
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();
}
}
{
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)
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;
}
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)
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;
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());
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);
}
}
}
-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();
}
}
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);
}
}
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);
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'));
// 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();
// 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)
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);
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)
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);
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();
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);
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);
}
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);
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,
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);
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 &&
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);
}
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();
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;
}
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;
}
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;
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);
{
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();
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)
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();
{
// 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)
{
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);
}
}
}
+
+ if (game_status == GAME_MODE_SCORES)
+ PlayMenuSoundIfLoop();
}
void DrawChoosePlayerName(void)
{
int i;
- FadeMenuSoundsAndMusic();
-
if (player_name != NULL)
{
freeTreeInfo(player_name);
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)
void DrawChooseLevelSet(void)
{
- FadeMenuSoundsAndMusic();
-
DrawChooseTree(&leveldir_current);
-
- PlayMenuSoundsAndMusic();
}
void HandleChooseLevelSet(int mx, int my, int dx, int dy, int button)
{
int i;
- FadeMenuSoundsAndMusic();
-
if (level_number != NULL)
{
freeTreeInfo(level_number);
level_number_current = level_number;
DrawChooseTree(&level_number_current);
-
- PlayMenuSoundsAndMusic();
}
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();
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();
}
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;
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)
// 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)
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();
DisableVsyncIfNeeded();
}
+static void execSetupChooseScoresType(void)
+{
+ setup_mode = SETUP_MODE_CHOOSE_SCORES_TYPE;
+
+ DrawSetupScreen();
+}
+
static void execSetupChooseGameSpeed(void)
{
setup_mode = SETUP_MODE_CHOOSE_GAME_SPEED;
// 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)
// 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)
// 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)
ClickOnGadget(gi, MB_LEFTBUTTON);
}
+static void execOfferUploadTapes(void)
+{
+ OfferUploadTapes();
+}
+
static void ToggleNetworkModeIfNeeded(void)
{
int font_title = FONT_TITLE_1;
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) ||
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 },
{ &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 }
};
{ 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:" },
{ 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:" },
#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" },
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, "" },
{ 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, "" },
{ 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, "" },
{
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)
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)
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;
// (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)
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();
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)
HandleOtherEvents(&event);
break;
}
+
+ // do not handle events for longer than standard frame delay period
+ if (DelayReached(&event_frame_delay))
+ break;
}
BackToFront();
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);
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();
{ 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");
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)
}
// 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;
}
}
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);
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();
case EVENT_KEYPRESS:
{
- Key key = GetEventKey((KeyEvent *)&event, FALSE);
+ Key key = GetEventKey((KeyEvent *)&event);
action = (key == KSYM_Escape ? ACTION_ESCAPE :
key == KSYM_BackSpace ||
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);
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();
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)
{
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)
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
}
static struct
{
- int gfx_unpressed, gfx_pressed;
+ int gfx_unpressed, gfx_pressed, gfx_active;
struct MenuPosInfo *pos;
boolean *check_value;
int gadget_id;
} 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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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;
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));
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;
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,
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,
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;
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;
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;
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();
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:
}
}
+void HandleScreenGadgetKeys(Key key)
+{
+ if (key == setup.shortcut.tape_play)
+ HandleScreenGadgets(screen_gadget[SCREEN_CTRL_ID_PLAY_TAPE]);
+}
+
void DumpScreenIdentifiers(void)
{
int i;
}
}
}
+
+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();
+}
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);
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);
void DumpScreenIdentifiers(void);
boolean DoScreenAction(int);
+void CheckApiServerTasks(void);
+
#endif // SCREENS_H
#include "files.h"
#include "network.h"
#include "anim.h"
+#include "api.h"
+
#define DEBUG_TAPE_WHEN_PLAYING FALSE
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;
// 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;
}
}
+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
tape.length_frames = 0;
tape.length_seconds = 0;
+ tape.score_tape_basename[0] = '\0';
+
if (leveldir_current)
{
strncpy(tape.level_identifier, leveldir_current->identifier,
tape.level_nr = level_nr;
tape.pos[tape.counter].delay = 0;
tape.changed = TRUE;
+ tape.solved = FALSE;
tape.random_seed = InitRND(level.random_seed);
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++)
// 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();
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)
tape.pos[tape.counter].delay++;
}
+ tape.changed = TRUE;
+
return TRUE;
}
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();
}
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)
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)
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
{
if (tape.auto_play)
PrintTapeReplayProgress(FALSE);
+ if (options.tape_log_filename != NULL)
+ WriteTapeLogfile(action);
+
return action;
}
}
}
+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;
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();
}
TapeStartWarpForward(AUTOPLAY_MODE_WARP_NO_DISPLAY);
tape.quick_resume = TRUE;
+ tape.property_bits |= TAPE_PROPERTY_SNAPSHOT;
}
else // this should not happen (basically checked above)
{
}
}
+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));
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) &&
// 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[] =
{
-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)
{
SaveTapeToFilename(filename);
tape.auto_play_level_fixed = TRUE;
- num_tapes_patched++;
+ autoplay.num_tapes_patched++;
}
// continue with next tape
// 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, "");
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);
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);
}
}
+ 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);
}
break;
case TAPE_CTRL_ID_STOP:
- TapeStop();
+ TapeStopTape();
break;
// 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
#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)
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;
boolean quick_resume;
boolean single_step;
boolean changed;
+ boolean solved;
boolean player_participates[MAX_PLAYERS];
int num_participating_players;
int centered_player_nr_next;
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
};
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);
}
};
+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);
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;
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;
}
}
-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)
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
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)
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)
}
}
-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,
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,
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];
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)];
}
*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);
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)
{
#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!");
#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!");
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);
}
}
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,
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))
else // border element
{
graphic = el2img(element);
- frame = getGraphicAnimationFrame(graphic, -1);
+ frame = getGraphicAnimationFrameXY(graphic, lx, ly);
}
if (element == EL_EXPANDABLE_WALL)
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;
// 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;
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;
// 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);
// 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))
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) ||
}
}
+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;
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)
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)))
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);
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();
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 ||
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;
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);
}
}
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;
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;
{
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;
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++)
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;
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++)
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();
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)
}
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)
void DrawLevel(int draw_background_mask)
{
- int x,y;
+ int x, y;
SetMainBackgroundImage(IMG_BACKGROUND_PLAYING);
SetDrawBackgroundMask(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++)
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++)
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;
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)
{
// 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)
{
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;
{
int element = level.field[x][y];
- if (ELEM_IS_PLAYER(element))
+ if (IS_PLAYER_ELEMENT(element))
{
int player_nr = GET_PLAYER_NR(element);
}
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);
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);
}
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);
}
}
DrawGraphicAnimationExt(drawto_field, FX + x * TILEX, FY + y * TILEY,
- graphic, GfxFrame[lx][ly], mask_mode);
+ graphic, lx, ly, mask_mode);
MarkTileDirty(x, y);
}
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
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)
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)
{
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
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;
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);
}
}
-#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;
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())
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)
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:
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;
}
case EVENT_KEYPRESS:
{
- Key key = GetEventKey((KeyEvent *)&event, TRUE);
+ Key key = GetEventKey((KeyEvent *)&event);
switch (key)
{
}
}
- 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);
// 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;
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;
}
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);
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);
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;
}
};
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)
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];
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;
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)
{
{
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,
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();
{
// 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();
+ }
}
}
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);
}
}
- 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;
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;
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 :
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);
}
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 :
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);
}
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);
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
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
}
}
-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)
}
}
-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)
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;
}
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);
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)
{
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];
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);
+}
#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);
void DrawMaskedBorder_ALL(void);
void DrawMaskedBorder(int);
void DrawMaskedBorderToTarget(int);
-void DrawTileCursor(int);
+void DrawTileCursor(int, int);
void SetDrawtoField(int);
int GetDrawtoField(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);
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);
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);
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);
void ShowEnvelope(int);
void ShowEnvelopeDoor(char *, int);
-void DrawEnvelopeRequestToScreen(int, int);
+void DrawEnvelopeRequestToScreen(int);
void DrawLevel(int);
void DrawSizedLevel(int, int, int, int, int);
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);
boolean CheckIfAllViewportsHaveChanged(void);
boolean CheckFadeAll(void);
+void OpenURL(char *);
+void OpenURLFromHash(SetupFileHash *, int);
+
+void TestGeneratingUUIDs(void);
+
#endif // TOOLS_H