Initial commit

This commit is contained in:
Victor 2015-04-04 15:09:43 +03:00
commit e8f7ff45eb
20 changed files with 1115 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.classpath
.cproject
.project
.settings
bin/
gen/
obj/
tmp/
proguard/

36
AndroidManifest.xml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

20
proguard-project.txt Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

32
res/layout/main.xml Normal file
View 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
View File

@ -0,0 +1,5 @@
<resources>
<string name="app_name">Everlasting Summer RpyPlayer</string>
</resources>

View 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;
}
}

View 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();
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}

View 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;
}
}

View 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
}

View 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;
}
}
}