Initial commit
This commit is contained in:
commit
e8f7ff45eb
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.classpath
|
||||||
|
.cproject
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
obj/
|
||||||
|
tmp/
|
||||||
|
proguard/
|
36
AndroidManifest.xml
Normal file
36
AndroidManifest.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.annimon.everlastingsummer"
|
||||||
|
android:installLocation="preferExternal"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0" >
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="8"
|
||||||
|
android:targetSdkVersion="666" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.annimon.everlastingsummer.MainActivity"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
|
android:label="@string/app_name" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.annimon.everlastingsummer.ViewActivity"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
|
android:label="@string/app_name" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
BIN
ic_launcher-web.png
Normal file
BIN
ic_launcher-web.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
20
proguard-project.txt
Normal file
20
proguard-project.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
14
project.properties
Normal file
14
project.properties
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system edit
|
||||||
|
# "ant.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
#
|
||||||
|
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||||
|
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-19
|
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
32
res/layout/main.xml
Normal file
32
res/layout/main.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:src="@android:color/black" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="false"
|
||||||
|
android:background="#7f000000"
|
||||||
|
android:textColor="#FFDEC88C"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:gravity="start|top"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
5
res/values/strings.xml
Normal file
5
res/values/strings.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">Everlasting Summer RpyPlayer</string>
|
||||||
|
|
||||||
|
</resources>
|
45
src/com/annimon/everlastingsummer/FadeInfo.java
Normal file
45
src/com/annimon/everlastingsummer/FadeInfo.java
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Информация о плавном переходе состояния воспроизведения.
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class FadeInfo {
|
||||||
|
|
||||||
|
private boolean in, out;
|
||||||
|
private double duration;
|
||||||
|
|
||||||
|
public FadeInfo() {
|
||||||
|
this(false, false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FadeInfo(boolean in, boolean out, double duration) {
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIn() {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIn(boolean in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOut() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOut(boolean out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(double duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
}
|
49
src/com/annimon/everlastingsummer/IOUtil.java
Normal file
49
src/com/annimon/everlastingsummer/IOUtil.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс для работы с файловой системой.
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class IOUtil {
|
||||||
|
|
||||||
|
private static String SDCARD = Environment.getExternalStorageDirectory().getPath();
|
||||||
|
private static String ES = SDCARD + "/everlastingsummer/";
|
||||||
|
|
||||||
|
public static Bitmap readBitmap(String file) throws IOException {
|
||||||
|
final InputStream is = open(file);
|
||||||
|
final Bitmap result = BitmapFactory.decodeStream(is);
|
||||||
|
is.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileDescriptor getFD(String file) throws IOException {
|
||||||
|
// TODO: is = ...; result = ..getFD; is.close; return result
|
||||||
|
return new FileInputStream(ES + file).getFD();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InputStream open(String file) throws IOException {
|
||||||
|
return new FileInputStream(ES + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readContents(InputStream is) throws IOException {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||||
|
String line;
|
||||||
|
while ( (line = reader.readLine()) != null ) {
|
||||||
|
sb.append(line);
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
190
src/com/annimon/everlastingsummer/Lexer.java
Normal file
190
src/com/annimon/everlastingsummer/Lexer.java
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class Lexer {
|
||||||
|
|
||||||
|
public static List<Token> tokenize(String input) {
|
||||||
|
return new Lexer().process(input).getTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Token> tokens;
|
||||||
|
private final StringBuilder buffer;
|
||||||
|
|
||||||
|
private static final String OPERATOR_CHARS = "=+-()[]$";
|
||||||
|
private static final TokenType[] OPERATOR_TYPES = new TokenType[] {
|
||||||
|
TokenType.EQ,
|
||||||
|
TokenType.PLUS, TokenType.MINUS,
|
||||||
|
TokenType.LPAREN, TokenType.RPAREN, TokenType.LBRACKET, TokenType.RBRACKET,
|
||||||
|
TokenType.COMMAND
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String[] KEYWORDS = {
|
||||||
|
"play", "stop",
|
||||||
|
"music", "ambience", "sound", "sound_loop",
|
||||||
|
"fadein", "fadeout",
|
||||||
|
|
||||||
|
"scene", "anim", "bg", "cg",
|
||||||
|
"at",
|
||||||
|
"window", "hide", "show",
|
||||||
|
"with",
|
||||||
|
"return",
|
||||||
|
|
||||||
|
"renpy.pause", "persistent.sprite_time",
|
||||||
|
"prolog_time", "day_time", "sunset_time", "night_time"
|
||||||
|
};
|
||||||
|
private static final TokenType[] KEYWORD_TYPES = new TokenType[] {
|
||||||
|
TokenType.PLAY, TokenType.STOP,
|
||||||
|
TokenType.MUSIC, TokenType.AMBIENCE, TokenType.SOUND, TokenType.SOUNDLOOP,
|
||||||
|
TokenType.FADEIN, TokenType.FADEOUT,
|
||||||
|
|
||||||
|
TokenType.SCENE, TokenType.ANIM, TokenType.BG, TokenType.CG,
|
||||||
|
TokenType.AT,
|
||||||
|
TokenType.WINDOW, TokenType.HIDE, TokenType.SHOW,
|
||||||
|
TokenType.WITH,
|
||||||
|
TokenType.RETURN,
|
||||||
|
|
||||||
|
TokenType.RENPY_PAUSE, TokenType.PERSISTENT_SPRITE_TIME,
|
||||||
|
TokenType.PROLOG_TIME, TokenType.DAY_TIME, TokenType.SUNSET_TIME, TokenType.NIGHT_TIME
|
||||||
|
};
|
||||||
|
|
||||||
|
private TokenizeState state;
|
||||||
|
private int pos;
|
||||||
|
|
||||||
|
private enum TokenizeState {
|
||||||
|
DEFAULT, NUMBER, OPERATOR, WORD, TEXT, COMMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lexer() {
|
||||||
|
tokens = new LinkedList<Token>();
|
||||||
|
buffer = new StringBuilder();
|
||||||
|
state = TokenizeState.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Token> getTokens() {
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Lexer process(String input) {
|
||||||
|
final int length = input.length();
|
||||||
|
for (pos = 0; pos < length; pos++) {
|
||||||
|
tokenize(input.charAt(pos));
|
||||||
|
}
|
||||||
|
tokenize('\0');// EOF
|
||||||
|
addToken(TokenType.EOF, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenize(char ch) {
|
||||||
|
switch (state) {
|
||||||
|
case DEFAULT: tokenizeDefault(ch); break;
|
||||||
|
case WORD: tokenizeWord(ch); break;
|
||||||
|
case NUMBER: tokenizeNumber(ch); break;
|
||||||
|
case OPERATOR: tokenizeOperator(ch); break;
|
||||||
|
case TEXT: tokenizeText(ch); break;
|
||||||
|
case COMMENT: tokenizeComment(ch); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenizeDefault(char ch) {
|
||||||
|
if (Character.isLetter(ch)) {
|
||||||
|
// Слово (ключевое слово или команда)
|
||||||
|
buffer.append(ch);
|
||||||
|
state = TokenizeState.WORD;
|
||||||
|
} else if (Character.isDigit(ch)) {
|
||||||
|
// Число
|
||||||
|
buffer.append(ch);
|
||||||
|
state = TokenizeState.NUMBER;
|
||||||
|
} else if (ch == '"') {
|
||||||
|
// Текст в "кавычках"
|
||||||
|
state = TokenizeState.TEXT;
|
||||||
|
} else if (ch == '#') {
|
||||||
|
clearBuffer();
|
||||||
|
state = TokenizeState.COMMENT;
|
||||||
|
} else {
|
||||||
|
// Операторы и спецсимволы
|
||||||
|
tokenizeOperator(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenizeWord(char ch) {
|
||||||
|
if (ch == ':') {
|
||||||
|
addToken(TokenType.LABEL, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Character.isLetterOrDigit(ch) || (ch == '_') || (ch == '.')) {
|
||||||
|
buffer.append(ch);
|
||||||
|
} else {
|
||||||
|
final String word = buffer.toString();
|
||||||
|
for (int i = 0; i < KEYWORDS.length; i++) {
|
||||||
|
if (KEYWORDS[i].equalsIgnoreCase(word)) {
|
||||||
|
addToken(KEYWORD_TYPES[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addToken(TokenType.WORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenizeNumber(char ch) {
|
||||||
|
// Целое или вещественное число.
|
||||||
|
if (ch == '.') {
|
||||||
|
// Пропускаем десятичные точки, если они уже были в числе.
|
||||||
|
if (buffer.indexOf(".") == -1) buffer.append(ch);
|
||||||
|
} else if (Character.isDigit(ch)) {
|
||||||
|
buffer.append(ch);
|
||||||
|
} else {
|
||||||
|
addToken(TokenType.NUMBER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenizeOperator(char ch) {
|
||||||
|
final int index = OPERATOR_CHARS.indexOf(ch);
|
||||||
|
if (index != -1) {
|
||||||
|
addToken(OPERATOR_TYPES[index], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenizeText(char ch) {
|
||||||
|
if (ch == '"') {
|
||||||
|
final int len = buffer.length();
|
||||||
|
// Добавляем токен, если не было экранирования символа кавычки.
|
||||||
|
if (len == 0 ||
|
||||||
|
( (len > 0) && (buffer.charAt(len - 1) != '\\') )) {
|
||||||
|
addToken(TokenType.TEXT, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Экранируем символ кавычки.
|
||||||
|
if (len > 0) {
|
||||||
|
buffer.setCharAt(len - 1, '\"');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.append(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tokenizeComment(char ch) {
|
||||||
|
if (ch == '\n' || ch == '\r') {
|
||||||
|
state = TokenizeState.DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToken(TokenType type) {
|
||||||
|
addToken(type, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToken(TokenType type, boolean reprocessLastChar) {
|
||||||
|
tokens.add(new Token(buffer.toString(), type));
|
||||||
|
clearBuffer();
|
||||||
|
if (reprocessLastChar) pos--;
|
||||||
|
state = TokenizeState.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearBuffer() {
|
||||||
|
buffer.setLength(0);
|
||||||
|
}
|
||||||
|
}
|
43
src/com/annimon/everlastingsummer/MainActivity.java
Normal file
43
src/com/annimon/everlastingsummer/MainActivity.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import android.app.ListActivity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Экран выбора сценариев из папки assets.
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class MainActivity extends ListActivity {
|
||||||
|
|
||||||
|
private String[] scripts;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
try {
|
||||||
|
scripts = getAssets().list(PathResolver.SCRIPT_ASSETS);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
scripts = null;
|
||||||
|
}
|
||||||
|
if (scripts == null || scripts.length == 0) {
|
||||||
|
Toast.makeText(this, "Нет скриптов в папке " + PathResolver.SCRIPT_ASSETS,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, scripts));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onListItemClick(ListView l, View v, int index, long id) {
|
||||||
|
final Intent intent = new Intent(this, ViewActivity.class);
|
||||||
|
intent.putExtra(ViewActivity.EXTRA_NAME, scripts[index]);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
262
src/com/annimon/everlastingsummer/Parser.java
Normal file
262
src/com/annimon/everlastingsummer/Parser.java
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class Parser {
|
||||||
|
|
||||||
|
private static final Token EOF = new Token("", TokenType.EOF);
|
||||||
|
|
||||||
|
private static Parser instance;
|
||||||
|
|
||||||
|
public static Parser parse(List<Token> tokens) {
|
||||||
|
instance = new Parser(tokens);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Parser getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final List<Token> tokens;
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public Parser(List<Token> tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void next() {
|
||||||
|
// Команды разделяются на терминальные и нетерминальные.
|
||||||
|
// Нетерминальные подготавливают сцену к выводу.
|
||||||
|
// Терминальные выводят всё на экран и ожидают следующего вызова.
|
||||||
|
boolean terminal = false;
|
||||||
|
int counter = 0;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
terminal = statement();
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
Log.e("Parser", re.getMessage(), re);
|
||||||
|
}
|
||||||
|
// антизацикливание
|
||||||
|
counter++;
|
||||||
|
if (counter >= 1000) {
|
||||||
|
position++;
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
} while (!terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean statement() {
|
||||||
|
// http://www.renpy.org/wiki/renpy/doc/reference/The_Ren%27Py_Language#Grammar_Rules
|
||||||
|
final Token token = get(0);
|
||||||
|
if (match(token, TokenType.COMMAND)) return command();
|
||||||
|
if (match(token, TokenType.SCENE)) return scene();
|
||||||
|
if (match(token, TokenType.PLAY)) return play();
|
||||||
|
if (match(token, TokenType.STOP)) return stop();
|
||||||
|
if (match(token, TokenType.SHOW)) return show();
|
||||||
|
|
||||||
|
// Текст с именем автора реплики.
|
||||||
|
if (lookMatch(1, TokenType.TEXT) && match(token, TokenType.WORD)) {
|
||||||
|
final String whoid = token.getText();
|
||||||
|
ViewActivity.getInstance().text(whoid, consume(TokenType.TEXT).getText());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обычный текст.
|
||||||
|
if (match(token, TokenType.TEXT)) {
|
||||||
|
ViewActivity.getInstance().text(token.getText());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(token, TokenType.RETURN) || match(token, TokenType.EOF)) {
|
||||||
|
ViewActivity.getInstance().finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(token, TokenType.WINDOW)) {
|
||||||
|
if (match(TokenType.SHOW))
|
||||||
|
ViewActivity.getInstance().windowShow();
|
||||||
|
else if (match(TokenType.HIDE))
|
||||||
|
ViewActivity.getInstance().windowHide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(matchWithEffect())) return false;
|
||||||
|
|
||||||
|
position++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Парсинг команд на языке Python. Начинаются на $.
|
||||||
|
* Поддерживается только самое нужное, весь питон я вам не интерпретирую.
|
||||||
|
*/
|
||||||
|
private boolean command() {
|
||||||
|
final Token token = get(0);
|
||||||
|
|
||||||
|
if (match(token, TokenType.RENPY_PAUSE)) {
|
||||||
|
consume(TokenType.LPAREN);
|
||||||
|
final double pause = consumeDouble();
|
||||||
|
ViewActivity.getInstance().pause((int)(1000 * pause));
|
||||||
|
consume(TokenType.RPAREN);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(token, TokenType.PERSISTENT_SPRITE_TIME)) {
|
||||||
|
consume(TokenType.EQ);
|
||||||
|
consume(TokenType.TEXT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(token, TokenType.PROLOG_TIME) ||
|
||||||
|
match(token, TokenType.DAY_TIME) ||
|
||||||
|
match(token, TokenType.SUNSET_TIME) ||
|
||||||
|
match(token, TokenType.NIGHT_TIME)) {
|
||||||
|
consume(TokenType.LPAREN);
|
||||||
|
consume(TokenType.RPAREN);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scene() {
|
||||||
|
String type;
|
||||||
|
if (match(TokenType.BG)) type = "bg";
|
||||||
|
else if (match(TokenType.CG)) type = "cg";
|
||||||
|
else if (match(TokenType.ANIM)) type = "anim";
|
||||||
|
else type = "";
|
||||||
|
|
||||||
|
final String name = consume(TokenType.WORD).getText();
|
||||||
|
final String effect = matchWithEffect();
|
||||||
|
ViewActivity.getInstance().background(type, name, effect);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean show() {
|
||||||
|
final String whoid = consume(TokenType.WORD).getText();
|
||||||
|
final String emotion = lookMatch(0, TokenType.WORD) ? consume(TokenType.WORD).getText() : "";
|
||||||
|
final String cloth = lookMatch(0, TokenType.WORD) ? consume(TokenType.WORD).getText() : "";
|
||||||
|
final String distance = lookMatch(0, TokenType.WORD) ? consume(TokenType.WORD).getText() : "";
|
||||||
|
// Положение (left, cleft, ...)
|
||||||
|
String position = "";
|
||||||
|
if (match(TokenType.AT)) {
|
||||||
|
position = consume(TokenType.WORD).getText();
|
||||||
|
}
|
||||||
|
matchWithEffect();
|
||||||
|
ViewActivity.getInstance().sprite(whoid, emotion, cloth, distance, position);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean play() {
|
||||||
|
if (match(TokenType.MUSIC)) return playMusic();
|
||||||
|
if (match(TokenType.AMBIENCE)) return playAmbience();
|
||||||
|
if (lookMatch(0, TokenType.SOUND) || lookMatch(0, TokenType.SOUNDLOOP)) {
|
||||||
|
return playSound();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean playMusic() {
|
||||||
|
consume(TokenType.WORD);
|
||||||
|
consume(TokenType.LBRACKET);
|
||||||
|
final String name = consume(TokenType.TEXT).getText();
|
||||||
|
consume(TokenType.RBRACKET);
|
||||||
|
|
||||||
|
final FadeInfo fade = matchFade();
|
||||||
|
ViewActivity.getInstance().music(name, fade);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean playSound() {
|
||||||
|
boolean loop = false;
|
||||||
|
if (match(TokenType.SOUND)) loop = false;
|
||||||
|
else if (match(TokenType.SOUNDLOOP)) loop = true;
|
||||||
|
|
||||||
|
final String name = consume(TokenType.WORD).getText();
|
||||||
|
final FadeInfo fade = matchFade();
|
||||||
|
ViewActivity.getInstance().sound(name, loop, fade);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean playAmbience() {
|
||||||
|
// Ambient не реализован, но парсится.
|
||||||
|
final String name = consume(TokenType.WORD).getText();
|
||||||
|
final FadeInfo fade = matchFade();
|
||||||
|
// ViewActivity.getInstance().sound(name, loop, fade);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean stop() {
|
||||||
|
if (match(TokenType.MUSIC)) {
|
||||||
|
final FadeInfo fade = matchFade();
|
||||||
|
ViewActivity.getInstance().stopMusic(fade);
|
||||||
|
}
|
||||||
|
if (match(TokenType.SOUND) || match(TokenType.SOUNDLOOP)) {
|
||||||
|
final FadeInfo fade = matchFade();
|
||||||
|
ViewActivity.getInstance().stopSound(fade);
|
||||||
|
}
|
||||||
|
if (match(TokenType.AMBIENCE)) {
|
||||||
|
final FadeInfo fade = matchFade();
|
||||||
|
// ViewActivity.getInstance().stopMusic(fade);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double consumeDouble() {
|
||||||
|
return Double.parseDouble(consume(TokenType.NUMBER).getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private FadeInfo matchFade() {
|
||||||
|
final FadeInfo result = new FadeInfo();
|
||||||
|
if (match(TokenType.FADEIN)) {
|
||||||
|
result.setIn(true);
|
||||||
|
result.setDuration(consumeDouble());
|
||||||
|
} else if (match(TokenType.FADEOUT)) {
|
||||||
|
result.setOut(true);
|
||||||
|
result.setDuration(consumeDouble());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String matchWithEffect() {
|
||||||
|
if (match(TokenType.WITH)) {
|
||||||
|
return consume(TokenType.WORD).getText();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean match(TokenType type) {
|
||||||
|
if (get(0).getType() != type) return false;
|
||||||
|
position++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean match(Token token, TokenType type) {
|
||||||
|
if (type != token.getType()) return false;
|
||||||
|
position++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token consume(TokenType type) {
|
||||||
|
if (get(0).getType() != type) throw new RuntimeException("Ожидался токен " + type + ".");
|
||||||
|
return tokens.get(position++);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean lookMatch(int pos, TokenType type) {
|
||||||
|
return (type == get(pos).getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token get(int offset) {
|
||||||
|
if (position + offset >= tokens.size()) return EOF;
|
||||||
|
return tokens.get(position + offset);
|
||||||
|
}
|
||||||
|
}
|
61
src/com/annimon/everlastingsummer/PathResolver.java
Normal file
61
src/com/annimon/everlastingsummer/PathResolver.java
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Корректировщик путей к ресурсам.
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class PathResolver {
|
||||||
|
|
||||||
|
public static final String SCRIPT_ASSETS = "scripts";
|
||||||
|
private static final String SPRITE = "sprites/";
|
||||||
|
private static final String MUSIC = "music/";
|
||||||
|
private static final String SOUND = "sfx/";
|
||||||
|
|
||||||
|
private static final String PNG = ".png";
|
||||||
|
private static final String JPG = ".jpg";
|
||||||
|
private static final String OGG = ".ogg";
|
||||||
|
|
||||||
|
public static String script(String name) {
|
||||||
|
return SCRIPT_ASSETS + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sprite(String whoid, String emotion, String cloth, String distance) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(SPRITE);
|
||||||
|
sb.append(TextUtils.isEmpty(distance) ? "normal" : distance).append('/');
|
||||||
|
sb.append(whoid.toLowerCase(Locale.ENGLISH)).append('/');
|
||||||
|
sb.append(emotion);
|
||||||
|
sb.append(cloth);
|
||||||
|
sb.append(PNG);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String background(String type, String name) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(type.toLowerCase(Locale.ENGLISH)).append('/');
|
||||||
|
sb.append(name);
|
||||||
|
sb.append(JPG);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String music(String name) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(MUSIC);
|
||||||
|
sb.append(name);
|
||||||
|
sb.append(OGG);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sound(String name) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(SOUND);
|
||||||
|
if (name.startsWith("sfx_")) sb.append(name.substring(4));
|
||||||
|
else sb.append(name);
|
||||||
|
sb.append(OGG);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/com/annimon/everlastingsummer/Token.java
Normal file
28
src/com/annimon/everlastingsummer/Token.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class Token {
|
||||||
|
|
||||||
|
private final String text;
|
||||||
|
private final TokenType type;
|
||||||
|
|
||||||
|
public Token(String text, TokenType type) {
|
||||||
|
this.text = text;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return type.name() + " " + text;
|
||||||
|
}
|
||||||
|
}
|
56
src/com/annimon/everlastingsummer/TokenType.java
Normal file
56
src/com/annimon/everlastingsummer/TokenType.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public enum TokenType {
|
||||||
|
|
||||||
|
COMMAND, // начинается с $
|
||||||
|
LABEL, // заканчивается на :
|
||||||
|
WORD,
|
||||||
|
TEXT,
|
||||||
|
NUMBER,
|
||||||
|
|
||||||
|
// операторы и спецсимволы
|
||||||
|
EQ,
|
||||||
|
PLUS,
|
||||||
|
MINUS,
|
||||||
|
LPAREN,
|
||||||
|
RPAREN,
|
||||||
|
LBRACKET,
|
||||||
|
RBRACKET,
|
||||||
|
|
||||||
|
// ключевые слова
|
||||||
|
PLAY,
|
||||||
|
STOP,
|
||||||
|
MUSIC,
|
||||||
|
AMBIENCE,
|
||||||
|
SOUND,
|
||||||
|
SOUNDLOOP,
|
||||||
|
FADEIN,
|
||||||
|
FADEOUT,
|
||||||
|
|
||||||
|
SCENE,
|
||||||
|
ANIM,
|
||||||
|
BG,
|
||||||
|
CG,
|
||||||
|
|
||||||
|
WINDOW,
|
||||||
|
HIDE,
|
||||||
|
SHOW,
|
||||||
|
|
||||||
|
AT,
|
||||||
|
WITH,
|
||||||
|
|
||||||
|
RETURN,
|
||||||
|
|
||||||
|
// команды
|
||||||
|
RENPY_PAUSE,
|
||||||
|
PERSISTENT_SPRITE_TIME,
|
||||||
|
PROLOG_TIME,
|
||||||
|
DAY_TIME,
|
||||||
|
SUNSET_TIME,
|
||||||
|
NIGHT_TIME,
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
265
src/com/annimon/everlastingsummer/ViewActivity.java
Normal file
265
src/com/annimon/everlastingsummer/ViewActivity.java
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package com.annimon.everlastingsummer;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.media.MediaPlayer;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.*;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Экран воспроизведения rpy-сценария.
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public final class ViewActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String EXTRA_NAME = "name";
|
||||||
|
private static final FadeInfo NO_FADE = new FadeInfo(false, false, 0);
|
||||||
|
private static ViewActivity instance;
|
||||||
|
|
||||||
|
public static ViewActivity getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageView background;
|
||||||
|
private FrameLayout container;
|
||||||
|
private TextView textview;
|
||||||
|
private MediaPlayer musicPlayer, soundPlayer;
|
||||||
|
|
||||||
|
private Map<String, Person> names;
|
||||||
|
private Map<String, ImageView> spriteInContainer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Постоянная подсветка.
|
||||||
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
|
||||||
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
|
setContentView(R.layout.main);
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
background = (ImageView) findViewById(R.id.background);
|
||||||
|
container = (FrameLayout) findViewById(R.id.container);
|
||||||
|
textview = (TextView) findViewById(R.id.text);
|
||||||
|
|
||||||
|
background.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Parser.getInstance().next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
spriteInContainer = new HashMap<String, ImageView>();
|
||||||
|
|
||||||
|
// Маппинг <короткое_имя, имя/цвет>
|
||||||
|
names = new HashMap<String, Person>();
|
||||||
|
// https://github.com/yakui-lover/eroge-dopil/blob/master/media.rpy#L365
|
||||||
|
names.put("me", new Person("Семён", 0xFFE1DD7D));
|
||||||
|
names.put("un", new Person("Лена", 0xFFB956FF)); // night 0xFFAA64D9, sunset: 0xFFB956FF, day: 0xFFB956FF, prolog: 0xFFB956FF
|
||||||
|
names.put("dv", new Person("Алиса", 0xFFFFAA00)); // night 0xFFD28B10, sunset: 0xFFFFAA00, day: 0xFFFFAA00, prolog: 0xFFFFAA00
|
||||||
|
names.put("sl", new Person("Славя", 0xFFFFD200)); // night 0xFFD6B000, sunset: 0xFFFFD200, day: 0xFFFFD200, prolog: 0xFFFFD200
|
||||||
|
names.put("us", new Person("Ульяна", 0xFFFF3200)); // night 0xFFEA3700, sunset: 0xFFFF3200, day: 0xFFFF3200, prolog: 0xFFFF3200
|
||||||
|
names.put("mt", new Person("Ольга Дмитриевна", 0xFF00EA32)); // night 0xFF00B627, sunset: 0xFF00EA32, day: 0xFF00EA32, prolog: 0xFF00EA32
|
||||||
|
names.put("cs", new Person("Виола", 0xFFA5A5FF)); // night 0xFF8686E6, sunset: 0xFFA5A5FF, day: 0xFFA5A5FF, prolog: 0xFFA5A5FF
|
||||||
|
names.put("mz", new Person("Женя", 0xFF4A86FF)); // night 0xFF5481DB, sunset: 0xFF72A0FF, day: 0xFF4A86FF, prolog: 0xFF4A86FF
|
||||||
|
names.put("mi", new Person("Мику", 0xFF00DEFF)); // night 0xFF00B4CF, sunset: 0xFF00FBFF, day: 0xFF00DEFF, prolog: 0xFF00DEFF
|
||||||
|
names.put("uv", new Person("Юля", 0xFF4EFF00)); // night 0xFF40D000, sunset: 0xFF4EFF00, day: 0xFF4EFF00, prolog: 0xFF4EFF00
|
||||||
|
names.put("lk", new Person("Луркмор-кун", 0xFFFF8080));
|
||||||
|
names.put("sh", new Person("Шурик", 0xFFFFF226)); // night 0xFFCDC212, sunset: 0xFFFFF226, day: 0xFFFFF226, prolog: 0xFFFFF226
|
||||||
|
names.put("el", new Person("Электроник", 0xFFFFFF00)); // night 0xFFCDCD00, sunset: 0xFFFFFF00, day: 0xFFFFFF00, prolog: 0xFFFFFF00
|
||||||
|
names.put("pi", new Person("Пионер", 0xFFE60101)); // night 0xFFE60000, sunset: 0xFFE60000, day: 0xFFE60101, prolog: 0xFFE60000
|
||||||
|
|
||||||
|
names.put("dy", new Person("Голос из динамика", 0xFFC0C0C0));
|
||||||
|
names.put("voice", new Person("Голос", 0xFFE1DD7D));
|
||||||
|
names.put("voices", new Person("Голоса", 0xFFC0C0C0));
|
||||||
|
names.put("message", new Person("Сообщение", 0xFFC0C0C0));
|
||||||
|
names.put("all", new Person("Пионеры", 0xFFED4444)); // night 0xFFE33A3A, sunset: 0xFFE33A3A, day: 0xFFED4444, prolog: 0xFFE33A3A
|
||||||
|
names.put("kids", new Person("Малышня", 0xFFEB7883));
|
||||||
|
names.put("dreamgirl", new Person("...", 0xFFC0C0C0));
|
||||||
|
names.put("bush", new Person("Голос", 0xFFC0C0C0));
|
||||||
|
names.put("FIXME_voice", new Person("Голос", 0xFFC0C0C0));
|
||||||
|
names.put("odn", new Person("Одногруппник", 0xFFC0C0C0));
|
||||||
|
names.put("mt_voice", new Person("Голос", 0xFF00EA32)); // night 0xFF00B627, sunset: 0xFF00EA32, day: 0xFF00EA32, prolog: 0xFF00EA32
|
||||||
|
|
||||||
|
final String name = getIntent().getStringExtra(EXTRA_NAME);
|
||||||
|
final String scriptpath = PathResolver.script(name);
|
||||||
|
try {
|
||||||
|
Parser.parse(Lexer.tokenize( IOUtil.readContents(getAssets().open(scriptpath)) ));
|
||||||
|
Parser.getInstance().next();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Toast.makeText(this, "Ошибка при открытии файла " + scriptpath, Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
stopMusic(NO_FADE);
|
||||||
|
stopSound(NO_FADE);
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void windowShow() {
|
||||||
|
if (textview.getVisibility() != View.VISIBLE)
|
||||||
|
textview.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void windowHide() {
|
||||||
|
if (textview.getVisibility() != View.INVISIBLE)
|
||||||
|
textview.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void background(String type, String name, String effect) {
|
||||||
|
spritesClear();
|
||||||
|
if (name.equalsIgnoreCase("black")) background.setImageResource(android.R.color.black);
|
||||||
|
else if (name.equalsIgnoreCase("white")) background.setImageResource(android.R.color.white);
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
background.setImageBitmap(IOUtil.readBitmap(PathResolver.background(type, name)));
|
||||||
|
} catch (Exception ioe) {
|
||||||
|
background.setImageResource(android.R.color.black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spritesClear() {
|
||||||
|
container.removeAllViews();
|
||||||
|
spriteInContainer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sprite(String whoid, String emotion, String cloth, String distance, String position) {
|
||||||
|
ImageView img;
|
||||||
|
if (spriteInContainer.containsKey(whoid)) {
|
||||||
|
img = spriteInContainer.get(whoid);
|
||||||
|
} else {
|
||||||
|
img = new ImageView(this);
|
||||||
|
spriteInContainer.put(whoid, img);
|
||||||
|
}
|
||||||
|
final String path = PathResolver.sprite(whoid, emotion, cloth, distance);;
|
||||||
|
try {
|
||||||
|
img.setImageBitmap(IOUtil.readBitmap(path));
|
||||||
|
container.addView(img);
|
||||||
|
} catch (Exception ioe) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause(final long duration) {
|
||||||
|
text("");
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(duration);
|
||||||
|
} catch (InterruptedException e) { }
|
||||||
|
runOnUiThread(nextCommandRunnable);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void text(String text) {
|
||||||
|
if (TextUtils.isEmpty(text)) windowHide();
|
||||||
|
else {
|
||||||
|
windowShow();
|
||||||
|
textview.setText(formatString(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void text(String whoid, String text) {
|
||||||
|
if (whoid.equalsIgnoreCase("th")) text("~ " + text + " ~");
|
||||||
|
else if (!names.containsKey(whoid)) text(text);
|
||||||
|
else {
|
||||||
|
windowShow();
|
||||||
|
final Person person = names.get(whoid);
|
||||||
|
final String who = person.name;
|
||||||
|
Spannable spannable = formatString(who + "\n" + text);
|
||||||
|
spannable.setSpan(new ForegroundColorSpan(person.color), 0, who.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
textview.setText(spannable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Spannable formatString(String text) {
|
||||||
|
String edited = text.replace("{w}", "");
|
||||||
|
final char[] codes = {'b','i','s','u'};
|
||||||
|
boolean html = false;
|
||||||
|
for (int i = 0; i < codes.length; i++) {
|
||||||
|
final char ch = codes[i];
|
||||||
|
if (edited.contains("{"+ch+"}")) {
|
||||||
|
edited = edited.replace("{"+ch+"}", "<"+ch+">");
|
||||||
|
edited = edited.replace("{/"+ch+"}", "</"+ch+">");
|
||||||
|
html = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SpannableString(html ? Html.fromHtml(edited) : edited);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void music(String name, FadeInfo fade) {
|
||||||
|
try {
|
||||||
|
stopMusic(fade);
|
||||||
|
musicPlayer = new MediaPlayer();
|
||||||
|
musicPlayer.setDataSource( IOUtil.getFD(PathResolver.music(name)) );
|
||||||
|
musicPlayer.prepare();
|
||||||
|
musicPlayer.setVolume(1f, 1f);
|
||||||
|
musicPlayer.setLooping(true);
|
||||||
|
musicPlayer.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopMusic(FadeInfo fade) {
|
||||||
|
if (musicPlayer == null) return;
|
||||||
|
if (musicPlayer.isPlaying()) {
|
||||||
|
musicPlayer.stop();
|
||||||
|
musicPlayer.release();
|
||||||
|
}
|
||||||
|
musicPlayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sound(String name, boolean loop, FadeInfo fade) {
|
||||||
|
try {
|
||||||
|
stopSound(fade);
|
||||||
|
soundPlayer = new MediaPlayer();
|
||||||
|
soundPlayer.setDataSource( IOUtil.getFD(PathResolver.sound(name)) );
|
||||||
|
soundPlayer.prepare();
|
||||||
|
soundPlayer.setVolume(1f, 1f);
|
||||||
|
soundPlayer.setLooping(loop);
|
||||||
|
soundPlayer.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopSound(FadeInfo fade) {
|
||||||
|
if (soundPlayer == null) return;
|
||||||
|
if (soundPlayer.isPlaying()) {
|
||||||
|
soundPlayer.stop();
|
||||||
|
soundPlayer.release();
|
||||||
|
}
|
||||||
|
soundPlayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Runnable nextCommandRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Parser.getInstance().next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private class Person {
|
||||||
|
String name;
|
||||||
|
int color;
|
||||||
|
Person(String fullName, int color) {
|
||||||
|
this.name = fullName;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user