diff --git a/src/extensions/extension_hooks.cpp b/src/extensions/extension_hooks.cpp
index 1d29ebb0c..3c71d96d9 100644
--- a/src/extensions/extension_hooks.cpp
+++ b/src/extensions/extension_hooks.cpp
@@ -107,6 +107,7 @@
#include "displayext_hooks.h"
#include "sidebarext_hooks.h"
+#include "mouseext_hooks.h"
#include "initext_hooks.h"
#include "mainloopext_hooks.h"
@@ -243,6 +244,7 @@ void Extension_Hooks()
DisplayClassExtension_Hooks();
SidebarClassExtension_Hooks();
+ MouseClassExtension_Hooks();
/**
* Various modules and functions.
diff --git a/src/extensions/mouse/mouseext_hooks.cpp b/src/extensions/mouse/mouseext_hooks.cpp
new file mode 100644
index 000000000..275c60160
--- /dev/null
+++ b/src/extensions/mouse/mouseext_hooks.cpp
@@ -0,0 +1,245 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file MOUSEEXT_HOOKS.CPP
+ *
+ * @author CCHyper
+ *
+ * @brief Contains the hooks for the extended MouseClass.
+ *
+ * @license Vinifera is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.
+ * If not, see .
+ *
+ ******************************************************************************/
+#include "mouseext_hooks.h"
+#include "mouse.h"
+#include "mousetype.h"
+#include "vinifera_globals.h"
+#include "wwmouse.h"
+#include "debughandler.h"
+#include "asserthandler.h"
+
+#include "hooker.h"
+#include "hooker_macros.h"
+
+
+/**
+ * A fake class for implementing new member functions which allow
+ * access to the "this" pointer of the intended class.
+ *
+ * @note: This must not contain a constructor or destructor!
+ * @note: All functions must be prefixed with "_" to prevent accidental virtualization.
+ */
+class MouseClassExt final : public MouseClass
+{
+ public:
+ void _AI(KeyNumType &input, Point2D &xy);
+ bool _Override_Mouse_Shape(MouseType mouse, bool wsmall = false);
+ void _Mouse_Small(bool wsmall = true);
+ int _Get_Mouse_Current_Frame(MouseType mouse, bool wsmall = false) const;
+ Point2D _Get_Mouse_Hotspot(MouseType mouse) const;
+ int _Get_Mouse_Start_Frame(MouseType mouse) const;
+ int _Get_Mouse_Frame_Count(MouseType mouse) const;
+};
+
+
+/**
+ * Controls the sizing of the mouse.
+ *
+ * @author: 09/21/1995 JLB - Red Alert source code.
+ * CCHyper - Adjustments for Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+void MouseClassExt::_Mouse_Small(bool wsmall)
+{
+ //MouseStruct const * control = &MouseControl[CurrentMouseShape];
+ MouseTypeClass const * control = MouseTypeClass::As_Pointer(CurrentMouseShape);
+
+ if (IsSmall == wsmall) {
+ return;
+ }
+
+ IsSmall = wsmall;
+
+ int frame = Get_Mouse_Current_Frame(CurrentMouseShape, wsmall);
+ Point2D hotspot = Get_Mouse_Hotspot(CurrentMouseShape);
+
+ WWMouse->Set_Cursor(&hotspot, MouseShapes, frame);
+}
+
+
+/**
+ * Alters the shape of the mouse.
+ *
+ * @author: 03/10/1994 JLB - Red Alert source code.
+ * CCHyper - Adjustments for Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+bool MouseClassExt::_Override_Mouse_Shape(MouseType mouse, bool wsmall)
+{
+ ASSERT((unsigned)mouse < MOUSE_COUNT);
+
+ //MouseStruct const * control = &MouseControl[mouse];
+ MouseTypeClass const * control = MouseTypeClass::As_Pointer(mouse);
+ static bool startup = false;
+ int baseshp;
+
+ /**
+ * Only certain mouse shapes have a small counterpart. If the requested mouse
+ * shape is not one of these, then force the small size override flag to false.
+ */
+ if (control->SmallFrame == -1) {
+ wsmall = false;
+ }
+
+ /**
+ * If the mouse shape is going to change, then inform the mouse driver of the
+ * change.
+ */
+ if (!startup || (MouseShapes && ((mouse != CurrentMouseShape) || (wsmall != IsSmall)))) {
+ startup = true;
+
+ Timer = control->FrameRate;
+ Frame = 0;
+
+ baseshp = Get_Mouse_Current_Frame(mouse, wsmall);
+ Point2D hotspot = Get_Mouse_Hotspot(mouse);
+ WWMouse->Set_Cursor(&hotspot, MouseShapes, baseshp);
+ CurrentMouseShape = mouse;
+ IsSmall = wsmall;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Process player input as it relates to the mouse.
+ *
+ * @author: 12/24/1994 JLB - Red Alert source code.
+ * CCHyper - Adjustments for Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+void MouseClassExt::_AI(KeyNumType &input, Point2D &xy)
+{
+ //MouseStruct const * control = &MouseControl[CurrentMouseShape];
+ MouseTypeClass const * control = MouseTypeClass::As_Pointer(CurrentMouseShape);
+
+ if (control->FrameRate && Timer == 0) {
+
+ Frame++;
+ Frame %= control->FrameCount;
+ Timer = control->FrameRate;
+ int baseframe = Get_Mouse_Current_Frame(CurrentMouseShape, IsSmall);
+ Point2D hotspot = Get_Mouse_Hotspot(CurrentMouseShape);
+ WWMouse->Set_Cursor(&hotspot, MouseShapes, baseframe);
+ }
+
+ ScrollClass::AI(input, xy);
+}
+
+
+/**
+ * x
+ *
+ * @author: CCHyper - Reimplemented from Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+int MouseClassExt::_Get_Mouse_Current_Frame(MouseType mouse, bool wsmall) const
+{
+ //MouseStruct const * control = &MouseControl[mouse];
+ MouseTypeClass const * control = MouseTypeClass::As_Pointer(mouse);
+
+ if (wsmall) {
+ if (control->SmallFrame != -1) {
+ return control->SmallFrame + Frame;
+ }
+ }
+
+ return control->StartFrame + Frame;
+}
+
+
+/**
+ * x
+ *
+ * @author: CCHyper - Reimplemented from Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+Point2D MouseClassExt::_Get_Mouse_Hotspot(MouseType mouse) const
+{
+ Point2D hotspot(0,0);
+
+ //MouseStruct const * control = &MouseControl[mouse];
+ MouseTypeClass const * control = MouseTypeClass::As_Pointer(mouse);
+
+ int hotspot_x = control->Hotspot.X;
+ if (hotspot_x == MOUSE_HOTSPOT_CENTER) {
+ hotspot.X = MouseShapes->Get_Width() / 2;
+ }
+ if (hotspot_x == MOUSE_HOTSPOT_MAX) {
+ hotspot.X = MouseShapes->Get_Width();
+ }
+
+ int hotspot_y = control->Hotspot.Y;
+ if (hotspot_y == MOUSE_HOTSPOT_CENTER) {
+ hotspot.Y = MouseShapes->Get_Height() / 2;
+ }
+ if (hotspot_y == MOUSE_HOTSPOT_MAX) {
+ hotspot.Y = MouseShapes->Get_Height();
+ }
+
+ return hotspot;
+}
+
+
+/**
+ * Returns the starting frame of the mouse.
+ *
+ * @author: CCHyper - Reimplemented from Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+int MouseClassExt::_Get_Mouse_Start_Frame(MouseType mouse) const
+{
+ //return MouseControl[mouse].StartFrame;
+ return MouseTypeClass::As_Pointer(mouse)->StartFrame;
+}
+
+
+/**
+ * Returns the frame count of the mouse.
+ *
+ * @author: CCHyper - Reimplemented from Tiberian Sun.
+ * CCHyper - Change use of MouseControl to MouseTypes.
+ */
+int MouseClassExt::_Get_Mouse_Frame_Count(MouseType mouse) const
+{
+ //return MouseControl[mouse].FrameCount;
+ return MouseTypeClass::As_Pointer(mouse)->FrameCount;
+}
+
+
+void MouseClassExtension_Hooks()
+{
+ Patch_Jump(0x00562200, &MouseClassExt::_Mouse_Small);
+ Patch_Jump(0x005622D0, &MouseClassExt::_Get_Mouse_Current_Frame);
+ Patch_Jump(0x00562310, &MouseClassExt::_Get_Mouse_Hotspot);
+ Patch_Jump(0x00562390, &MouseClassExt::_Override_Mouse_Shape);
+ Patch_Jump(0x005624D0, &MouseClassExt::_AI);
+ Patch_Jump(0x00563220, &MouseClassExt::_Get_Mouse_Start_Frame);
+ Patch_Jump(0x00563240, &MouseClassExt::_Get_Mouse_Frame_Count);
+}
diff --git a/src/extensions/mouse/mouseext_hooks.h b/src/extensions/mouse/mouseext_hooks.h
new file mode 100644
index 000000000..cfc555e94
--- /dev/null
+++ b/src/extensions/mouse/mouseext_hooks.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file MOUSEEXT_HOOKS.H
+ *
+ * @author CCHyper
+ *
+ * @brief Contains the hooks for the extended MouseClass.
+ *
+ * @license Vinifera is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.
+ * If not, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+
+void MouseClassExtension_Hooks();
diff --git a/src/new/mousetype/mousetype.cpp b/src/new/mousetype/mousetype.cpp
new file mode 100644
index 000000000..90f6cca0d
--- /dev/null
+++ b/src/new/mousetype/mousetype.cpp
@@ -0,0 +1,446 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file MOUSETYPE.CPP
+ *
+ * @author CCHyper
+ *
+ * @brief Mouse cursor controls and overrides.
+ *
+ * @license Vinifera is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.
+ * If not, see StartFrame = std::strtol(tok, nullptr, 10);
+
+ tok = std::strtok(nullptr, ",");
+ mousectrl->FrameCount = std::strtol(tok, nullptr, 10);
+
+ tok = std::strtok(nullptr, ",");
+ mousectrl->FrameRate = std::strtol(tok, nullptr, 10);
+
+ tok = std::strtok(nullptr, ",");
+ mousectrl->SmallFrame = std::strtol(tok, nullptr, 10);
+
+ tok = std::strtok(nullptr, ",");
+ if (!strcmpi(tok, "left")) {
+ value = MOUSE_HOTSPOT_MIN;
+ } else if (!strcmpi(tok, "center")) {
+ value = MOUSE_HOTSPOT_CENTER;
+ } else if (!strcmpi(tok, "right")) {
+ value = MOUSE_HOTSPOT_MAX;
+ } else {
+ value = std::strtol(tok, nullptr, 10);
+ }
+ mousectrl->Hotspot.X = value;
+
+ tok = std::strtok(nullptr, ",");
+ if (!strcmpi(tok, "top")) {
+ value = MOUSE_HOTSPOT_MIN;
+ } else if (!strcmpi(tok, "middle")) {
+ value = MOUSE_HOTSPOT_CENTER;
+ } else if (!strcmpi(tok, "bottom")) {
+ value = MOUSE_HOTSPOT_MAX;
+ } else {
+ value = std::strtol(tok, nullptr, 10);
+ }
+ mousectrl->Hotspot.Y = value;
+
+ }
+
+ return true;
+}
+
+
+#ifndef NDEBUG
+/**
+ * Writes out the default mouse control values.
+ *
+ * @author: CCHyper
+ */
+bool MouseTypeClass::Write_Default_Mouse_INI(CCINIClass &ini)
+{
+ static char const * const MOUSE = "MouseTypes";
+
+ char buffer[1024];
+
+ for (MouseType mouse = MOUSE_NORMAL; mouse < MOUSE_COUNT; ++mouse) {
+
+ MouseTypeClass &mousectrl = MouseControl[mouse];
+
+ const char *hotspot_x = nullptr;
+ const char *hotspot_y = nullptr;
+
+ switch (mousectrl.Hotspot.X) {
+ case MOUSE_HOTSPOT_MIN:
+ hotspot_x = "left";
+ break;
+ case MOUSE_HOTSPOT_CENTER:
+ hotspot_x = "center";
+ break;
+ case MOUSE_HOTSPOT_MAX:
+ hotspot_x = "right";
+ break;
+ default:
+ DEBUG_ERROR("Mouse: Invalid hotspot X for %s!\n", MouseNames[mouse]);
+ return false;
+ };
+
+ switch (mousectrl.Hotspot.Y) {
+ case MOUSE_HOTSPOT_MIN:
+ hotspot_y = "top";
+ break;
+ case MOUSE_HOTSPOT_CENTER:
+ hotspot_y = "middle";
+ break;
+ case MOUSE_HOTSPOT_MAX:
+ hotspot_y = "bottom";
+ break;
+ default:
+ DEBUG_ERROR("Mouse: Invalid hotspot Y for %s!\n", MouseNames[mouse]);
+ return false;
+ };
+
+ std::snprintf(buffer, sizeof(buffer), "%d,%d,%d,%d,%s,%s",
+ mousectrl.StartFrame,
+ mousectrl.FrameCount,
+ mousectrl.FrameRate,
+ mousectrl.SmallFrame,
+ hotspot_x,
+ hotspot_y);
+
+ ini.Put_String(MOUSE, MouseNames[mouse], buffer);
+ }
+
+ return true;
+}
+#endif
+
+
+/**
+ * Converts a mouse number into a mouse control object pointer.
+ *
+ * @author: CCHyper
+ */
+const MouseTypeClass *MouseTypeClass::As_Pointer(MouseType type)
+{
+ //ASSERT(type >= MOUSE_NORMAL && type < MouseTypes.Count());
+ return type >= MOUSE_NORMAL && type < MouseTypes.Count() ? MouseTypes[type] : nullptr;
+}
+
+
+/**
+ * Converts a mouse name into a mouse control object pointer.
+ *
+ * @author: CCHyper
+ */
+const MouseTypeClass *MouseTypeClass::As_Pointer(const char *name)
+{
+ return As_Pointer(From_Name(name));
+}
+
+
+/**
+ * Retrieves the mouse type for given name.
+ *
+ * @author: CCHyper
+ */
+MouseType MouseTypeClass::From_Name(const char *name)
+{
+ ASSERT(name != nullptr);
+
+ if (Wstring(name) == "" || Wstring(name) == "none") {
+ return MOUSE_NORMAL;
+ }
+
+ if (name != nullptr) {
+ for (MouseType index = MOUSE_NORMAL; index < MouseTypes.Count(); ++index) {
+ if (Wstring(MouseNames[index]) == name) {
+ return index;
+ }
+ }
+ }
+
+ return MOUSE_NORMAL;
+}
+
+
+/**
+ * Returns name for given mouse control type.
+ *
+ * @author: CCHyper
+ */
+const char *MouseTypeClass::Name_From(MouseType type)
+{
+ return (type >= MOUSE_NORMAL && type < MouseTypes.Count() ? MouseNames[type] : "");
+}
diff --git a/src/new/mousetype/mousetype.h b/src/new/mousetype/mousetype.h
new file mode 100644
index 000000000..934f5f1f4
--- /dev/null
+++ b/src/new/mousetype/mousetype.h
@@ -0,0 +1,93 @@
+/*******************************************************************************
+/* O P E N S O U R C E -- V I N I F E R A **
+/*******************************************************************************
+ *
+ * @project Vinifera
+ *
+ * @file MOUSETYPE.H
+ *
+ * @author CCHyper
+ *
+ * @brief Mouse cursor controls and overrides.
+ *
+ * @license Vinifera is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version
+ * 3 of the License, or (at your option) any later version.
+ *
+ * Vinifera is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.
+ * If not, see .
+ *
+ ******************************************************************************/
+#pragma once
+
+#include "always.h"
+#include "iomap.h"
+#include "point.h"
+
+
+class CCINIClass;
+class NoInitClass;
+
+
+/**
+ * This type is used to control the frames and rates of the mouse
+ * pointer. Some mouse pointers are actually looping animations.
+ */
+class MouseTypeClass
+{
+ friend class MouseClassExt;
+
+ public:
+ MouseTypeClass(int start_frame, int frame_count, int frame_rate, int small_frame, Point2D hotspot);
+ MouseTypeClass(const NoInitClass &noinit);
+ virtual ~MouseTypeClass();
+
+ static void One_Time();
+
+ static bool Read_Mouse_INI(CCINIClass &ini);
+#ifndef NDEBUG
+ static bool Write_Default_Mouse_INI(CCINIClass &ini);
+#endif
+
+ static const MouseTypeClass *As_Pointer(MouseType type);
+ static const MouseTypeClass *As_Pointer(const char *name);
+ static MouseType From_Name(const char *name);
+ static const char *Name_From(MouseType type);
+
+ private:
+ /**
+ * Starting frame number.
+ */
+ int StartFrame;
+
+ /**
+ * Number of animation frames.
+ */
+ int FrameCount;
+
+ /**
+ * Frame delay between changing frames.
+ */
+ int FrameRate;
+
+ /**
+ * Start frame number for small version (if any).
+ */
+ int SmallFrame;
+
+ /**
+ * Hotspot X and Y offset.
+ */
+ Point2D Hotspot;
+
+ private:
+ static MouseTypeClass MouseControl[MOUSE_COUNT];
+ static const char *MouseTypeClass::MouseNames[MOUSE_COUNT];
+};
diff --git a/src/vinifera/vinifera_functions.cpp b/src/vinifera/vinifera_functions.cpp
index f1a0fbce9..946f2ab6f 100644
--- a/src/vinifera/vinifera_functions.cpp
+++ b/src/vinifera/vinifera_functions.cpp
@@ -49,6 +49,7 @@
#include "extension.h"
#include "theatertype.h"
#include "uicontrol.h"
+#include "mousetype.h"
#include "debughandler.h"
#include "asserthandler.h"
#include
@@ -639,6 +640,7 @@ bool Vinifera_Shutdown()
*/
EBoltClass::Clear_All();
TheaterTypes.Clear();
+ MouseTypes.Clear();
/**
* Cleanup global extension instances.
@@ -697,6 +699,44 @@ int Vinifera_Pre_Init_Game(int argc, char *argv[])
Vinifera_SkipStartupMovies = true;
#endif
+ /**
+ * Read the mouse controls and overrides.
+ *
+ * This must be loaded before Init_Game as MouseClass::Override_Mouse_Shape
+ * is called as part of the games initialisation.
+ */
+ MouseTypeClass::One_Time();
+
+#ifndef NDEBUG
+ /**
+ * Write the default mouse control values to ini.
+ */
+ {
+ CCFileClass mouse_write_file("MOUSE.DBG");
+ CCINIClass mouse_write_ini;
+ mouse_write_file.Delete();
+ MouseTypeClass::Write_Default_Mouse_INI(mouse_write_ini);
+ mouse_write_ini.Save(mouse_write_file, false);
+ mouse_write_file.Close();
+ }
+#endif
+
+ CCFileClass mouse_file("MOUSE.INI");
+
+ if (mouse_file.Is_Available()) {
+
+ CCINIClass mouse_ini;
+ mouse_ini.Load(mouse_file, false);
+
+ if (!MouseTypeClass::Read_Mouse_INI(mouse_ini)) {
+ DEV_DEBUG_ERROR("Failed to read MOUSE.INI!\n");
+ return EXIT_FAILURE;
+ }
+
+ } else {
+ DEV_DEBUG_WARNING("MOUSE.INI not found!\n");
+ }
+
return EXIT_SUCCESS;
}
diff --git a/src/vinifera/vinifera_globals.cpp b/src/vinifera/vinifera_globals.cpp
index 0dc2bc495..32c3b8014 100644
--- a/src/vinifera/vinifera_globals.cpp
+++ b/src/vinifera/vinifera_globals.cpp
@@ -68,6 +68,7 @@ DynamicVectorClass ViniferaMoviesMixes;
DynamicVectorClass EBolts;
DynamicVectorClass TheaterTypes;
+DynamicVectorClass MouseTypes;
MFCC *GenericMix = nullptr;
MFCC *IsoGenericMix = nullptr;
diff --git a/src/vinifera/vinifera_globals.h b/src/vinifera/vinifera_globals.h
index 79abf62da..bc3d065fe 100644
--- a/src/vinifera/vinifera_globals.h
+++ b/src/vinifera/vinifera_globals.h
@@ -34,6 +34,7 @@
class EBoltClass;
class TheaterTypeClass;
+class MouseTypeClass;
extern bool Vinifera_DeveloperMode;
@@ -95,6 +96,7 @@ extern MFCC *IsoGenericMix;
*/
extern DynamicVectorClass EBolts;
extern DynamicVectorClass TheaterTypes;
+extern DynamicVectorClass MouseTypes;
/**