Merge changes

This commit is contained in:
UnknownShadow200 2021-02-05 07:15:14 +11:00
commit 08232da712
30 changed files with 516 additions and 298 deletions

View File

@ -8,16 +8,12 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="26"/>
<application android:icon="@mipmap/ccicon" android:label="ClassiCube">
<application
android:icon="@mipmap/ic_launcher"
android:label="ClassiCube">
<activity android:name="com.classicube.MainActivity"
android:label="ClassiCube"
<activity android:name="com.classicube.MainActivity" android:label="ClassiCube"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden">
<!-- Tell NativeActivity the name of our .so -->
<meta-data android:name="android.app.lib_name" android:value="classicube" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -130,7 +130,15 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
void startGameAsync() {
Log.i("CC_WIN", "handing off to native..");
System.loadLibrary("classicube");
try {
System.loadLibrary("classicube");
} catch (UnsatisfiedLinkError ex) {
ex.printStackTrace();
showAlert("Failed to start", ex.getMessage());
return;
}
gameRunning = true;
runGameAsync();
}
@ -157,7 +165,6 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
}
if (!gameRunning) startGameAsync();
gameRunning = true;
super.onCreate(savedInstanceState);
}
@ -392,7 +399,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
setContentView(curView);
curView.requestFocus();
if (fullscreen) goFullscreen();
if (fullscreen) setUIVisibility(FULLSCREEN_FLAGS);
}
class LauncherView extends SurfaceView {
@ -516,7 +523,10 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
//ApplicationInfo info = getPackageManager().getApplicationInfo(name, 0);
ApplicationInfo info = getApplicationInfo();
File apkFile = new File(info.sourceDir);
return apkFile.lastModified();
// https://developer.android.com/reference/java/io/File#lastModified()
// lastModified is returned in milliseconds
return apkFile.lastModified() / 1000;
} catch (Exception ex) {
return 0;
}
@ -606,7 +616,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
//final Activity activity = this;
runOnUiThread(new Runnable() {
public void run() {
AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this, AlertDialog.THEME_HOLO_DARK);
AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this);
dlg.setTitle(title);
dlg.setMessage(message);
dlg.setPositiveButton("Close", new DialogInterface.OnClickListener() {
@ -623,29 +633,30 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
// TODO: this fails because multiple dialog boxes show
}
public int getWindowState() {
return fullscreen ? 1 : 0;
}
void goFullscreen() {
curView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
public int getWindowState() { return fullscreen ? 1 : 0; }
final static int FULLSCREEN_FLAGS = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
void setUIVisibility(int flags) {
if (curView == null) return;
try {
curView.setSystemUiVisibility(flags);
} catch (NoSuchMethodError ex) {
// Not available on API < 11 (Android 3.0)
ex.printStackTrace();
}
}
public void enterFullscreen() {
fullscreen = true;
runOnUiThread(new Runnable() {
public void run() {
if (curView != null) goFullscreen();
}
public void run() { setUIVisibility(FULLSCREEN_FLAGS); }
});
}
public void exitFullscreen() {
fullscreen = false;
runOnUiThread(new Runnable() {
public void run() {
if (curView != null) curView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
public void run() { setUIVisibility(View.SYSTEM_UI_FLAG_VISIBLE); }
});
}
@ -703,16 +714,19 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 {
int len;
try {
conn.connect();
Map<String, List<String>> all = conn.getHeaderFields();
for (Map.Entry<String, List<String>> h : all.entrySet()) {
String key = h.getKey();
for (String value : h.getValue()) {
if (key == null) {
httpParseHeader(value);
} else {
httpParseHeader(key + ":" + value);
}
// Some implementations also provide this as getHeaderField(0), but some don't
httpParseHeader("HTTP/1.1 " + conn.getResponseCode() + " MSG");
// Legitimate webservers aren't going to reply with over 200 headers
for (int i = 0; i < 200; i++) {
String key = conn.getHeaderFieldKey(i);
String val = conn.getHeaderField(i);
if (key == null && val == null) break;
if (key == null) {
httpParseHeader(val);
} else {
httpParseHeader(key + ":" + val);
}
}

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

64
misc/build_server.py Normal file
View File

@ -0,0 +1,64 @@
from http.server import HTTPServer, SimpleHTTPRequestHandler
import os
import subprocess
build_files = {
'/ClassiCube.exe' : 'cc-w32-d3d.exe', '/ClassiCube.opengl.exe' : 'cc-w32-ogl.exe',
'/ClassiCube.64.exe' : 'cc-w64-d3d.exe', '/ClassiCube.64-opengl.exe' : 'cc-w64-ogl.exe',
'/ClassiCube.32' : 'cc-nix32', '/ClassiCube' : 'cc-nix64',
'/ClassiCube.osx' : 'cc-osx32', '/ClassiCube.64.osx' : 'cc-osx64',
'/ClassiCube.js' : 'cc.js', '/ClassiCube.apk' : 'cc.apk',
'/ClassiCube.rpi' : 'cc-rpi',
}
release_files = {
'/win32' : 'win32/ClassiCube.exe', '/win64' : 'win64/ClassiCube.exe',
'/osx32' : 'osx32/ClassiCube.tar.gz', '/osx64' : 'osx64/ClassiCube.tar.gz',
'/mac32' : 'mac32/ClassiCube.tar.gz', '/mac64' : 'mac64/ClassiCube.tar.gz',
'/nix32' : 'nix32/ClassiCube.tar.gz', '/nix64' : 'nix64/ClassiCube.tar.gz',
'/rpi32' : 'rpi32/ClassiCube.tar.gz',
}
def run_script(file):
args = ["sh", file]
su = subprocess.Popen(args)
return su.wait()
class Handler(SimpleHTTPRequestHandler):
def serve_script(self, file, msg):
ret = run_script(file)
self.send_response(200)
self.end_headers()
self.wfile.write(msg % ret)
def do_GET(self):
if self.path in build_files:
self.serve_exe('client/src/' + build_files[self.path])
elif self.path in release_files:
self.serve_exe('client/release/' + release_files[self.path])
elif self.path == '/rebuild':
self.serve_script('build.sh', 'Rebuild client (%s)')
elif self.path == '/rebuild_android':
self.serve_script('build_android.sh', 'Rebuild android (%s)')
elif self.path == '/release':
self.serve_script('build_release.sh', 'Package release (%s)')
else:
self.send_error(404, "Unknown action")
return
def serve_exe(self, path):
try:
f = open(path, 'rb')
fs = os.fstat(f.fileno())
self.send_response(200)
self.send_header("Content-type", "application/octet-stream")
self.send_header("Content-Length", str(fs[6]))
self.end_headers()
self.copyfile(f, self.wfile)
f.close()
except IOError:
self.send_error(404, "File not found")
httpd = HTTPServer(('', 80), Handler)
httpd.serve_forever()

54
misc/buildbot_android.sh Normal file
View File

@ -0,0 +1,54 @@
FLAGS="-fPIC -shared -s -O1 -fvisibility=hidden -rdynamic"
LIBS="-lGLESv2 -lEGL -lm -landroid -llog"
NDK_ROOT="/home/buildbot/android/android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/bin"
TOOLS_ROOT="/home/buildbot/android/sdk/build-tools/26.0.0"
SDK_ROOT="/home/buildbot/android/sdk/platforms/android-26"
cd /home/buildbot/client/src
$NDK_ROOT/armv7a-linux-androideabi16-clang *.c $FLAGS -march=armv5 $LIBS -o cc-droid-arm_16
$NDK_ROOT/armv7a-linux-androideabi16-clang *.c $FLAGS $LIBS -o cc-droid-arm_32
$NDK_ROOT/aarch64-linux-android21-clang *.c $FLAGS $LIBS -o cc-droid-arm_64
$NDK_ROOT/i686-linux-android16-clang *.c $FLAGS $LIBS -o cc-droid-x86_32
$NDK_ROOT/x86_64-linux-android21-clang *.c $FLAGS $LIBS -o cc-droid-x86_64
cd ../android/app/src/main
# remove old java temp files
rm -rf obj
mkdir obj
rm classes.dex
# copy required native libraries
rm -rf lib
mkdir lib
mkdir lib/armeabi
mkdir lib/armeabi-v7a
mkdir lib/arm64-v8a
mkdir lib/x86
mkdir lib/x86_64
cp ~/client/src/cc-droid-arm_16 lib/armeabi/libclassicube.so
cp ~/client/src/cc-droid-arm_32 lib/armeabi-v7a/libclassicube.so
cp ~/client/src/cc-droid-arm_64 lib/arm64-v8a/libclassicube.so
cp ~/client/src/cc-droid-x86_32 lib/x86/libclassicube.so
cp ~/client/src/cc-droid-x86_64 lib/x86_64/libclassicube.so
# The following commands are for manually building an .apk, see
# https://spin.atomicobject.com/2011/08/22/building-android-application-bundles-apks-by-hand/
# https://github.com/cnlohr/rawdrawandroid/blob/master/Makefile
# https://stackoverflow.com/questions/41132753/how-can-i-build-an-android-apk-without-gradle-on-the-command-line
# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/Makefile
# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/CMakeLists.txt
# compile interop java file into its multiple .class files
javac java/com/classicube/MainActivity.java -d ./obj -classpath $SDK_ROOT/android.jar
# compile the multiple .class files into one .dex file
$TOOLS_ROOT/dx --dex --output=obj/classes.dex ./obj
# create initial .apk with packaged version of resources
$TOOLS_ROOT/aapt package -f -M AndroidManifest.xml -S res -F obj/cc-unsigned.apk -I $SDK_ROOT/android.jar
# and add all the required files
cp obj/classes.dex classes.dex
$TOOLS_ROOT/aapt add -f obj/cc-unsigned.apk classes.dex lib/armeabi/libclassicube.so lib/armeabi-v7a/libclassicube.so lib/arm64-v8a/libclassicube.so lib/x86/libclassicube.so lib/x86_64/libclassicube.so
# sign the apk with debug key (https://stackoverflow.com/questions/16711233/)
cp obj/cc-unsigned.apk obj/cc-signed.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debug.keystore -storepass android -keypass android obj/cc-signed.apk androiddebugkey
# create aligned .apk file
$TOOLS_ROOT/zipalign -f -v 4 obj/cc-signed.apk ~/client/src/cc.apk

View File

@ -17,22 +17,23 @@ def notify_webhook(body):
"content" : body
}
r = requests.post(WEBHOOK_URL, json=webhook_data)
print("BuildNotify response: " + r.text)
print("Webhook response: " + r.text)
except Exception as e:
print("BuildNotify failed: %s" % (e))
print("Webhook failed: %s" % (e))
def build_exists(file):
return os.path.exists('client/src/' + file)
builds = {
'Win32' : build_exists('cc-w32-d3d.exe'),
'Win64' : build_exists('cc-w64-d3d.exe'),
'Mac32' : build_exists('cc-osx32'),
'Mac64' : build_exists('cc-osx64'),
'Nix32' : build_exists('cc-nix32'),
'Nix64' : build_exists('cc-nix64'),
'Rpi' : build_exists('cc-rpi'),
'Web' : build_exists('cc.js'),
'Windows32' : build_exists('cc-w32-d3d.exe'),
'Windows64' : build_exists('cc-w64-d3d.exe'),
'macOS32' : build_exists('cc-osx32'),
'macOS64' : build_exists('cc-osx64'),
'Linux32' : build_exists('cc-nix32'),
'Linux64' : build_exists('cc-nix64'),
'RPi' : build_exists('cc-rpi'),
'Web' : build_exists('cc.js'),
'Android' : build_exists('cc.apk'),
'Win-ogl32' : build_exists('cc-w32-ogl.exe'),
'Win-ogl64' : build_exists('cc-w64-ogl.exe'),
}
@ -48,17 +49,19 @@ def check_build(key):
failed.append(key)
else:
if not builds[key_32] and not builds[key_64]:
failed.append(key + '32/64')
failed.append(key + ' 32/64')
elif not builds[key_32]:
failed.append(key_32)
failed.append(key + ' 32')
elif not builds[key_64]:
failed.append(key_64)
failed.append(key + ' 64')
check_build('Win')
check_build('Mac')
check_build('Nix')
check_build('Rpi')
check_build('Windows')
check_build('macOS')
check_build('Linux')
check_build('RPi')
check_build('Web')
check_build('Android')
check_build('Win-ogl')
if len(failed):
notify_webhook('<@%s>, failed to compile for: %s' % (TARGET_USER, ', '.join(failed)))
notify_webhook('<@%s>, failed to compile for: %s' % (TARGET_USER, ', '.join(failed)))

View File

@ -1,4 +1,4 @@
ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, OSX, Linux, BSD, Solaris, Haiku, and in a browser.
ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, BSD, Solaris, Haiku, and in a browser.
**It is not affiliated with (or supported by) Mojang AB, Minecraft, or Microsoft in any way.**
![screenshot_n](http://i.imgur.com/FCiwl27.png)
@ -14,9 +14,10 @@ You can grab the latest stable binaries [from here](https://www.classicube.net/d
It **does not** work with 'modern/premium' Minecraft servers.
#### Requirements
* Windows: 2000 or later. (Windows 98 with KernelEx also *technically* works)
* macOS: macOS 10.5 or later. (Can be compiled to work with 10.3/10.4 though)
* Windows: 98 or later.
* macOS: 10.5 or later. (Can be compiled to work with 10.3/10.4 though)
* Linux: libcurl and libopenal.
* Android: 2.3 or later.
**Note:** When running from within VirtualBox, disable Mouse Integration, otherwise the camera will not work properly.

View File

@ -17,6 +17,8 @@
#include "Window.h"
#endif
#define QUOTE(x) #x
#define DefineDynFunc(sym) { QUOTE(sym), (void**)&_ ## sym }
int Audio_SoundsVolume, Audio_MusicVolume;
#if defined CC_BUILD_NOAUDIO
@ -129,20 +131,18 @@ static const cc_string alLib = String_FromConst("libopenal.so");
static const cc_string alLib = String_FromConst("libopenal.so.1");
#endif
#define QUOTE(x) #x
#define DefineALFunc(sym) { QUOTE(sym), (void**)&_ ## sym }
static cc_bool LoadALFuncs(void) {
static const struct DynamicLibSym funcs[18] = {
DefineALFunc(alcCreateContext), DefineALFunc(alcMakeContextCurrent),
DefineALFunc(alcDestroyContext), DefineALFunc(alcOpenDevice),
DefineALFunc(alcCloseDevice), DefineALFunc(alcGetError),
DefineDynFunc(alcCreateContext), DefineDynFunc(alcMakeContextCurrent),
DefineDynFunc(alcDestroyContext), DefineDynFunc(alcOpenDevice),
DefineDynFunc(alcCloseDevice), DefineDynFunc(alcGetError),
DefineALFunc(alGetError), DefineALFunc(alGenSources),
DefineALFunc(alDeleteSources), DefineALFunc(alGetSourcei),
DefineALFunc(alSourcePlay), DefineALFunc(alSourceStop),
DefineALFunc(alSourceQueueBuffers), DefineALFunc(alSourceUnqueueBuffers),
DefineALFunc(alGenBuffers), DefineALFunc(alDeleteBuffers),
DefineALFunc(alBufferData), DefineALFunc(alDistanceModel)
DefineDynFunc(alGetError), DefineDynFunc(alGenSources),
DefineDynFunc(alDeleteSources), DefineDynFunc(alGetSourcei),
DefineDynFunc(alSourcePlay), DefineDynFunc(alSourceStop),
DefineDynFunc(alSourceQueueBuffers), DefineDynFunc(alSourceUnqueueBuffers),
DefineDynFunc(alGenBuffers), DefineDynFunc(alDeleteBuffers),
DefineDynFunc(alBufferData), DefineDynFunc(alDistanceModel)
};
void* lib = DynamicLib_Load2(&alLib);
@ -390,22 +390,51 @@ struct AudioContext {
SLBufferQueueItf bqPlayerQueue;
};
static SLresult (SLAPIENTRY *_slCreateEngine)(
SLObjectItf *pEngine,
SLuint32 numOptions,
const SLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
const SLInterfaceID *pInterfaceIds,
const SLboolean *pInterfaceRequired
);
static SLInterfaceID* _SL_IID_NULL;
static SLInterfaceID* _SL_IID_PLAY;
static SLInterfaceID* _SL_IID_ENGINE;
static SLInterfaceID* _SL_IID_BUFFERQUEUE;
static const cc_string slLib = String_FromConst("libOpenSLES.so");
static cc_bool LoadSLFuncs(void) {
static const struct DynamicLibSym funcs[5] = {
DefineDynFunc(slCreateEngine), DefineDynFunc(SL_IID_NULL),
DefineDynFunc(SL_IID_PLAY), DefineDynFunc(SL_IID_ENGINE),
DefineDynFunc(SL_IID_BUFFERQUEUE)
};
void* lib = DynamicLib_Load2(&slLib);
if (!lib) { Logger_DynamicLibWarn("loading", &slLib); return false; }
return DynamicLib_GetAll(lib, funcs, Array_Elems(funcs));
}
static cc_bool Backend_Init(void) {
static const cc_string msg = String_FromConst("Failed to init OpenSLES. No audio will play.");
SLInterfaceID ids[1];
SLboolean req[1];
SLresult res;
if (slEngineObject) return true;
/* mixer doesn't use any effects */
ids[0] = SL_IID_NULL; req[0] = SL_BOOLEAN_FALSE;
if (!LoadSLFuncs()) { Logger_WarnFunc(&msg); return false; }
res = slCreateEngine(&slEngineObject, 0, NULL, 0, NULL, NULL);
/* mixer doesn't use any effects */
ids[0] = *_SL_IID_NULL; req[0] = SL_BOOLEAN_FALSE;
res = _slCreateEngine(&slEngineObject, 0, NULL, 0, NULL, NULL);
if (res) { Logger_SimpleWarn(res, "creating OpenSL ES engine"); return false; }
res = (*slEngineObject)->Realize(slEngineObject, SL_BOOLEAN_FALSE);
if (res) { Logger_SimpleWarn(res, "realising OpenSL ES engine"); return false; }
res = (*slEngineObject)->GetInterface(slEngineObject, SL_IID_ENGINE, &slEngineEngine);
res = (*slEngineObject)->GetInterface(slEngineObject, *_SL_IID_ENGINE, &slEngineEngine);
if (res) { Logger_SimpleWarn(res, "initing OpenSL ES engine"); return false; }
res = (*slEngineEngine)->CreateOutputMix(slEngineEngine, &slOutputObject, 1, ids, req);
@ -489,16 +518,16 @@ static cc_result Backend_SetFormat(struct AudioContext* ctx, struct AudioFormat*
dst.pLocator = &output;
dst.pFormat = NULL;
ids[0] = SL_IID_BUFFERQUEUE; req[0] = SL_BOOLEAN_TRUE;
ids[1] = SL_IID_PLAY; req[1] = SL_BOOLEAN_TRUE;
ids[0] = *_SL_IID_BUFFERQUEUE; req[0] = SL_BOOLEAN_TRUE;
ids[1] = *_SL_IID_PLAY; req[1] = SL_BOOLEAN_TRUE;
res = (*slEngineEngine)->CreateAudioPlayer(slEngineEngine, &bqPlayerObject, &src, &dst, 2, ids, req);
ctx->bqPlayerObject = bqPlayerObject;
if (res) return res;
if ((res = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE))) return res;
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &ctx->bqPlayerPlayer))) return res;
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &ctx->bqPlayerQueue))) return res;
if ((res = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE))) return res;
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, *_SL_IID_PLAY, &ctx->bqPlayerPlayer))) return res;
if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, *_SL_IID_BUFFERQUEUE, &ctx->bqPlayerQueue))) return res;
return (*ctx->bqPlayerQueue)->RegisterCallback(ctx->bqPlayerQueue, OnBufferFinished, ctx);
}

View File

@ -5,7 +5,7 @@
*/
#define GAME_MAX_CMDARGS 5
#define GAME_APP_VER "1.2.3"
#define GAME_APP_VER "1.2.4"
#define GAME_API_VER 1
#if defined CC_BUILD_WEB
@ -13,14 +13,14 @@
#define GAME_APP_NAME "ClassiCube web"
#define GAME_APP_TITLE "ClassiCube"
#elif defined CC_BUILD_COCOA
#define GAME_APP_NAME "ClassiCube 1.2.3 alpha"
#define GAME_APP_TITLE "ClassiCube 1.2.3 alpha"
#define GAME_APP_NAME "ClassiCube 1.2.4 alpha"
#define GAME_APP_TITLE "ClassiCube 1.2.4 alpha"
#elif defined CC_BUILD_ANDROID
#define GAME_APP_NAME "ClassiCube 1.2.3 android alpha"
#define GAME_APP_TITLE "ClassiCube 1.2.3 android alpha"
#define GAME_APP_NAME "ClassiCube 1.2.4 android alpha"
#define GAME_APP_TITLE "ClassiCube 1.2.4 android alpha"
#else
#define GAME_APP_NAME "ClassiCube 1.2.3"
#define GAME_APP_TITLE "ClassiCube 1.2.3"
#define GAME_APP_NAME "ClassiCube 1.2.4"
#define GAME_APP_TITLE "ClassiCube 1.2.4"
#endif
/* Max number of characters strings can have. */

View File

@ -36,7 +36,7 @@ void DrawTextArgs_MakeEmpty(struct DrawTextArgs* args, struct FontDesc* font, cc
*-----------------------------------------------------Font functions------------------------------------------------------*
*#########################################################################################################################*/
static char defaultBuffer[STRING_SIZE];
static cc_string font_candidates[11] = {
static cc_string font_candidates[12] = {
String_FromArray(defaultBuffer), /* Filled in with user's default font */
String_FromConst("Arial"), /* preferred font on all platforms */
String_FromConst("Liberation Sans"), /* nice looking fallbacks for linux */
@ -45,9 +45,10 @@ static cc_string font_candidates[11] = {
String_FromConst("Cantarell"),
String_FromConst("DejaVu Sans Book"),
String_FromConst("Century Schoolbook L Roman"), /* commonly available on linux */
String_FromConst("Slate For OnePlus"), /* android 10, some devices */
String_FromConst("Roboto"), /* android (broken on some android 10 devices) */
String_FromConst("Geneva") /* for ancient macOS versions */
String_FromConst("Slate For OnePlus"), /* Android 10, some devices */
String_FromConst("Roboto"), /* Android (broken on some Android 10 devices) */
String_FromConst("Geneva"), /* for ancient macOS versions */
String_FromConst("Droid Sans") /* for old Android versions */
};
void Drawer2D_SetDefaultFont(const cc_string* fontName) {

View File

@ -973,13 +973,18 @@ void Gfx_CalcOrthoMatrix(float width, float height, struct Matrix* matrix) {
matrix->row4.Z = ORTHO_NEAR / (ORTHO_NEAR - ORTHO_FAR);
}
void Gfx_CalcPerspectiveMatrix(float fov, float aspect, float zFar, struct Matrix* matrix) {
static float CalcZNear(float fov) {
/* With reversed z depth, near Z plane can be much closer (with sufficient depth buffer precision) */
/* This reduces clipping with high FOV without sacrificing depth precision for faraway objects */
/* However for low FOV, don't reduce near Z in order to gain a bit more depth precision */
float zNear = (depthBits < 24 || fov <= 70 * MATH_DEG2RAD) ? 0.05f : 0.001953125f;
Matrix_PerspectiveFieldOfView(matrix, fov, aspect, zNear, zFar);
if (depthBits < 24 || fov <= 70 * MATH_DEG2RAD) return 0.05f;
if (fov <= 100 * MATH_DEG2RAD) return 0.025f;
if (fov <= 150 * MATH_DEG2RAD) return 0.0125f;
return 0.00390625f;
}
void Gfx_CalcPerspectiveMatrix(float fov, float aspect, float zFar, struct Matrix* matrix) {
Matrix_PerspectiveFieldOfView(matrix, fov, aspect, CalcZNear(fov), zFar);
/* Adjust the projection matrix to produce reversed Z values */
matrix->row3.Z = -matrix->row3.Z - 1.0f;
matrix->row4.Z = -matrix->row4.Z;

View File

@ -68,6 +68,18 @@ static cc_bool AnyBlockTouches(void) {
return false;
}
static void ClearTouches(void) {
int i;
for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0;
Pointers_Count = 0;
}
void Input_SetTouchMode(cc_bool enabled) {
ClearTouches();
Input_TouchMode = enabled;
Pointers_Count = enabled ? 0 : 1;
}
void Input_AddTouch(long id, int x, int y) {
int i;
for (i = 0; i < INPUT_MAX_POINTERS; i++) {
@ -215,6 +227,9 @@ void Input_SetPressed(int key) {
Input_Pressed[key] = true;
Event_RaiseInput(&InputEvents.Down, key, wasPressed);
if (key == 'C' && Key_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
if (key == 'V' && Key_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
/* don't allow multiple left mouse down events */
if (key != KEY_LMOUSE || wasPressed) return;
Pointer_SetPressed(0, true);
@ -456,9 +471,9 @@ int Hotkeys_FindPartial(int key) {
struct HotkeyData hk;
int i, modifiers = 0;
if (Key_IsControlPressed()) modifiers |= HOTKEY_MOD_CTRL;
if (Key_IsShiftPressed()) modifiers |= HOTKEY_MOD_SHIFT;
if (Key_IsAltPressed()) modifiers |= HOTKEY_MOD_ALT;
if (Key_IsCtrlPressed()) modifiers |= HOTKEY_MOD_CTRL;
if (Key_IsShiftPressed()) modifiers |= HOTKEY_MOD_SHIFT;
if (Key_IsAltPressed()) modifiers |= HOTKEY_MOD_ALT;
for (i = 0; i < HotkeysText.count; i++) {
hk = HotkeysList[i];
@ -806,7 +821,7 @@ cc_bool Input_HandleMouseWheel(float delta) {
struct HacksComp* h;
cc_bool hotbar;
hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed();
hotbar = Key_IsAltPressed() || Key_IsCtrlPressed() || Key_IsShiftPressed();
if (!hotbar && Camera.Active->Zoom(delta)) return true;
if (!KeyBind_IsPressed(KEYBIND_ZOOM_SCROLL)) return false;
@ -1089,9 +1104,7 @@ static void OnInit(void) {
static void OnFree(void) {
#ifdef CC_BUILD_TOUCH
int i;
for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0;
Pointers_Count = 0;
ClearTouches();
#endif
}

View File

@ -44,22 +44,25 @@ enum InputButtons {
/* NOTE: RMOUSE must be before MMOUSE for PlayerClick compatibility */
KEY_XBUTTON1, KEY_XBUTTON2, KEY_LMOUSE, KEY_RMOUSE, KEY_MMOUSE,
INPUT_COUNT
INPUT_COUNT,
INPUT_CLIPBOARD_COPY = 1001,
INPUT_CLIPBOARD_PASTE = 1002
};
/* Simple names for each input button. */
extern const char* const Input_Names[INPUT_COUNT];
#define Key_IsWinPressed() (Input_Pressed[KEY_LWIN] || Input_Pressed[KEY_RWIN])
#define Key_IsAltPressed() (Input_Pressed[KEY_LALT] || Input_Pressed[KEY_RALT])
#define Key_IsControlPressed() (Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])
#define Key_IsShiftPressed() (Input_Pressed[KEY_LSHIFT] || Input_Pressed[KEY_RSHIFT])
#define Key_IsWinPressed() (Input_Pressed[KEY_LWIN] || Input_Pressed[KEY_RWIN])
#define Key_IsAltPressed() (Input_Pressed[KEY_LALT] || Input_Pressed[KEY_RALT])
#define Key_IsCtrlPressed() (Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL])
#define Key_IsShiftPressed() (Input_Pressed[KEY_LSHIFT] || Input_Pressed[KEY_RSHIFT])
#ifdef CC_BUILD_DARWIN
/* macOS uses CMD instead of CTRL for clipboard and stuff */
#define Key_IsActionPressed() Key_IsWinPressed()
#else
#define Key_IsActionPressed() Key_IsControlPressed()
#define Key_IsActionPressed() Key_IsCtrlPressed()
#endif
/* Pressed state of each input button. Use Input_Set to change. */
@ -83,6 +86,7 @@ extern int Pointers_Count;
extern cc_bool Input_TapPlace, Input_HoldPlace;
/* Whether touch input is being used. */
extern cc_bool Input_TouchMode;
void Input_SetTouchMode(cc_bool enabled);
void Input_AddTouch(long id, int x, int y);
void Input_UpdateTouch(long id, int x, int y);

View File

@ -1005,10 +1005,8 @@ static struct CheckResourcesScreen {
static void CheckResourcesScreen_Yes(void* w, int idx) { FetchResourcesScreen_SetActive(); }
static void CheckResourcesScreen_Next(void* w, int idx) {
static const cc_string optionsTxt = String_FromConst("options.txt");
Http_ClearPending();
if (File_Exists(&optionsTxt)) {
if (Options_LoadResult != ReturnCode_FileNotFound) {
MainScreen_SetActive();
} else {
ChooseModeScreen_SetActive(true);

View File

@ -109,43 +109,47 @@ static void Json_ConsumeObject(struct JsonContext* ctx) {
char keyBuffer[STRING_SIZE];
cc_string value, oldKey = ctx->curKey;
int token;
ctx->depth++;
ctx->OnNewObject(ctx);
while (true) {
token = Json_ConsumeToken(ctx);
if (token == ',') continue;
if (token == '}') return;
if (token == '}') break;
if (token != '"') { ctx->failed = true; return; }
if (token != '"') { ctx->failed = true; break; }
String_InitArray(ctx->curKey, keyBuffer);
Json_ConsumeString(ctx, &ctx->curKey);
token = Json_ConsumeToken(ctx);
if (token != ':') { ctx->failed = true; return; }
if (token != ':') { ctx->failed = true; break; }
token = Json_ConsumeToken(ctx);
if (token == TOKEN_NONE) { ctx->failed = true; return; }
if (token == TOKEN_NONE) { ctx->failed = true; break; }
value = Json_ConsumeValue(token, ctx);
ctx->OnValue(ctx, &value);
ctx->curKey = oldKey;
}
ctx->depth--;
}
static void Json_ConsumeArray(struct JsonContext* ctx) {
cc_string value;
int token;
ctx->depth++;
ctx->OnNewArray(ctx);
while (true) {
token = Json_ConsumeToken(ctx);
if (token == ',') continue;
if (token == ']') return;
if (token == ']') break;
if (token == TOKEN_NONE) { ctx->failed = true; return; }
if (token == TOKEN_NONE) { ctx->failed = true; break; }
value = Json_ConsumeValue(token, ctx);
ctx->OnValue(ctx, &value);
}
ctx->depth--;
}
static cc_string Json_ConsumeValue(int token, struct JsonContext* ctx) {
@ -169,6 +173,7 @@ void Json_Init(struct JsonContext* ctx, STRING_REF char* str, int len) {
ctx->left = len;
ctx->failed = false;
ctx->curKey = String_Empty;
ctx->depth = 0;
ctx->OnNewArray = Json_NullOnNew;
ctx->OnNewObject = Json_NullOnNew;
@ -421,12 +426,19 @@ void FetchServerTask_Run(const cc_string* hash) {
*#########################################################################################################################*/
struct FetchServersData FetchServersTask;
static void FetchServersTask_Count(struct JsonContext* ctx) {
/* JSON is expected in this format: */
/* { "servers" : (depth = 1) */
/* [ (depth = 2) */
/* { server1 }, (depth = 3) */
/* { server2 }, (depth = 3) */
/* ... */
if (ctx->depth != 3) return;
FetchServersTask.numServers++;
}
static void FetchServersTask_Next(struct JsonContext* ctx) {
if (ctx->depth != 3) return;
curServer++;
if (curServer < FetchServersTask.servers) return;
ServerInfo_Init(curServer);
}
@ -440,8 +452,7 @@ static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) {
FetchServersTask.servers = NULL;
FetchServersTask.orders = NULL;
/* -1 because servers is surrounded by a { */
FetchServersTask.numServers = -1;
FetchServersTask.numServers = 0;
Json_Handle(data, len, NULL, NULL, FetchServersTask_Count);
count = FetchServersTask.numServers;
@ -449,8 +460,7 @@ static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) {
FetchServersTask.servers = (struct ServerInfo*)Mem_Alloc(count, sizeof(struct ServerInfo), "servers list");
FetchServersTask.orders = (cc_uint16*)Mem_Alloc(count, 2, "servers order");
/* -2 because servers is surrounded by a { */
curServer = FetchServersTask.servers - 2;
curServer = FetchServersTask.servers - 1;
Json_Handle(data, len, ServerInfo_Parse, NULL, FetchServersTask_Next);
}

View File

@ -15,6 +15,7 @@ struct JsonContext {
int left; /* Number of characters left to be inspected. */
cc_bool failed; /* Whether there was an error parsing the JSON. */
cc_string curKey; /* Key/Name of current member */
int depth; /* Object/Array depth (e.g. { { { is depth 3 */
JsonOnNew OnNewArray; /* Invoked when start of an array is read. */
JsonOnNew OnNewObject; /* Invoked when start of an object is read. */

View File

@ -438,13 +438,16 @@ static void LInput_Unselect(void* widget, int idx) {
Window_CloseKeyboard();
}
static void LInput_CopyFromClipboard(cc_string* text, void* widget) {
struct LInput* w = (struct LInput*)widget;
String_UNSAFE_TrimStart(text);
String_UNSAFE_TrimEnd(text);
static void LInput_CopyFromClipboard(struct LInput* w) {
cc_string text; char textBuffer[2048];
String_InitArray(text, textBuffer);
if (w->ClipboardFilter) w->ClipboardFilter(text);
LInput_AppendString(w, text);
Clipboard_GetText(&text);
String_UNSAFE_TrimStart(&text);
String_UNSAFE_TrimEnd(&text);
if (w->ClipboardFilter) w->ClipboardFilter(&text);
LInput_AppendString(w, &text);
}
static void LInput_KeyDown(void* widget, int key, cc_bool was) {
@ -453,10 +456,10 @@ static void LInput_KeyDown(void* widget, int key, cc_bool was) {
LInput_Backspace(w);
} else if (key == KEY_DELETE) {
LInput_Delete(w);
} else if (key == 'C' && Key_IsActionPressed()) {
} else if (key == INPUT_CLIPBOARD_COPY) {
if (w->text.length) Clipboard_SetText(&w->text);
} else if (key == 'V' && Key_IsActionPressed()) {
Clipboard_RequestText(LInput_CopyFromClipboard, w);
} else if (key == INPUT_CLIPBOARD_PASTE) {
LInput_CopyFromClipboard(w);
} else if (key == KEY_ESCAPE) {
LInput_Clear(w);
} else if (key == KEY_LEFT) {

View File

@ -9,6 +9,7 @@
struct StringsBuffer Options;
static struct StringsBuffer changedOpts;
cc_result Options_LoadResult;
void Options_Free(void) {
StringsBuffer_Clear(&Options);
@ -35,8 +36,8 @@ static cc_bool Options_LoadFilter(const cc_string* entry) {
void Options_Load(void) {
/* Increase from max 512 to 2048 per entry */
StringsBuffer_SetLengthBits(&Options, 11);
EntryList_Load(&Options, "options-default.txt", '=', NULL);
EntryList_Load(&Options, "options.txt", '=', NULL);
Options_LoadResult = EntryList_Load(&Options, "options-default.txt", '=', NULL);
Options_LoadResult = EntryList_Load(&Options, "options.txt", '=', NULL);
}
void Options_Reload(void) {
@ -52,7 +53,7 @@ void Options_Reload(void) {
StringsBuffer_Remove(&Options, i);
}
/* Load only options which have not changed */
EntryList_Load(&Options, "options.txt", '=', Options_LoadFilter);
Options_LoadResult = EntryList_Load(&Options, "options.txt", '=', Options_LoadFilter);
}
static void SaveOptions(void) {

View File

@ -72,6 +72,7 @@
#define OPT_TOUCH_BUTTONS "gui-touchbuttons"
#define OPT_TOUCH_SCALE "gui-touchscale"
#define OPT_HTTP_ONLY "http-no-https"
#define OPT_RAW_INPUT "win-raw-input"
#define LOPT_SESSION "launcher-session"
#define LOPT_USERNAME "launcher-cc-username"
@ -87,6 +88,7 @@
struct StringsBuffer;
extern struct StringsBuffer Options;
extern cc_result Options_LoadResult;
/* Frees any memory allocated in storing options. */
void Options_Free(void);

View File

@ -357,48 +357,68 @@ int File_Exists(const cc_string* path) {
return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
}
static cc_result Directory_EnumCore(const cc_string* dirPath, const cc_string* file, DWORD attribs,
void* obj, Directory_EnumCallback callback) {
cc_string path; char pathBuffer[MAX_PATH + 10];
/* ignore . and .. entry */
if (file->length == 1 && file->buffer[0] == '.') return 0;
if (file->length == 2 && file->buffer[0] == '.' && file->buffer[1] == '.') return 0;
String_InitArray(path, pathBuffer);
String_Format2(&path, "%s/%s", dirPath, file);
if (attribs & FILE_ATTRIBUTE_DIRECTORY) return Directory_Enum(&path, obj, callback);
callback(&path, obj);
return 0;
}
cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) {
cc_string path; char pathBuffer[MAX_PATH + 10];
WCHAR str[NATIVE_STR_LEN];
WCHAR* src;
WIN32_FIND_DATAW entry;
WIN32_FIND_DATAW eW;
WIN32_FIND_DATAA eA;
int i, ansi = false;
HANDLE find;
cc_result res;
int i;
/* Need to append \* to search for files in directory */
String_InitArray(path, pathBuffer);
String_Format1(&path, "%s\\*", dirPath);
Platform_EncodeUtf16(str, &path);
find = FindFirstFileW(str, &entry);
if (find == INVALID_HANDLE_VALUE) return GetLastError();
find = FindFirstFileW(str, &eW);
if (!find || find == INVALID_HANDLE_VALUE) {
if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
ansi = true;
do {
path.length = 0;
String_Format1(&path, "%s/", dirPath);
/* Windows 9x does not support W API functions */
Platform_Utf16ToAnsi(str);
find = FindFirstFileA((LPCSTR)str, &eA);
if (find == INVALID_HANDLE_VALUE) return GetLastError();
}
/* ignore . and .. entry */
src = entry.cFileName;
if (src[0] == '.' && src[1] == '\0') continue;
if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue;
for (i = 0; i < MAX_PATH && src[i]; i++) {
/* TODO: UTF16 to codepoint conversion */
String_Append(&path, Convert_CodepointToCP437(src[i]));
}
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
res = Directory_Enum(&path, obj, callback);
if (res) { FindClose(find); return res; }
} else {
callback(&path, obj);
}
} while (FindNextFileW(find, &entry));
if (ansi) {
do {
path.length = 0;
for (i = 0; i < MAX_PATH && eA.cFileName[i]; i++) {
String_Append(&path, Convert_CodepointToCP437(eA.cFileName[i]));
}
if ((res = Directory_EnumCore(dirPath, &path, eA.dwFileAttributes, obj, callback))) return res;
} while (FindNextFileA(find, &eA));
} else {
do {
path.length = 0;
for (i = 0; i < MAX_PATH && eW.cFileName[i]; i++) {
/* TODO: UTF16 to codepoint conversion */
String_Append(&path, Convert_CodepointToCP437(eW.cFileName[i]));
}
if ((res = Directory_EnumCore(dirPath, &path, eW.dwFileAttributes, obj, callback))) return res;
} while (FindNextFileW(find, &eW));
}
res = GetLastError(); /* return code from FindNextFile */
FindClose(find);
return res == ERROR_NO_MORE_FILES ? 0 : GetLastError();
return res == ERROR_NO_MORE_FILES ? 0 : res;
}
static cc_result DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) {
@ -1021,19 +1041,40 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
*-----------------------------------------------------Process/Module------------------------------------------------------*
*#########################################################################################################################*/
#if defined CC_BUILD_WIN
static cc_result Process_RawStart(WCHAR* path, WCHAR* args) {
STARTUPINFOW si = { 0 };
static cc_result Process_RawGetExePath(WCHAR* path, int* len) {
*len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN);
return *len ? 0 : GetLastError();
}
cc_result Process_StartGame(const cc_string* args) {
WCHAR path[NATIVE_STR_LEN + 1], raw[NATIVE_STR_LEN];
cc_string argv; char argvBuffer[NATIVE_STR_LEN];
STARTUPINFOW si = { 0 };
PROCESS_INFORMATION pi = { 0 };
cc_result res;
int len;
Process_RawGetExePath(path, &len);
path[len] = '\0';
si.cb = sizeof(STARTUPINFOW);
String_InitArray(argv, argvBuffer);
/* Game doesn't actually care about argv[0] */
String_Format1(&argv, "cc %s", args);
String_UNSAFE_TrimEnd(&argv);
Platform_EncodeUtf16(raw, &argv);
if (CreateProcessW(path, args, NULL, NULL,
if (CreateProcessW(path, raw, NULL, NULL,
false, 0, NULL, NULL, &si, &pi)) goto success;
//if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
//Platform_Utf16ToAnsi(path);
//if (CreateProcessA((LPCSTR)path, args, NULL, NULL,
// false, 0, NULL, NULL, &si, &pi)) goto success;
/* Windows 9x does not support W API functions */
len = GetModuleFileNameA(NULL, (LPSTR)path, NATIVE_STR_LEN);
((char*)path)[len] = '\0';
Platform_Utf16ToAnsi(raw);
if (CreateProcessA((LPCSTR)path, (LPSTR)raw, NULL, NULL,
false, 0, NULL, NULL, &si, &pi)) goto success;
return GetLastError();
success:
@ -1043,27 +1084,7 @@ success:
return 0;
}
static cc_result Process_RawGetExePath(WCHAR* path, int* len) {
*len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN);
return *len ? 0 : GetLastError();
}
cc_result Process_StartGame(const cc_string* args) {
cc_string argv; char argvBuffer[NATIVE_STR_LEN];
WCHAR raw[NATIVE_STR_LEN], path[NATIVE_STR_LEN + 1];
int len;
cc_result res = Process_RawGetExePath(path, &len);
if (res) return res;
path[len] = '\0';
String_InitArray(argv, argvBuffer);
String_Format1(&argv, "ClassiCube.exe %s", args);
Platform_EncodeUtf16(raw, &argv);
return Process_RawStart(path, raw);
}
void Process_Exit(cc_result code) { ExitProcess(code); }
void Process_StartOpen(const cc_string* args) {
WCHAR str[NATIVE_STR_LEN];
Platform_EncodeUtf16(str, args);
@ -1272,7 +1293,6 @@ cc_bool Updater_Clean(void) {
cc_result Updater_Start(const char** action) {
WCHAR path[NATIVE_STR_LEN + 1];
WCHAR args[2] = { 'a', '\0' }; /* don't actually care about arguments */
cc_result res;
int len = 0;
@ -1286,7 +1306,7 @@ cc_result Updater_Start(const char** action) {
if (!MoveFileExW(UPDATE_SRC, path, MOVEFILE_REPLACE_EXISTING)) return GetLastError();
*action = "Restarting game";
return Process_RawStart(path, args);
return Process_StartGame(&String_Empty);
}
cc_result Updater_GetBuildTime(cc_uint64* timestamp) {
@ -1343,10 +1363,7 @@ cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; }
cc_result Updater_GetBuildTime(cc_uint64* t) {
JNIEnv* env;
JavaGetCurrentEnv(env);
/* https://developer.android.com/reference/java/io/File#lastModified() */
/* lastModified is returned in milliseconds */
*t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL) / 1000;
*t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL);
return 0;
}
#endif
@ -1565,7 +1582,7 @@ void Platform_Utf16ToAnsi(void* data) {
WCHAR* src = (WCHAR*)data;
char* dst = (char*)data;
while (*src) { *dst++ = *src++; }
while (*src) { *dst++ = (char)(*src++); }
*dst = '\0';
}

View File

@ -737,9 +737,12 @@ static void Classic_Reset(void) {
}
static void Classic_Tick(void) {
struct Entity* p = &LocalPlayer_Instance.Base;
struct LocalPlayer* p = &LocalPlayer_Instance;
struct Entity* e = &LocalPlayer_Instance.Base;
if (!classic_receivedFirstPos) return;
Classic_WritePosition(p->Position, p->Yaw, p->Pitch);
/* Report end position of each physics tick, rather than current position */
/* (otherwise can miss landing on a block then jumping off of it again) */
Classic_WritePosition(p->Interp.Next.Pos, e->Yaw, e->Pitch);
}

View File

@ -1448,7 +1448,7 @@ static int InventoryScreen_PointerDown(void* screen, int id, int x, int y) {
handled = Elem_HandlesPointerDown(table, id, x, y);
if (!handled || table->pendingClose) {
hotbar = Key_IsControlPressed() || Key_IsShiftPressed();
hotbar = Key_IsCtrlPressed() || Key_IsShiftPressed();
if (!hotbar) Gui_Remove((struct Screen*)s);
}
return TOUCH_TYPE_GUI;
@ -1467,7 +1467,7 @@ static int InventoryScreen_PointerMove(void* screen, int id, int x, int y) {
static int InventoryScreen_MouseScroll(void* screen, float delta) {
struct InventoryScreen* s = (struct InventoryScreen*)screen;
cc_bool hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed();
cc_bool hotbar = Key_IsAltPressed() || Key_IsCtrlPressed() || Key_IsShiftPressed();
if (hotbar) return false;
return Elem_HandlesMouseScroll(&s->table, delta);
}

View File

@ -222,7 +222,7 @@ int Convert_FromBase64(const char* src, int len, cc_uint8* dst) {
/*########################################################################################################################*
*--------------------------------------------------------EntryList--------------------------------------------------------*
*#########################################################################################################################*/
void EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter) {
cc_result EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter) {
cc_string entry; char entryBuffer[1024];
cc_string path;
cc_string key, value;
@ -236,8 +236,8 @@ void EntryList_Load(struct StringsBuffer* list, const char* file, char separator
maxLen = list->_lenMask ? list->_lenMask : STRINGSBUFFER_DEF_LEN_MASK;
res = Stream_OpenFile(&stream, &path);
if (res == ReturnCode_FileNotFound) return;
if (res) { Logger_SysWarn2(res, "opening", &path); return; }
if (res == ReturnCode_FileNotFound) return res;
if (res) { Logger_SysWarn2(res, "opening", &path); return res; }
/* ReadLine reads single byte at a time */
Stream_ReadonlyBuffered(&buffered, &stream, buffer, sizeof(buffer));
@ -274,10 +274,11 @@ void EntryList_Load(struct StringsBuffer* list, const char* file, char separator
res = stream.Close(&stream);
if (res) { Logger_SysWarn2(res, "closing", &path); }
return res;
}
void EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file) {
EntryList_Load(list, file, '\0', NULL);
cc_result EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file) {
return EntryList_Load(list, file, '\0', NULL);
}
void EntryList_Save(struct StringsBuffer* list, const char* file) {

View File

@ -59,10 +59,10 @@ typedef cc_bool (*EntryList_Filter)(const cc_string* entry);
/* Loads the entries from disc. */
/* NOTE: If separator is \0, does NOT check for duplicate keys when loading. */
/* filter can be used to optionally skip loading some entries from the file. */
CC_NOINLINE void EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter);
CC_NOINLINE cc_result EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter);
/* Shortcut for EntryList_Load with separator of \0 and filter of NULL */
/* NOTE: Does NOT check for duplicate keys */
CC_NOINLINE void EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file);
CC_NOINLINE cc_result EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file);
/* Saves the entries in the given list to disc. */
CC_NOINLINE void EntryList_Save(struct StringsBuffer* list, const char* file);
/* Removes the entry whose key caselessly equals the given key. */

View File

@ -1216,18 +1216,20 @@ static void InputWidget_EndKey(struct InputWidget* w) {
InputWidget_UpdateCaret(w);
}
static void InputWidget_CopyFromClipboard(cc_string* text, void* w) {
InputWidget_AppendText((struct InputWidget*)w, text);
static void InputWidget_CopyFromClipboard(struct InputWidget* w) {
cc_string text; char textBuffer[2048];
String_InitArray(text, textBuffer);
Clipboard_GetText(&text);
InputWidget_AppendText(w, &text);
}
static cc_bool InputWidget_OtherKey(struct InputWidget* w, int key) {
int maxChars = w->GetMaxLines() * INPUTWIDGET_LEN;
if (!Key_IsActionPressed()) return false;
if (key == 'V' && w->text.length < maxChars) {
Clipboard_RequestText(InputWidget_CopyFromClipboard, w);
if (key == INPUT_CLIPBOARD_PASTE && w->text.length < maxChars) {
InputWidget_CopyFromClipboard(w);
return true;
} else if (key == 'C') {
} else if (key == INPUT_CLIPBOARD_COPY) {
if (!w->text.length) return true;
Clipboard_SetText(&w->text);
return true;

View File

@ -14,17 +14,9 @@ struct _DisplayData DisplayInfo;
struct _WinData WindowInfo;
int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
#ifndef CC_BUILD_WEB
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
cc_string text; char textBuffer[2048];
String_InitArray(text, textBuffer);
Clipboard_GetText(&text);
callback(&text, obj);
}
#endif
int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
#define Display_CentreX(width) (DisplayInfo.X + (DisplayInfo.Width - width) / 2)
#define Display_CentreY(height) (DisplayInfo.Y + (DisplayInfo.Height - height) / 2)
#if defined CC_BUILD_IOS
/* iOS implements these functions in external interop_ios.m file */
@ -982,7 +974,9 @@ static void InitRawMouse(void) {
_getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData");
rawMouseSupported = _registerRawInput && _getRawInputData;
}
if (!rawMouseSupported) { Platform_LogConst("Raw input unsupported!"); return; }
rawMouseSupported &= Options_GetBool(OPT_RAW_INPUT, true);
if (!rawMouseSupported) { Platform_LogConst("## Raw input unsupported!"); return; }
rid.usUsagePage = 1; /* HID_USAGE_PAGE_GENERIC; */
rid.usUsage = 2; /* HID_USAGE_GENERIC_MOUSE; */
@ -2741,8 +2735,8 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) {
/*########################################################################################################################*
*-------------------------------------------------------Cocoa window------------------------------------------------------*
*#########################################################################################################################*/
#elif defined CC_BUILD_COCOA
/* NOTE: Mostly implemented in interop_cocoa.m */
#elif defined CC_BUILD_COCOA
/* NOTE: Mostly implemented in interop_cocoa.m */
#endif
@ -2920,7 +2914,7 @@ static const char* OnBeforeUnload(int type, const void* ev, void *data) {
return NULL;
}
static int MapNativeKey(int k) {
static int MapNativeKey(int k, int l) {
if (k >= '0' && k <= '9') return k;
if (k >= 'A' && k <= 'Z') return k;
if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); }
@ -2929,10 +2923,10 @@ static int MapNativeKey(int k) {
switch (k) {
case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
case DOM_VK_TAB: return KEY_TAB;
case DOM_VK_RETURN: return KEY_ENTER;
case DOM_VK_SHIFT: return KEY_LSHIFT;
case DOM_VK_CONTROL: return KEY_LCTRL;
case DOM_VK_ALT: return KEY_LALT;
case DOM_VK_RETURN: return l == DOM_KEY_LOCATION_NUMPAD ? KEY_KP_ENTER : KEY_ENTER;
case DOM_VK_SHIFT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RSHIFT : KEY_LSHIFT;
case DOM_VK_CONTROL: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RCTRL : KEY_LCTRL;
case DOM_VK_ALT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RALT : KEY_LALT;
case DOM_VK_PAUSE: return KEY_PAUSE;
case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK;
case DOM_VK_ESCAPE: return KEY_ESCAPE;
@ -2952,7 +2946,7 @@ static int MapNativeKey(int k) {
case DOM_VK_SEMICOLON: return KEY_SEMICOLON;
case DOM_VK_EQUALS: return KEY_EQUALS;
case DOM_VK_WIN: return KEY_LWIN;
case DOM_VK_WIN: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RWIN : KEY_LWIN;
case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
case DOM_VK_ADD: return KEY_KP_PLUS;
case DOM_VK_SUBTRACT: return KEY_KP_MINUS;
@ -2979,34 +2973,21 @@ static int MapNativeKey(int k) {
return KEY_NONE;
}
static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) {
int key = MapNativeKey(ev->keyCode);
if (ev->location == DOM_KEY_LOCATION_RIGHT) {
switch (key) {
case KEY_LALT: key = KEY_RALT; break;
case KEY_LCTRL: key = KEY_RCTRL; break;
case KEY_LSHIFT: key = KEY_RSHIFT; break;
case KEY_LWIN: key = KEY_RWIN; break;
}
}
else if (ev->location == DOM_KEY_LOCATION_NUMPAD) {
switch (key) {
case KEY_ENTER: key = KEY_KP_ENTER; break;
}
}
if (key) Input_Set(key, type == EMSCRIPTEN_EVENT_KEYDOWN);
static EM_BOOL OnKeyDown(int type, const EmscriptenKeyboardEvent* ev, void* data) {
int key = MapNativeKey(ev->keyCode, ev->location);
/* iOS safari still sends backspace key events, don't intercept those */
if (key == KEY_BACKSPACE && Input_TouchMode && keyboardOpen) return false;
if (key) Input_SetPressed(key);
DeferredEnableRawMouse();
if (!key) return false;
/* KeyUp always intercepted */
if (type != EMSCRIPTEN_EVENT_KEYDOWN) return true;
/* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */
/* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */
if (Key_IsAltPressed() || Key_IsWinPressed()) return true;
if (Key_IsControlPressed() && key != 'C' && key != 'V') return true;
/* NOTE: macOS uses Win (Command) key instead of Ctrl, have to account for that too */
if (Key_IsAltPressed()) return true;
if (Key_IsWinPressed()) return key != 'C' && key != 'V';
if (Key_IsCtrlPressed()) return key != 'C' && key != 'V';
/* Space needs special handling, as intercepting this prevents the ' ' key press event */
/* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */
@ -3019,6 +3000,13 @@ static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) {
(key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK);
}
static EM_BOOL OnKeyUp(int type, const EmscriptenKeyboardEvent* ev, void* data) {
int key = MapNativeKey(ev->keyCode, ev->location);
if (key) Input_SetReleased(key);
DeferredEnableRawMouse();
return key != KEY_NONE;
}
static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) {
char keyChar;
DeferredEnableRawMouse();
@ -3032,6 +3020,10 @@ static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* dat
/* have these intercepted key presses in its text buffer) */
if (Input_TouchMode && keyboardOpen) return false;
/* Safari on macOS still sends a keypress event, which must not be cancelled */
/* (otherwise copy/paste doesn't work, as it uses Win+C / Win+V) */
if (ev->metaKey) return false;
if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) {
Event_RaiseInt(&InputEvents.Press, keyChar);
}
@ -3056,8 +3048,8 @@ static void HookEvents(void) {
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnResize);
emscripten_set_beforeunload_callback( NULL, OnBeforeUnload);
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKey);
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKey);
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyDown);
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyUp);
emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyPress);
emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchStart);
@ -3101,19 +3093,24 @@ void Window_Init(void) {
EM_ASM(window.addEventListener('copy',
function(e) {
if (window.getSelection && window.getSelection().toString()) return;
if (window.cc_copyText) {
if (e.clipboardData) { e.clipboardData.setData('text/plain', window.cc_copyText); }
ccall('Window_RequestClipboardText', 'void');
if (!window.cc_copyText) return;
if (e.clipboardData) {
e.clipboardData.setData('text/plain', window.cc_copyText);
e.preventDefault();
window.cc_copyText = null;
}
}
window.cc_copyText = null;
});
);
/* Paste text (window.clipboardData is handled in Clipboard_RequestText instead) */
/* Paste text (window.clipboardData is handled in Clipboard_GetText instead) */
EM_ASM(window.addEventListener('paste',
function(e) {
var contents = e.clipboardData ? e.clipboardData.getData('text/plain') : "";
ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
if (e.clipboardData) {
var contents = e.clipboardData.getData('text/plain');
ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
}
});
);
@ -3124,8 +3121,7 @@ void Window_Init(void) {
return /iPhone|iPad|iPod/i.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints && navigator.maxTouchPoints > 2);
});
Input_TouchMode = is_ios || droid;
Pointers_Count = Input_TouchMode ? 0 : 1;
Input_SetTouchMode(is_ios || droid);
/* iOS shifts the whole webpage up when opening chat, which causes problems */
/* as the chat/send butons are positioned at the top of the canvas - they */
@ -3169,20 +3165,33 @@ void Window_SetTitle(const cc_string* title) {
EM_ASM_({ document.title = UTF8ToString($0); }, str);
}
static RequestClipboardCallback clipboard_func;
static void* clipboard_obj;
EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
cc_string str; char strBuffer[512];
if (!clipboard_func) return;
String_InitArray(str, strBuffer);
String_AppendUtf8(&str, src, String_CalcLen(src, 2048));
clipboard_func(&str, clipboard_obj);
clipboard_func = NULL;
static char pasteBuffer[512];
static cc_string pasteStr;
EMSCRIPTEN_KEEPALIVE void Window_RequestClipboardText(void) {
Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
}
void Clipboard_GetText(cc_string* value) { }
EMSCRIPTEN_KEEPALIVE void Window_StoreClipboardText(char* src) {
String_InitArray(pasteStr, pasteBuffer);
String_AppendUtf8(&pasteStr, src, String_CalcLen(src, 2048));
}
EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
Window_StoreClipboardText(src);
Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
}
void Clipboard_GetText(cc_string* value) {
/* For IE11, use window.clipboardData to get the clipboard */
EM_ASM_({
if (window.clipboardData) {
var contents = window.clipboardData.getData('Text');
ccall('Window_StoreClipboardText', 'void', ['string'], [contents]);
}
});
String_Copy(value, &pasteStr);
pasteStr.length = 0;
}
void Clipboard_SetText(const cc_string* value) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, value);
@ -3199,18 +3208,6 @@ void Clipboard_SetText(const cc_string* value) {
}, str);
}
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
clipboard_func = callback;
clipboard_obj = obj;
/* For IE11, use window.clipboardData to get the clipboard */
EM_ASM_({
if (!window.clipboardData) return;
var contents = window.clipboardData.getData('Text');
ccall('Window_GotClipboardText', 'void', ['string'], [contents]);
});
}
void Window_Show(void) { }
int Window_GetWindowState(void) {
@ -3672,7 +3669,8 @@ void Window_Init(void) {
JavaRegisterNatives(env, methods);
WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
Input_TouchMode = true;
Input_SetTouchMode(true);
DisplayInfo.Depth = 32;
DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL);
DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL);
@ -3820,13 +3818,13 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) {
Mem_Free(bmp->scan0);
}
void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
void Window_OpenKeyboard(const struct OpenKeyboardArgs* kArgs) {
JNIEnv* env;
jvalue args[2];
JavaGetCurrentEnv(env);
args[0].l = JavaMakeString(env, args->text);
args[1].i = args->type;
args[0].l = JavaMakeString(env, kArgs->text);
args[1].i = kArgs->type;
JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args);
(*env)->DeleteLocalRef(env, args[0].l);
}
@ -4214,12 +4212,11 @@ static XVisualInfo GLContext_SelectVisual(void) {
InitGraphicsMode(&mode);
GetAttribs(&mode, attribs, GLCONTEXT_DEFAULT_DEPTH);
if (!glXQueryVersion(win_display, &major, &minor)) {
Logger_Abort("glXQueryVersion failed");
}
screen = DefaultScreen(win_display);
if (major >= 1 && minor >= 3) {
if (!glXQueryVersion(win_display, &major, &minor)) {
Platform_LogConst("glXQueryVersion failed");
} else if (major >= 1 && minor >= 3) {
/* ChooseFBConfig returns an array of GLXFBConfig opaque structures */
fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount);
if (fbconfigs && fbcount) {

View File

@ -84,15 +84,14 @@ void Window_Create(int width, int height);
/* Sets the text of the titlebar above the window. */
CC_API void Window_SetTitle(const cc_string* title);
typedef void (*RequestClipboardCallback)(cc_string* value, void* obj);
/* Gets the text currently on the clipboard. */
/* NOTE: On most platforms this function can be called at any time. */
/* In web backend, can only be called during INPUT_CLIPBOARD_PASTE event. */
CC_API void Clipboard_GetText(cc_string* value);
/* Sets the text currently on the clipboard. */
/* NOTE: On most platforms this function can be called at any time. */
/* In web backend, can only be called during INPUT_CLIPBOARD_COPY event. */
CC_API void Clipboard_SetText(const cc_string* value);
/* Calls a callback function when text is retrieved from the clipboard. */
/* NOTE: On most platforms this just calls Clipboard_GetText. */
/* With emscripten however, the callback is instead called when a 'paste' event arrives. */
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj);
/* Makes the window visible and focussed on screen. */
void Window_Show(void);