diff options
Diffstat (limited to 'modules')
204 files changed, 52400 insertions, 2615 deletions
diff --git a/modules/SCsub b/modules/SCsub index f37c3a55c..4b9c08cf7 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -1,21 +1,23 @@ +#!/usr/bin/env python + Import('env') env_modules = env.Clone() Export('env_modules') -env.modules_sources=[ - "register_module_types.cpp", +env.modules_sources = [ + "register_module_types.cpp", ] -#env.add_source_files(env.modules_sources,"*.cpp") +# env.add_source_files(env.modules_sources,"*.cpp") Export('env') for x in env.module_list: - if (x in env.disabled_modules): - continue - env_modules.Append(CPPFLAGS=["-DMODULE_"+x.upper()+"_ENABLED"]) - SConscript(x+"/SCsub") + if (x in env.disabled_modules): + continue + env_modules.Append(CPPFLAGS=["-DMODULE_" + x.upper() + "_ENABLED"]) + SConscript(x + "/SCsub") -lib = env_modules.Library("modules",env.modules_sources) +lib = env_modules.Library("modules", env.modules_sources) env.Prepend(LIBS=[lib]) diff --git a/modules/chibi/SCsub b/modules/chibi/SCsub new file mode 100644 index 000000000..dffd96675 --- /dev/null +++ b/modules/chibi/SCsub @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_chibi = env_modules.Clone() + +# Godot source files +env_chibi.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/ik/config.py b/modules/chibi/config.py index f9bd7da08..fb920482f 100644 --- a/modules/ik/config.py +++ b/modules/chibi/config.py @@ -1,11 +1,7 @@ - def can_build(platform): - return True - - + return True + + def configure(env): - pass - - - + pass diff --git a/modules/chibi/cp_config.h b/modules/chibi/cp_config.h new file mode 100644 index 000000000..35312b68b --- /dev/null +++ b/modules/chibi/cp_config.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* cp_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_CONFIG_H +#define CP_CONFIG_H + + +#include "typedefs.h" +#include "error_macros.h" +#include "math_funcs.h" +#include "os/memory.h" +#include "os/copymem.h" + +#define CP_PRINTERR(m_err) ERR_PRINT(m_err) +#define CP_ERR_COND(m_cond) ERR_FAIL_COND(m_cond) +#define CP_ERR_COND_V(m_cond,m_ret) ERR_FAIL_COND_V(m_cond,m_ret) +#define CP_FAIL_INDEX(m_index,m_size) ERR_FAIL_INDEX(m_index,m_size) +#define CP_FAIL_INDEX_V(m_index,m_size,m_ret) ERR_FAIL_INDEX_V(m_index,m_size,m_ret) +#define cp_intabs(m_val) ABS(m_val) + +#define CP_ALLOC(m_mem) memalloc(m_mem) +#define CP_REALLOC(m_mem,m_size) memrealloc(m_mem,m_size) +#define CP_FREE(m_mem) memfree(m_mem) + +#define cp_memzero(m_mem,m_size) zeromem(m_mem,m_size) + +#endif // CP_CONFIG_H diff --git a/modules/chibi/cp_envelope.cpp b/modules/chibi/cp_envelope.cpp new file mode 100644 index 000000000..36259e8d6 --- /dev/null +++ b/modules/chibi/cp_envelope.cpp @@ -0,0 +1,369 @@ +/*************************************************************************/ +/* cp_envelope.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_envelope.h" + + +CPEnvelope::CPEnvelope() { + + + reset(); +} + +void CPEnvelope::reset() { + + + + on=false; + carry=false; + loop_on=false; + loop_begin_node=0; + loop_end_node=0; + sustain_loop_on=false; + sustain_loop_begin_node=0; + sustain_loop_end_node=0; + node_count=0; + +} + +int CPEnvelope::get_height_at_pos(int pos) { + + if (node_count && pos>node[node_count-1].tick_offset) + return node[node_count-1].value; + + int begin_x,begin_y; + int end_x,end_y,xdif; + int count=0; + int limit=-1; + + if (node_count<2) return NO_POINT; + + while ((count<node_count) && (limit==-1)) { + + if (node[count].tick_offset>=pos) limit=count; + count++; + } + + if (pos==0) return node[0].value; + + if (limit==-1) return NO_POINT; + + begin_x=node[limit-1].tick_offset; + end_x=node[limit].tick_offset; + begin_y=node[limit-1].value; + end_y=node[limit].value; + + xdif=end_x-begin_x; + return begin_y+((pos-begin_x)*(end_y-begin_y))/(xdif?xdif:1); +} + +/* +int CPEnvelope::get_fx_height_at_pos(int pos) { + + if (node_count && pos>node[node_count-1].tick_offset) + return node[node_count-1].value<<FX_HEIGHT_BITS; + + int begin_x,begin_y; + int end_x,end_y,xdif; + int count=0; + int limit=-1; + + if (node_count<2) return NO_POINT; + + while ((count<node_count) && (limit==-1)) { + + if (node[count].tick_offset>=pos) limit=count; + count++; + } + + if (pos==0) return node[0].value<<FX_HEIGHT_BITS; + + if (limit==-1) return NO_POINT; + + begin_x=node[limit-1].tick_offset; + end_x=node[limit].tick_offset; + begin_y=node[limit-1].value; + end_y=node[limit].value; + + xdif=end_x-begin_x; + return (begin_y<<FX_HEIGHT_BITS)+((pos-begin_x)*(end_y-begin_y)*(int)(1<<FX_HEIGHT_BITS))/(xdif?xdif:1); +} +*/ + +float CPEnvelope::get_interp_height_at_pos(float pos) { + + if (node_count && pos>node[node_count-1].tick_offset) + return node[node_count-1].value; + + int begin_x,begin_y; + int end_x,end_y,xdif; + int count=0; + int limit=-1; + + if (node_count<2) return NO_POINT; + + while ((count<node_count) && (limit==-1)) { + + if (node[count].tick_offset>=pos) limit=count; + count++; + } + + if (pos==0) return node[0].value; + + if (limit==-1) return NO_POINT; + + begin_x=node[limit-1].tick_offset; + end_x=node[limit].tick_offset; + begin_y=node[limit-1].value; + end_y=node[limit].value; + + xdif=end_x-begin_x; + return begin_y+((pos-begin_x)*(end_y-begin_y))/(xdif?xdif:1); +} + +void CPEnvelope::set_position(int p_node,int p_x,int p_y) { + + if (p_node>=node_count) return; + + + + if (p_node==0) { + + p_x=0; + + } else if (p_x<=node[p_node-1].tick_offset) { + + p_x=node[p_node-1].tick_offset+1; + + } else if ((p_node<(node_count-1)) && (p_x>=node[p_node+1].tick_offset)) { + + p_x=node[p_node+1].tick_offset-1; + } + + if (p_x>=9999) p_x=9999; + + if (p_y>max_value) p_y=max_value; + if (p_y<min_value) p_y=min_value; + + + node[p_node].tick_offset=p_x; + node[p_node].value=p_y; + + + +} + +int CPEnvelope::add_position(int p_x,int p_y,bool p_move_loops) { + + if (node_count==MAX_POINTS) return -1; + + + int i,new_node; + + // if this is assigning an existing node, let's quit. + for (i=0;i<node_count;i++) if (p_x==node[i].tick_offset) return -1; + + + i=0; + while ((i<node_count) && (p_x>=node[i].tick_offset)) i++; + + new_node=i; + node_count++; + + if (p_move_loops) { + if (loop_begin_node>=new_node) loop_begin_node++; + if (loop_end_node>=new_node) loop_end_node++; + if (sustain_loop_begin_node>=new_node) sustain_loop_begin_node++; + if (sustain_loop_end_node>=new_node) sustain_loop_end_node++; + } + for (i=node_count-1;i>new_node;i--) node[i]=node[i-1]; + + + + set_position(new_node,p_x,p_y); + + + + return new_node; + +} + +void CPEnvelope::set_loop_begin(int pos) { + + if ((pos<0) || (pos>=node_count)) return; + + + + loop_begin_node=pos; + + if (loop_end_node<loop_begin_node) loop_end_node=loop_begin_node; + + + +} + +void CPEnvelope::set_loop_end(int pos) { + + if ((pos<0) || (pos>=node_count)) return; + + + + loop_end_node=pos; + + if (loop_end_node<loop_begin_node) loop_begin_node=loop_end_node; + + + + +} + + +void CPEnvelope::set_sustain_loop_begin(int pos) { + + if ((pos<0) || (pos>=node_count)) return; + + + + sustain_loop_begin_node=pos; + + if (sustain_loop_end_node<sustain_loop_begin_node) sustain_loop_end_node=sustain_loop_begin_node; + + + +} + +void CPEnvelope::set_sustain_loop_end(int pos) { + + if ((pos<0) || (pos>=node_count)) return; + + + + sustain_loop_end_node=pos; + + if (sustain_loop_end_node<sustain_loop_begin_node) sustain_loop_begin_node=sustain_loop_end_node; + + + +} + +void CPEnvelope::set_loop_enabled(bool p_enabled) { + + loop_on=p_enabled; +} +bool CPEnvelope::is_loop_enabled() { + + return loop_on; +} + + +void CPEnvelope::set_sustain_loop_enabled(bool p_enabled) { + + sustain_loop_on=p_enabled; +} +bool CPEnvelope::is_sustain_loop_enabled() { + + return sustain_loop_on; +} + +void CPEnvelope::del_position(int p_node) { + + if ((node_count<3) || (p_node<=0) || (p_node>=node_count)) return; + + + + int i; + + if (loop_begin_node>=p_node) loop_begin_node--; + if (loop_end_node>=p_node) loop_end_node--; + if (sustain_loop_begin_node>=p_node) sustain_loop_begin_node--; + if (sustain_loop_end_node>=p_node) sustain_loop_end_node--; + + for (i=p_node;i<node_count-1;i++) node[i]=node[i+1]; + + node_count--; + + + +} + +uint8_t CPEnvelope::get_loop_begin() { + + + return loop_begin_node; +} +uint8_t CPEnvelope::get_loop_end() { + + return loop_end_node; +} + +uint8_t CPEnvelope::get_sustain_loop_begin() { + + + return sustain_loop_begin_node; +} +uint8_t CPEnvelope::get_sustain_loop_end() { + + return sustain_loop_end_node; +} + + + +void CPEnvelope::set_enabled(bool p_enabled) { + + on=p_enabled; +} + +bool CPEnvelope::is_enabled() { + + return on; +} + +void CPEnvelope::set_carry_enabled(bool p_enabled) { + + carry=p_enabled; +} +bool CPEnvelope::is_carry_enabled() { + + return carry; +} + +uint8_t CPEnvelope::get_node_count() { + + return node_count; +} + +const CPEnvelope::Point& CPEnvelope::get_node(int p_idx) { + + if (p_idx<0 || p_idx>=node_count) + return node[node_count-1]; + + return node[p_idx]; + +} + + diff --git a/modules/chibi/cp_envelope.h b/modules/chibi/cp_envelope.h new file mode 100644 index 000000000..af27f5f18 --- /dev/null +++ b/modules/chibi/cp_envelope.h @@ -0,0 +1,129 @@ +/*************************************************************************/ +/* cp_envelope.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CP_ENVELOPE_H +#define CP_ENVELOPE_H + +#include "cp_config.h" + +/**envelope? + *@author Juan Linietsky + */ + +/****************************** + envelope.h + ---------- + +Proovides an envelope, and basic functions +for it that can be used for both player +and interface +********************************/ + + +class CPEnvelope { + enum { + + MAX_POINTS=25 + }; + + struct Point { + + uint16_t tick_offset; + int16_t value; + }; + + Point node[MAX_POINTS]; + + int8_t node_count; + + bool on; + bool carry; + + bool loop_on; + + uint8_t loop_begin_node; + uint8_t loop_end_node; + + bool sustain_loop_on; + uint8_t sustain_loop_begin_node; + uint8_t sustain_loop_end_node; + + + int8_t max_value; + int8_t min_value; + + +public: + enum { + + NO_POINT=-5000, + }; + + void set_max(int8_t p_max) { max_value=p_max; } + int8_t get_max() { return max_value; } + void set_min(int8_t p_min) { min_value=p_min; } + int8_t get_min() { return min_value; } + + uint8_t get_node_count(); + const Point& get_node(int p_idx); + + void set_position(int p_node,int p_x,int p_y); + int add_position(int p_x,int p_y,bool p_move_loops=true); + void del_position(int p_node); + + void set_loop_enabled(bool p_enabled); + bool is_loop_enabled(); + void set_loop_begin(int pos); + void set_loop_end(int pos); + uint8_t get_loop_begin(); + uint8_t get_loop_end(); + + void set_sustain_loop_enabled(bool p_enabled); + bool is_sustain_loop_enabled(); + void set_sustain_loop_begin(int pos); + void set_sustain_loop_end(int pos); + uint8_t get_sustain_loop_begin(); + uint8_t get_sustain_loop_end(); + + void set_enabled(bool p_enabled); + bool is_enabled(); + + void set_carry_enabled(bool p_enabled); + bool is_carry_enabled(); + + void reset(); + int get_height_at_pos(int pos); + float get_interp_height_at_pos(float pos); + + + CPEnvelope(); + +}; + +#endif diff --git a/modules/chibi/cp_file_access_wrapper.h b/modules/chibi/cp_file_access_wrapper.h new file mode 100644 index 000000000..ade077c1e --- /dev/null +++ b/modules/chibi/cp_file_access_wrapper.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* cp_file_access_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_FILE_ACCESS_WRAPPER_H +#define CP_FILE_ACCESS_WRAPPER_H + +#include "cp_config.h" + +class CPFileAccessWrapper { +public: + + enum ModeFlags { + + READ=1, + WRITE=2, + READ_WRITE=3, + }; + + enum Error { + + OK, + ERROR_FILE_NOT_FOUND, + ERROR_FILE_BAD_DRIVE, + ERROR_FILE_BAD_PATH, + ERROR_FILE_NO_PERMISSION, + ERROR_ALREADY_IN_USE, + ERROR_INVALID_PARAMETERS, + ERROR_OPENING_FILE, + ERROR_READING_FILE, + ERROR_WRITING_FILE + }; + + virtual Error open(const char *p_filename, int p_mode_flags)=0; + virtual void close()=0; + + virtual void seek(uint32_t p_position)=0; + virtual void seek_end()=0; + virtual uint32_t get_pos()=0; + + virtual bool eof_reached()=0; + + virtual uint8_t get_byte()=0; + virtual void get_byte_array(uint8_t *p_dest,int p_elements)=0; + virtual void get_word_array(uint16_t *p_dest,int p_elements)=0; + + virtual uint16_t get_word()=0; + virtual uint32_t get_dword()=0; + + // use this for files WRITTEN in _big_ endian machines (ie, amiga/mac) + // It's not about the current CPU type but file formats. + // this flags get reset to false (little endian) on each open + virtual void set_endian_conversion(bool p_swap)=0; + virtual bool is_open()=0; + + virtual Error get_error()=0; + + virtual void store_byte(uint8_t p_dest)=0; + virtual void store_byte_array(const uint8_t *p_dest,int p_elements)=0; + + virtual void store_word(uint16_t p_dest)=0; + virtual void store_dword(uint32_t p_dest)=0; + + + + virtual ~CPFileAccessWrapper(){} + +}; + + + +#endif diff --git a/modules/chibi/cp_instrument.cpp b/modules/chibi/cp_instrument.cpp new file mode 100644 index 000000000..606a4217e --- /dev/null +++ b/modules/chibi/cp_instrument.cpp @@ -0,0 +1,344 @@ +/*************************************************************************/ +/* cp_instrument.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_instrument.h" +#include "cp_song.h" +#include "cp_note.h" + + + +const char *CPInstrument::get_name() { + + return name; + +} +void CPInstrument::set_name(const char *p_name) { + + + if (p_name==NULL) { + name[0]=0; + return; + } + + + bool done=false; + for (int i=0;i<MAX_NAME_LEN;i++) { + + + name[i]=done?0:p_name[i]; + if (!done && p_name[i]==0) + done=true; + } + + name[MAX_NAME_LEN-1]=0; /* just in case */ + + +} + +void CPInstrument::set_sample_number(uint8_t p_note,uint8_t p_sample_id) { + + CP_ERR_COND(p_note>=CPNote::NOTES); + CP_ERR_COND(p_sample_id>CPSong::MAX_SAMPLES && p_sample_id!=CPNote::EMPTY); + data.sample_number[p_note]=p_sample_id; + + +} +uint8_t CPInstrument::get_sample_number(uint8_t p_note) { + + CP_ERR_COND_V(p_note>=CPNote::NOTES,0); + return data.sample_number[p_note]; +} + +void CPInstrument::set_note_number(uint8_t p_note,uint8_t p_note_id) { + + CP_ERR_COND(p_note>=CPNote::NOTES); + CP_ERR_COND(p_note_id>=CPNote::NOTES && p_note_id!=CPNote::EMPTY); + data.note_number[p_note]=p_note_id; + +} +uint8_t CPInstrument::get_note_number(uint8_t p_note) { + + CP_ERR_COND_V(p_note>=CPNote::NOTES,0); + return data.note_number[p_note]; + +} + +void CPInstrument::set_NNA_type(NNA_Type p_NNA_type) { + + data.NNA_type=p_NNA_type; +} +CPInstrument::NNA_Type CPInstrument::get_NNA_type() { + + return data.NNA_type; +} + +void CPInstrument::set_DC_type(DC_Type p_DC_type) { + + data.DC_type=p_DC_type; +} +CPInstrument::DC_Type CPInstrument::get_DC_type() { + + return data.DC_type; + +} + +void CPInstrument::set_DC_action(DC_Action p_DC_action) { + + data.DC_action=p_DC_action; +} +CPInstrument::DC_Action CPInstrument::get_DC_action() { + + return data.DC_action; +} + +/* Volume */ + +void CPInstrument::set_volume_global_amount(uint8_t p_amount) { + + CP_ERR_COND(p_amount>MAX_VOLUME); + data.volume.global_amount=p_amount; + +} +uint8_t CPInstrument::get_volume_global_amount() { + + return data.volume.global_amount; +} + +void CPInstrument::set_volume_fadeout(uint16_t p_amount) { + CP_ERR_COND(p_amount>MAX_FADEOUT); + data.volume.fadeout=p_amount; +} +uint16_t CPInstrument::get_volume_fadeout() { + + return data.volume.fadeout; +} +void CPInstrument::set_volume_random_variation(uint8_t p_amount) { + + CP_ERR_COND(p_amount>MAX_VOLUME_RANDOM); + data.volume.random_variation=p_amount; +} +uint8_t CPInstrument::get_volume_random_variation() { + + return data.volume.random_variation; +} + +/* Panning */ + +void CPInstrument::set_pan_default_amount(uint8_t p_amount) { + + CP_ERR_COND(p_amount>MAX_PAN); + data.pan.default_amount=p_amount; +} +uint8_t CPInstrument::get_pan_default_amount() { + + return data.pan.default_amount; +} + +void CPInstrument::set_pan_default_enabled(bool p_enabled) { + + data.pan.use_default=p_enabled; +} +bool CPInstrument::is_pan_default_enabled() { + + return data.pan.use_default; + +} + +void CPInstrument::set_pan_pitch_separation(int8_t p_amount) { + + CP_ERR_COND(p_amount<-32); + CP_ERR_COND(p_amount>32); + data.pan.pitch_separation=p_amount; +} +int8_t CPInstrument::get_pan_pitch_separation() { + + return data.pan.pitch_separation; +} + +void CPInstrument::set_pan_pitch_center(uint8_t p_amount) { + + CP_ERR_COND(p_amount>=CPNote::NOTES); + data.pan.pitch_center=p_amount; +} +uint8_t CPInstrument::get_pan_pitch_center() { + + return data.pan.pitch_center; +} + +void CPInstrument::set_pan_random_variation(uint8_t p_amount) { + + CP_ERR_COND(p_amount>MAX_PAN_RANDOM); + data.pan.random_variation=p_amount; +} +uint8_t CPInstrument::get_pan_random_variation() { + + return data.pan.random_variation; +} + +/* Pitch / Filter */ + +void CPInstrument::set_pitch_use_as_filter(bool p_enabled) { + + data.pitch.use_as_filter=p_enabled; +} +bool CPInstrument::is_pitch_use_as_filter() { + + return data.pitch.use_as_filter; +} + +void CPInstrument::set_filter_use_default_cutoff(bool p_enabled) { + + data.pitch.use_default_cutoff=p_enabled; + +} +bool CPInstrument::filter_use_default_cutoff() { + + return data.pitch.use_default_cutoff; +} + +void CPInstrument::set_filter_default_cutoff(uint8_t p_amount) { + + CP_ERR_COND(p_amount>MAX_FILTER_CUTOFF); + data.pitch.default_cutoff=p_amount; +} +uint8_t CPInstrument::get_filter_default_cutoff() { + + return data.pitch.default_cutoff; +} + +void CPInstrument::set_filter_use_default_resonance(bool p_enabled) { + + data.pitch.use_default_resonance=p_enabled; +} +bool CPInstrument::filter_use_default_resonance() { + + return data.pitch.use_default_resonance; +} + +void CPInstrument::set_filter_default_resonance(uint8_t p_amount) { + + CP_ERR_COND(p_amount>MAX_FILTER_RESONANCE); + data.pitch.default_resonance=p_amount; + +} +uint8_t CPInstrument::get_filter_default_resonance() { + + return data.pitch.default_resonance; +} + +/* Envelopes */ + + +CPEnvelope* CPInstrument::get_volume_envelope() { + + return &data.volume.envelope; +} +CPEnvelope* CPInstrument::get_pan_envelope() { + + return &data.pan.envelope; +} +CPEnvelope* CPInstrument::get_pitch_filter_envelope() { + + return &data.pitch.envelope; + + +} + + +void CPInstrument::reset() { + + name[0]=0; + + data.NNA_type=NNA_NOTE_CUT; + data.DC_action=DCA_NOTE_CUT; + data.DC_type=DCT_DISABLED; + + for (int i=0;i<CPNote::NOTES;i++) { + + data.sample_number[i]=CPNote::EMPTY; + data.note_number[i]=i; + } + + data.volume.envelope.reset(); + data.volume.envelope.set_max(64); + data.volume.envelope.set_min(0); + data.volume.envelope.add_position(0,64,false); + data.volume.envelope.add_position(30,64,false); + + data.volume.global_amount=MAX_VOLUME; + data.volume.fadeout=0; + data.volume.random_variation=0; + + data.pan.envelope.reset(); + data.pan.envelope.set_max(32); + data.pan.envelope.set_min(-32); + data.pan.envelope.add_position(0,0,false); + data.pan.envelope.add_position(30,0,false); + + data.pan.default_amount=32; + data.pan.pitch_center=48; + data.pan.pitch_separation=0; + data.pan.use_default=false; + data.pan.random_variation=0; + + + data.pitch.envelope.reset(); + data.pitch.envelope.set_max(32); + data.pitch.envelope.set_min(-32); + data.pitch.envelope.add_position(0,0,false); + data.pitch.envelope.add_position(30,0,false); + data.pitch.use_as_filter=false; + data.pitch.use_default_cutoff=false; + data.pitch.use_default_resonance=false; + data.pitch.default_cutoff=0; + data.pitch.default_resonance=0; + +} + +bool CPInstrument::is_empty() { + + bool has_sample=false; + + for (int i=0;i<CPNote::NOTES;i++) { + + if (data.sample_number[i]!=CPNote::EMPTY) { + + has_sample=true; + break; + } + } + + return !has_sample; +} + +CPInstrument::CPInstrument() { + + reset(); + +} + diff --git a/modules/chibi/cp_instrument.h b/modules/chibi/cp_instrument.h new file mode 100644 index 000000000..e51612a38 --- /dev/null +++ b/modules/chibi/cp_instrument.h @@ -0,0 +1,219 @@ +/*************************************************************************/ +/* cp_instrument.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_INSTRUMENT_H +#define CP_INSTRUMENT_H + + +#include "cp_config.h" +#include "cp_note.h" +#include "cp_envelope.h" + +class CPInstrument { +public: + + + enum NNA_Type { + + NNA_NOTE_CUT, + NNA_NOTE_CONTINUE, + NNA_NOTE_OFF, + NNA_NOTE_FADE + }; + + enum DC_Type { + + DCT_DISABLED, + DCT_NOTE, + DCT_SAMPLE, + DCT_INSTRUMENT + }; + + enum DC_Action + { + + DCA_NOTE_CUT, + DCA_NOTE_OFF, + DCA_NOTE_FADE, + }; + + enum EnvelopeType { + VOLUME_ENVELOPE, + PAN_ENVELOPE, + PITCH_ENVELOPE + }; + + + enum { + MAX_NAME_LEN=26, + MAX_ENVELOPE_NODES=25, + ENVELOPE_FRAC_BITS=8, + MAX_VOLUME=128, + MAX_FADEOUT=256, + MAX_PAN=128, + MAX_VOLUME_RANDOM=100, + MAX_PAN_RANDOM=64, //what did this guy have inside his head? + + MAX_FILTER_CUTOFF=127, + MAX_FILTER_RESONANCE=127 + + }; + + + struct Data { + + + uint8_t sample_number[CPNote::NOTES]; + uint8_t note_number[CPNote::NOTES]; + + NNA_Type NNA_type; + DC_Type DC_type; + DC_Action DC_action; + + struct Volume { + + CPEnvelope envelope; + uint8_t global_amount; + uint16_t fadeout; + uint8_t random_variation; + + } volume; + + struct Pan { + + CPEnvelope envelope; + bool use_default; + uint8_t default_amount; + int8_t pitch_separation; + uint8_t pitch_center; + uint8_t random_variation; + + } pan; + + struct Pitch { + + CPEnvelope envelope; + bool use_as_filter; + bool use_default_cutoff; + uint8_t default_cutoff; + bool use_default_resonance; + uint8_t default_resonance; + } pitch; + + }; + +private: + + + + Data data; + char name[MAX_NAME_LEN]; + +public: + + /* CPInstrument General */ + + const char *get_name(); + void set_name(const char *p_name); + + void set_sample_number(uint8_t p_note,uint8_t p_sample_id); + uint8_t get_sample_number(uint8_t p_note); + + void set_note_number(uint8_t p_note,uint8_t p_note_id); + uint8_t get_note_number(uint8_t p_note); + + void set_NNA_type(NNA_Type p_NNA_type); + NNA_Type get_NNA_type(); + + void set_DC_type(DC_Type p_DC_type); + DC_Type get_DC_type(); + + void set_DC_action(DC_Action p_DC_action); + DC_Action get_DC_action(); + + /* Volume */ + + void set_volume_global_amount(uint8_t p_amount); + uint8_t get_volume_global_amount(); + + void set_volume_fadeout(uint16_t p_amount); + uint16_t get_volume_fadeout(); + + void set_volume_random_variation(uint8_t p_amount); + uint8_t get_volume_random_variation(); + + /* Panning */ + + void set_pan_default_amount(uint8_t p_amount); + uint8_t get_pan_default_amount(); + + void set_pan_default_enabled(bool p_enabled); + bool is_pan_default_enabled(); + + void set_pan_pitch_separation(int8_t p_amount); + int8_t get_pan_pitch_separation(); + + void set_pan_pitch_center(uint8_t p_amount); + uint8_t get_pan_pitch_center(); + + void set_pan_random_variation(uint8_t p_amount); + uint8_t get_pan_random_variation(); + + /* Pitch / Filter */ + + void set_pitch_use_as_filter(bool p_enabled); + bool is_pitch_use_as_filter(); + + void set_filter_use_default_cutoff(bool p_enabled); + bool filter_use_default_cutoff(); + + void set_filter_default_cutoff(uint8_t p_amount); + uint8_t get_filter_default_cutoff(); + + void set_filter_use_default_resonance(bool p_enabled); + bool filter_use_default_resonance(); + + void set_filter_default_resonance(uint8_t p_amount); + uint8_t get_filter_default_resonance(); + + CPEnvelope* get_volume_envelope(); + CPEnvelope* get_pan_envelope(); + CPEnvelope* get_pitch_filter_envelope(); + + bool is_empty(); + + void reset(); + CPInstrument(); + +}; + + + +#endif + + diff --git a/modules/chibi/cp_loader.h b/modules/chibi/cp_loader.h new file mode 100644 index 000000000..163044448 --- /dev/null +++ b/modules/chibi/cp_loader.h @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* cp_loader.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_LOADER_H +#define CP_LOADER_H + + +#include "cp_song.h" +#include "cp_file_access_wrapper.h" +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ +class CPLoader { + +public: + + enum Error { + FILE_OK, + FILE_UNRECOGNIZED, + FILE_CANNOT_OPEN, + FILE_CORRUPTED, + FILE_OUT_OF_MEMORY, + }; + + + virtual bool can_load_song()=0; + virtual bool can_load_sample()=0; + virtual bool can_load_instrument()=0; + + virtual Error load_song(const char *p_file,CPSong *p_song,bool p_sampleset)=0; + virtual Error load_sample(const char *p_file,CPSample *p_sample)=0; + virtual Error load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx)=0; + + + virtual ~CPLoader() {} + +}; + +#endif diff --git a/modules/chibi/cp_loader_it.cpp b/modules/chibi/cp_loader_it.cpp new file mode 100644 index 000000000..bfffd9b50 --- /dev/null +++ b/modules/chibi/cp_loader_it.cpp @@ -0,0 +1,216 @@ +/*************************************************************************/ +/* cp_loader_it.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_loader_it.h" + +bool CPLoader_IT::can_load_song() { return true; } +bool CPLoader_IT::can_load_sample() { return true; } +bool CPLoader_IT::can_load_instrument() { return true; } + +CPLoader::Error CPLoader_IT::load_song(const char *p_file,CPSong *p_song, bool p_sampleset) { + + + song=p_song; + + if (file->open( p_file, CPFileAccessWrapper::READ )!=CPFileAccessWrapper::OK) + return CPLoader::FILE_CANNOT_OPEN; + + + Error err; + + char aux_identifier[4]; + file->get_byte_array((uint8_t*)aux_identifier,4); + + if ( aux_identifier[0]!='I' || + aux_identifier[1]!='M' || + aux_identifier[2]!='P' || + aux_identifier[3]!='M') { + + + CP_PRINTERR("IT CPLoader CPSong: Failed Identifier"); + return FILE_UNRECOGNIZED; + } + + + if (p_sampleset) { + + song->reset(false,true,true,false); + + if ((err=load_header(true))) { + file->close(); + return err; + } + + if ((err=load_samples())) { + file->close(); + return err; + } + + if ((err=load_instruments())) { + file->close(); + return err; + } + + return FILE_OK; + } + + song->reset(); + + if ((err=load_header(false))) { + file->close(); + return err; + } + + if ((err=load_orders())) { + file->close(); + return err; + } + + if ((err=load_patterns())) { + file->close(); + return err; + } + + if ((err=load_samples())) { + file->close(); + return err; + } + + if ((err=load_effects())) { + file->close(); + return err; + } + + if ((err=load_instruments())) { + file->close(); + return err; + } + + if ((err=load_message())) { + file->close(); + return err; + } + + file->close(); + return FILE_OK; + +} + + + + +CPLoader::Error CPLoader_IT::load_sample(const char *p_file,CPSample *p_sample) { + + if (file->open( p_file, CPFileAccessWrapper::READ )!=CPFileAccessWrapper::OK) + return CPLoader::FILE_CANNOT_OPEN; + + p_sample->reset(); + CPLoader::Error res=load_sample(p_sample); + + file->close(); + + return res; +} +CPLoader::Error CPLoader_IT::load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx) { + + CP_FAIL_INDEX_V(p_instr_idx,CPSong::MAX_INSTRUMENTS,CPLoader::FILE_CANNOT_OPEN); + + if (file->open( p_file, CPFileAccessWrapper::READ )!=CPFileAccessWrapper::OK) + return CPLoader::FILE_CANNOT_OPEN; + + + p_song->get_instrument( p_instr_idx )->reset(); + + + int samples=0; + CPLoader::Error res=load_instrument( p_song->get_instrument( p_instr_idx ), &samples ); + + if (res) { + file->close(); + return res; + } + + + char exchange[CPSong::MAX_SAMPLES]; + for (int i=0;i<CPSong::MAX_SAMPLES;i++) + exchange[i]=0; + + for (int i=0;i<samples;i++) { + + file->seek( 554+i*80 ); //i think this should work?! seems to.. but i'm not sure + + /* find free sample */ + + int free_idx=-1; + for (int s=0;s<CPSong::MAX_SAMPLES;s++) { + + if (p_song->get_sample( s )->get_sample_data().is_null()) { + free_idx=s; + break; + } + } + if (free_idx==-1) + break; //can't seem to be able to load more samples + + exchange[i]=free_idx; + res=load_sample( p_song->get_sample( free_idx ) ); + + if (res) { + + file->close(); + return res; + } + } + + for (int i=0;i<CPNote::NOTES;i++) { + + int smp=song->get_instrument(p_instr_idx)->get_sample_number(i); + + if (smp>=CPSong::MAX_SAMPLES) + continue; + + if (smp<0) + continue; + + smp=exchange[smp]; + + song->get_instrument(p_instr_idx)->set_sample_number(i,smp); + + } + + file->close(); + + return res; + +} + +CPLoader_IT::CPLoader_IT(CPFileAccessWrapper *p_file) { + + file=p_file; + +} diff --git a/modules/chibi/cp_loader_it.h b/modules/chibi/cp_loader_it.h new file mode 100644 index 000000000..5ce62a6a4 --- /dev/null +++ b/modules/chibi/cp_loader_it.h @@ -0,0 +1,125 @@ +/*************************************************************************/ +/* cp_loader_it.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CP_LOADER_IT_H +#define CP_LOADER_IT_H + +#include "cp_loader.h" +/** + *@author Juan Linietsky + */ + +/****************************** + loader_it.h + ---------- +Impulse Tracker Module CPLoader! +It lacks support for old +instrument files methinks... +and some other things like +midi. +********************************/ + +class AuxSampleData; //used for internal crap + +class CPLoader_IT : public CPLoader { + + + + CPFileAccessWrapper *file; + CPSong *song; + + struct IT_Header { + uint8_t blank01[2]; + uint16_t ordnum; + uint16_t insnum; + uint16_t smpnum; + uint16_t patnum; + uint16_t cwt; /* Created with tracker (y.xx = 0x0yxx) */ + uint16_t cmwt; /* Compatible with tracker ver > than val. */ + uint16_t flags; + uint16_t special; /* bit 0 set = song message attached */ + uint16_t msglength; + uint32_t msgoffset; + bool is_chibi; + }; + + /* Variables to store temp data */ + IT_Header header; + + /* CPSong Info Methods */ + Error load_header(bool p_dont_set); + Error load_orders(); + Error load_message(); + + /* CPPattern Methods */ + Error load_patterns(); + + /* CPSample Methods */ + + Error load_samples(); + Error load_sample(CPSample *p_sample); + CPSample_ID load_sample_data(AuxSampleData& p_sample_data); + + // CPSample decompression + + uint32_t read_n_bits_from_IT_compressed_block(uint8_t p_bits_to_read); + bool read_IT_compressed_block (bool p_16bits); + void free_IT_compressed_block (); + bool load_sample_8bits_IT_compressed(void *p_dest_buffer,int p_buffsize); + bool load_sample_16bits_IT_compressed(void *p_dest_buffer,int p_buffsize); + uint32_t *source_buffer; /* source buffer */ + uint32_t *source_position; /* actual reading position */ + uint8_t source_remaining_bits; /* bits remaining in read dword */ + uint8_t* pat_data; + + /* CPInstruments Methods */ + Error load_effects(); + Error load_instruments(); + Error load_instrument(CPInstrument *p_instrument,int *p_samples=0); + void load_envelope(CPEnvelope *p_envelope,bool*p_has_filter_flag=0); + + +public: + + + bool can_load_song(); + bool can_load_sample(); + bool can_load_instrument(); + + Error load_song(const char *p_file,CPSong *p_song, bool p_sampleset=false); + Error load_sample(const char *p_file,CPSample *p_sample); + Error load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx); + + CPLoader_IT(CPFileAccessWrapper *p_file); + +}; + + + +#endif diff --git a/modules/chibi/cp_loader_it_info.cpp b/modules/chibi/cp_loader_it_info.cpp new file mode 100644 index 000000000..a474fcd2f --- /dev/null +++ b/modules/chibi/cp_loader_it_info.cpp @@ -0,0 +1,268 @@ +/*************************************************************************/ +/* cp_loader_it_info.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_loader_it.h" + + + +CPLoader::Error CPLoader_IT::load_header(bool p_dont_set) { + + + char aux_songname[26]; + + file->get_byte_array((uint8_t*)aux_songname,26); + if (!p_dont_set) + song->set_name( aux_songname ); + + uint8_t aux_hlmin=file->get_byte(); + uint8_t aux_hlmaj=file->get_byte(); + + if (aux_hlmin==0) aux_hlmin=4; + if (aux_hlmaj==0) aux_hlmaj=16; + + if (!p_dont_set) { + song->set_row_highlight_minor( aux_hlmin ); + song->set_row_highlight_major( aux_hlmaj ); + } + + header.ordnum=file->get_word(); + header.insnum=file->get_word(); + header.smpnum=file->get_word(); + header.patnum=file->get_word(); + + header.cwt=file->get_word(); /* Created with tracker (y.xx = 0x0yxx) */ + header.cmwt=file->get_word(); /* Compatible with tracker ver > than val. */ + header.flags=file->get_word(); + + if (!p_dont_set) { + song->set_stereo( header.flags & 1 ); + song->set_linear_slides( header.flags & 8 ); + song->set_old_effects( header.flags & 16 ); + song->set_compatible_gxx( header.flags & 32 ); + song->set_instruments( header.flags & 4 ); + } + + + header.special=file->get_word(); + if (!p_dont_set) { + + song->set_global_volume( file->get_byte() ); + song->set_mixing_volume( file->get_byte() ); + song->set_speed( file->get_byte() ); + song->set_tempo( file->get_byte() ); + song->set_stereo_separation( file->get_byte() ); + + } else { + + file->get_byte(); // skip + file->get_byte(); // skip + file->get_byte(); // skip + file->get_byte(); // skip + file->get_byte(); // skip + } + file->get_byte(); // ZERO Byte + header.msglength=file->get_word(); + header.msgoffset=file->get_dword(); + char chibi[4]; + file->get_byte_array((uint8_t*)chibi,4); + header.is_chibi=(chibi[0]=='C' && chibi[1]=='H' && chibi[2]=='B' && chibi[3]=='I'); + + for (int i=0;i<64;i++) { + + uint8_t panbyte=file->get_byte(); + + uint8_t pan_dst=(panbyte<65) ? panbyte : 32; + bool surround_dst=(panbyte==100); + bool mute_dst=(panbyte>=128); + + if (!p_dont_set) { + song->set_channel_pan( i, pan_dst ); + song->set_channel_surround( i, surround_dst ); + song->set_channel_mute( i, mute_dst ); + } + } + for (int i=0;i<64;i++) { + unsigned char cv = file->get_byte(); + if (!p_dont_set) + song->set_channel_volume( i, cv ); + } + + CP_ERR_COND_V( file->eof_reached(),FILE_CORRUPTED ); + CP_ERR_COND_V( file->get_error(),FILE_CORRUPTED ); + + return FILE_OK; +} + +CPLoader::Error CPLoader_IT::load_effects() { + + if (!header.is_chibi) + return FILE_OK; //no effects, regular IT file + + /* GOTO End of IT header */ + file->seek(0xC0+header.ordnum+header.insnum*4+header.smpnum*4+header.patnum*4); + + + if (file->get_byte()>0) //not made with this version, ignore extended info + return FILE_OK; + + /* Chibitracker Extended info */ + + switch(file->get_byte()) { + + case CPSong::REVERB_MODE_ROOM: { + + song->set_reverb_mode( CPSong::REVERB_MODE_ROOM ); + } break; + case CPSong::REVERB_MODE_STUDIO_SMALL: { + + song->set_reverb_mode( CPSong::REVERB_MODE_STUDIO_SMALL ); + + } break; + case CPSong::REVERB_MODE_STUDIO_MEDIUM: { + + song->set_reverb_mode( CPSong::REVERB_MODE_STUDIO_MEDIUM ); + + } break; + case CPSong::REVERB_MODE_STUDIO_LARGE: { + + song->set_reverb_mode( CPSong::REVERB_MODE_STUDIO_LARGE ); + + } break; + case CPSong::REVERB_MODE_HALL: { + + song->set_reverb_mode( CPSong::REVERB_MODE_HALL ); + + } break; + case CPSong::REVERB_MODE_SPACE_ECHO: { + + song->set_reverb_mode( CPSong::REVERB_MODE_SPACE_ECHO ); + + } break; + + case CPSong::REVERB_MODE_ECHO: { + + song->set_reverb_mode( CPSong::REVERB_MODE_ECHO ); + + } break; + case CPSong::REVERB_MODE_DELAY: { + + song->set_reverb_mode( CPSong::REVERB_MODE_DELAY ); + + } break; + case CPSong::REVERB_MODE_HALF_ECHO: { + + song->set_reverb_mode( CPSong::REVERB_MODE_HALF_ECHO ); + + } break; + + } + + //chorus + song->set_chorus_speed_hz10( file->get_byte() ); + song->set_chorus_delay_ms( file->get_byte() ); + song->set_chorus_depth_ms10( file->get_byte() ); + song->set_chorus_separation_ms( file->get_byte() ); + + for (int i=0;i<CPPattern::WIDTH;i++) { + song->set_channel_reverb(i,file->get_byte()); + } + for (int i=0;i<CPPattern::WIDTH;i++) { + song->set_channel_chorus(i,file->get_byte()); + } + + return FILE_OK; + +} + +CPLoader::Error CPLoader_IT::load_message() { + + + if (!(header.special & 1)) { + + return FILE_OK; + } + + + file->seek(header.msgoffset); + + //(void*)tmpmsg=malloc(header.msglength+1); + + char message[8000]; + + + char *tmpmsg = message; + + file->get_byte_array((uint8_t*)tmpmsg,header.msglength); + tmpmsg[header.msglength]=0; + + for (int i=0;i<header.msglength;i++) if (tmpmsg[i]=='\r') tmpmsg[i]='\n'; + + song->set_message(tmpmsg); + + return FILE_OK; +} + +CPLoader::Error CPLoader_IT::load_orders() { + + file->seek(0xC0); + + + for (int i=0;i<header.ordnum;i++) { + + uint8_t aux_order=file->get_byte(); + CPOrder order=CP_ORDER_NONE; + + + if (i>=CPSong::MAX_ORDERS) + continue; + if (aux_order==254) { + + order=CP_ORDER_BREAK; + + } else if (aux_order<200) { + + order=aux_order; + //nothing! + + } + song->set_order(i,order); + + } + + if (file->eof_reached() || file->get_error()) { + + + return FILE_CORRUPTED; + + } + + return FILE_OK; +} + + + diff --git a/modules/chibi/cp_loader_it_instruments.cpp b/modules/chibi/cp_loader_it_instruments.cpp new file mode 100644 index 000000000..446e841c5 --- /dev/null +++ b/modules/chibi/cp_loader_it_instruments.cpp @@ -0,0 +1,224 @@ +/*************************************************************************/ +/* cp_loader_it_instruments.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_loader_it.h" + +enum EnvFlags { + ENV_ON=1, + ENV_LOOP=2, + ENV_SUSLOOP=4, + ENV_CARRY=8, + ENV_FILTER=128 +}; + +void CPLoader_IT::load_envelope(CPEnvelope *p_envelope,bool*p_has_filter_flag) { + + uint8_t flags=file->get_byte(); + uint8_t points=file->get_byte(); + uint8_t begin=file->get_byte(); + uint8_t end=file->get_byte(); + uint8_t susbegin=file->get_byte(); + uint8_t susend=file->get_byte(); + + p_envelope->reset(); + + for (int i=0;i<25;i++) { + + uint8_t height=file->get_byte(); + int8_t &signed_height=(int8_t&)height; + uint16_t tick=file->get_word(); + + if (i>=points) + continue; + p_envelope->add_position( tick, signed_height ); + + } + + p_envelope->set_enabled( flags & ENV_ON ); + p_envelope->set_carry_enabled( flags & ENV_CARRY); + + p_envelope->set_loop_enabled( flags & ENV_LOOP ); + p_envelope->set_loop_begin( begin ); + p_envelope->set_loop_end( end ); + + p_envelope->set_sustain_loop_enabled( flags & ENV_SUSLOOP ); + p_envelope->set_sustain_loop_begin( susbegin ); + p_envelope->set_sustain_loop_end( susend ); + + if (p_has_filter_flag) + *p_has_filter_flag=flags&ENV_FILTER; + + file->get_byte(); //zerobyte + + //fill with stuff if the envelope hass less than 2 points + while(p_envelope->get_node_count()<2) { + + p_envelope->add_position( 30*p_envelope->get_node_count(), p_envelope->get_min()==0 ? 64 : 0, false ); + } +} + + +CPLoader::Error CPLoader_IT::load_instrument(CPInstrument *p_instrument,int *p_samples) { + + + + char aux_header[4]; + + file->get_byte_array((uint8_t*)aux_header,4); + + + if ( aux_header[0]!='I' || + aux_header[1]!='M' || + aux_header[2]!='P' || + aux_header[3]!='I') { + CP_PRINTERR("IT CPLoader CPInstrument: Failed Identifier"); + + return FILE_UNRECOGNIZED; + } + + + + // Ignore deprecated 8.3 filename field + for (int i=0;i<12;i++) file->get_byte(); + + //Ignore zerobyte + file->get_byte(); /* (byte) CPInstrument type (always 0) */ + + switch( file->get_byte() ) { /* New CPNote Action [0,1,2,3] */ + case 0: p_instrument->set_NNA_type( CPInstrument::NNA_NOTE_CUT ) ; break; + case 1: p_instrument->set_NNA_type( CPInstrument::NNA_NOTE_CONTINUE ) ; break; + case 2: p_instrument->set_NNA_type( CPInstrument::NNA_NOTE_OFF ) ; break; + case 3: p_instrument->set_NNA_type( CPInstrument::NNA_NOTE_FADE ) ; break; + }; + switch( file->get_byte() ) { // Duplicate Check Type + case 0: p_instrument->set_DC_type( CPInstrument::DCT_DISABLED ); break ; + case 1: p_instrument->set_DC_type( CPInstrument::DCT_NOTE ); break ; + case 2: p_instrument->set_DC_type( CPInstrument::DCT_SAMPLE ); break ; + case 3: p_instrument->set_DC_type( CPInstrument::DCT_INSTRUMENT ); break ; + } + switch( file->get_byte() ) { //Duplicate Check Action + case 0: p_instrument->set_DC_action( CPInstrument::DCA_NOTE_CUT ); break ; + case 1: p_instrument->set_DC_action( CPInstrument::DCA_NOTE_OFF ); break ; + case 2: p_instrument->set_DC_action( CPInstrument::DCA_NOTE_FADE ); break ; + } + + int fade = file->get_word(); + //intf("AFADE: %i\n",fade); + if (fade>CPInstrument::MAX_FADEOUT) //needs to be clipped because of horrible modplug doings + fade=CPInstrument::MAX_FADEOUT; + + p_instrument->set_volume_fadeout( fade ); + p_instrument->set_pan_pitch_separation( file->get_byte() ); + p_instrument->set_pan_pitch_center( file->get_byte() ); + p_instrument->set_volume_global_amount( file->get_byte() ); + uint8_t pan=file->get_byte(); + p_instrument->set_pan_default_amount(pan&0x7F); + p_instrument->set_pan_default_enabled( !(pan&0x80) ); + p_instrument->set_volume_random_variation( file->get_byte() ); + p_instrument->set_pan_random_variation( file->get_byte() ); + + + + file->get_word(); //empty (version) + uint8_t samples=file->get_byte(); + if (p_samples) + *p_samples=samples; + file->get_byte(); //empty + char aux_name[26]; + file->get_byte_array((uint8_t*)aux_name,26); + p_instrument->set_name(aux_name); + + uint8_t cutoff=file->get_byte(); + + p_instrument->set_filter_default_cutoff(cutoff&0x7F); + p_instrument->set_filter_use_default_cutoff(cutoff&0x80); + + uint8_t resonance=file->get_byte(); + + p_instrument->set_filter_default_resonance(resonance&0x7F); + p_instrument->set_filter_use_default_resonance(resonance&0x80); + + file->get_dword(); //MIDI, IGNORED! + + /* CPNote -> CPSample table */ + for (uint8_t i=0;i<CPNote::NOTES;i++) { + + + uint8_t note=file->get_byte(); + if (note>=CPNote::NOTES) + note=0; + p_instrument->set_note_number(i,note); + + uint8_t samp=file->get_byte(); + if (samp==0 || samp>99) + samp=CPNote::EMPTY; + else + samp--; + + + p_instrument->set_sample_number(i,samp); + + + } + + + load_envelope( p_instrument->get_volume_envelope() ); + load_envelope( p_instrument->get_pan_envelope() ); + bool use_as_filter; + load_envelope( p_instrument->get_pitch_filter_envelope(), &use_as_filter ); + p_instrument->set_pitch_use_as_filter( use_as_filter ); + + return FILE_OK; + +} + + +CPLoader::Error CPLoader_IT::load_instruments() { + + + for (int i=0;i<header.insnum;i++) { + + + file->seek(0xC0+header.ordnum+i*4); + uint32_t final_location=file->get_dword(); + file->seek( final_location ); + + Error err=load_instrument( song->get_instrument( i ) ); + if (err) + return err; + + } + + return FILE_OK; + + if (file->eof_reached() || file->get_error()) + return FILE_CORRUPTED; +} + + diff --git a/modules/chibi/cp_loader_it_patterns.cpp b/modules/chibi/cp_loader_it_patterns.cpp new file mode 100644 index 000000000..528d99fff --- /dev/null +++ b/modules/chibi/cp_loader_it_patterns.cpp @@ -0,0 +1,166 @@ +/*************************************************************************/ +/* cp_loader_it_patterns.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_loader_it.h" + + +CPLoader::Error CPLoader_IT::load_patterns() { + + + for (int i=0;i<header.patnum;i++) { + + if (i>=CPSong::MAX_PATTERNS) + break; + + /* Position where pattern offsets are stored */ + file->seek(0xC0+header.ordnum+header.insnum*4+header.smpnum*4+i*4); + uint32_t pattern_offset=file->get_dword(); + + if (pattern_offset==0) { + + continue; + } + + uint16_t pat_size; + uint16_t pat_length; + + int row=0,flag,channel,j; + uint8_t aux_byte; + uint32_t reserved; + uint8_t chan_mask[64]; //mask cache for each + CPNote last_value[64]; //last value of each + + for (j=0;j<64;j++) { + + chan_mask[j]=0; + last_value[j].clear(); + } + + file->seek(pattern_offset); + + pat_size=file->get_word(); + pat_length=file->get_word(); + reserved=file->get_dword(); + + song->get_pattern(i)->set_length( pat_length ); + + do { + + aux_byte=file->get_byte(); + flag=aux_byte; + + if ( flag==0 ) { + + row++; + } else { + + channel=(flag-1) & 63; + + if ( flag & 128 ) { + + aux_byte=file->get_byte(); + chan_mask[channel]=aux_byte; + } + + CPNote note; //note used for reading + + if ( chan_mask[channel]&1 ) { // read note + + aux_byte=file->get_byte(); + + if ( aux_byte<120 ) + note.note=aux_byte; + else if ( aux_byte==255 ) + note.note=CPNote::OFF; + else if ( aux_byte==254 ) + note.note=CPNote::CUT; + + last_value[channel].note=note.note; + } + + + if ( chan_mask[channel]&2 ) { + + aux_byte=file->get_byte(); + if ( aux_byte<100 ) + note.instrument=aux_byte-1; + + last_value[channel].instrument=note.instrument; + } + if ( chan_mask[channel]&4 ) { + + aux_byte=file->get_byte(); + if ( aux_byte<213 ) + note.volume=aux_byte; + + last_value[channel].volume=note.volume; + } + if ( chan_mask[channel]&8 ) { + + aux_byte=file->get_byte(); + if ( aux_byte>0 ) + note.command=aux_byte-1; + + + last_value[channel].command=note.command; + + note.parameter=file->get_byte(); + + last_value[channel].parameter=note.parameter; + } + + if ( chan_mask[channel]&16 ) { + + note.note=last_value[channel].note; + } + + if ( chan_mask[channel]&32 ) { + + note.instrument=last_value[channel].instrument; + } + if ( chan_mask[channel]&64 ) { + + note.volume=last_value[channel].volume; + } + if ( chan_mask[channel]&128 ) { + + note.command=last_value[channel].command; + note.parameter=last_value[channel].parameter; + } + + song->get_pattern(i)->set_note(channel,row,note); + } + + + } while(row<pat_length); + + } + + return FILE_OK; +} + diff --git a/modules/chibi/cp_loader_it_samples.cpp b/modules/chibi/cp_loader_it_samples.cpp new file mode 100644 index 000000000..c736c99c0 --- /dev/null +++ b/modules/chibi/cp_loader_it_samples.cpp @@ -0,0 +1,620 @@ +/*************************************************************************/ +/* cp_loader_it_samples.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_loader_it.h" +#include "cp_sample.h" + +struct AuxSampleData { + + + uint32_t fileofs; + uint32_t c5spd; + uint32_t length; + uint32_t loop_begin; + uint32_t loop_end; + bool loop_enabled; + bool pingpong_enabled; + bool is16bit; + bool stereo; + bool exists; + bool compressed; + +}; + + +enum IT_Sample_Flags { + + IT_SAMPLE_EXISTS=1, + IT_SAMPLE_16BITS=2, + IT_SAMPLE_STEREO=4, + IT_SAMPLE_COMPRESSED=8, + IT_SAMPLE_LOOPED=16, + IT_SAMPLE_SUSTAIN_LOOPED=32, + IT_SAMPLE_LOOP_IS_PINGPONG=64, + IT_SAMPLE_SUSTAIN_LOOP_IS_PINGPONG=128 +}; + + +CPLoader::Error CPLoader_IT::load_sample(CPSample *p_sample) { + + + AuxSampleData aux_sample_data; + + char aux_header[4]; + + file->get_byte_array((uint8_t*)aux_header,4); + + if ( aux_header[0]!='I' || + aux_header[1]!='M' || + aux_header[2]!='P' || + aux_header[3]!='S') { + + //CP_PRINTERR("IT CPLoader CPSample: Failed Identifier"); + return FILE_UNRECOGNIZED; + } + + + // Ignore deprecated 8.3 filename + for (int i=0;i<12;i++) file->get_byte(); + + file->get_byte(); //ignore zerobyte + + p_sample->set_global_volume( file->get_byte() ); + + /* SAMPLE FLAGS */ + uint8_t flags=file->get_byte(); + aux_sample_data.loop_enabled=flags&IT_SAMPLE_LOOPED; + aux_sample_data.pingpong_enabled=flags&IT_SAMPLE_LOOP_IS_PINGPONG; + aux_sample_data.is16bit=flags&IT_SAMPLE_16BITS; + aux_sample_data.exists=flags&IT_SAMPLE_EXISTS; + aux_sample_data.stereo=flags&IT_SAMPLE_STEREO; + aux_sample_data.compressed=flags&IT_SAMPLE_COMPRESSED; + + p_sample->set_default_volume(file->get_byte()); + /* SAMPLE NAME */ + char aux_name[26]; + file->get_byte_array((uint8_t*)aux_name,26); + p_sample->set_name(aux_name); + + // ?? + uint8_t convert_flag=file->get_byte(); + // PAN + uint8_t pan=file->get_byte(); + p_sample->set_pan( pan&0x7F ); + p_sample->set_pan_enabled( pan & 0x80 ); + + aux_sample_data.length=file->get_dword(); + + + aux_sample_data.loop_begin= file->get_dword(); + aux_sample_data.loop_end= file->get_dword(); + aux_sample_data.c5spd=file->get_dword(); + /*p_sample->data.set_sustain_loop_begin=*/file->get_dword(); + /*p_sample->data.sustain_loop_end=*/file->get_dword(); + aux_sample_data.fileofs=file->get_dword(); + p_sample->set_vibrato_speed( file->get_byte() ); + p_sample->set_vibrato_depth( file->get_byte() ); + p_sample->set_vibrato_rate( file->get_byte() ); + switch( file->get_byte() ) { + /* Vibrato Wave: 0=sine, 1=rampdown, 2=square, 3=random */ + case 0: p_sample->set_vibrato_type( CPSample::VIBRATO_SINE ); break; + case 1: p_sample->set_vibrato_type( CPSample::VIBRATO_SAW ); break; + case 2: p_sample->set_vibrato_type( CPSample::VIBRATO_SQUARE ); break; + case 3: p_sample->set_vibrato_type( CPSample::VIBRATO_RANDOM ); break; + default: p_sample->set_vibrato_type( CPSample::VIBRATO_SINE ); break; + } + + //printf("Name %s - Flags: fileofs :%i - c5spd %i - len %i 16b %i - data?: %i\n",p_sample->get_name(),aux_sample_data.fileofs,aux_sample_data.c5spd, aux_sample_data.length, aux_sample_data.is16bit,aux_sample_data.exists); + CPSample_ID samp_id; + + if (aux_sample_data.exists) { + samp_id=load_sample_data(aux_sample_data); + CPSampleManager::get_singleton()->set_c5_freq(samp_id,aux_sample_data.c5spd); + CPSampleManager::get_singleton()->set_loop_begin( samp_id,aux_sample_data.loop_begin ); + CPSampleManager::get_singleton()->set_loop_end( samp_id,aux_sample_data.loop_end ); + CPSample_Loop_Type loop_type=aux_sample_data.loop_enabled?( aux_sample_data.pingpong_enabled? CP_LOOP_BIDI: CP_LOOP_FORWARD):CP_LOOP_NONE; + CPSampleManager::get_singleton()->set_loop_end( samp_id,aux_sample_data.loop_end ); + CPSampleManager::get_singleton()->set_loop_type( samp_id, loop_type); + + } + + //printf("Loaded id is null?: %i\n",samp_id.is_null()); + p_sample->set_sample_data(samp_id); + if (!samp_id.is_null()) { + + //printf("Loaded ID: stereo: %i len %i 16bit %i\n",CPSampleManager::get_singleton()->is_stereo(samp_id), CPSampleManager::get_singleton()->get_size( samp_id), CPSampleManager::get_singleton()->is_16bits( samp_id) ); + } + + CP_ERR_COND_V( file->eof_reached(),FILE_CORRUPTED ); + CP_ERR_COND_V( file->get_error(),FILE_CORRUPTED ); + + return FILE_OK; + +} + +CPSample_ID CPLoader_IT::load_sample_data(AuxSampleData& p_sample_data) { + + + int aux_sample_properties = (p_sample_data.is16bit?IT_SAMPLE_16BITS:0)|(p_sample_data.compressed?IT_SAMPLE_COMPRESSED:0)|(p_sample_data.stereo?IT_SAMPLE_STEREO:0); + + file->seek(p_sample_data.fileofs); + + CPSampleManager *sm=CPSampleManager::get_singleton(); + + CPSample_ID id; + + switch (aux_sample_properties) { + + case (0): // 8 bits, mono + case (IT_SAMPLE_16BITS): // 16 bits mono + case (IT_SAMPLE_STEREO): // 8 bits stereo + case (IT_SAMPLE_16BITS|IT_SAMPLE_STEREO): { // 16 bits mono + + id=sm->create(p_sample_data.is16bit,p_sample_data.stereo,p_sample_data.length); + if (id.is_null()) + break; + + sm->lock_data(id); + + int16_t *ptr16 = (int16_t*)sm->get_data(id); + int8_t *ptr8=(int8_t*)ptr16; + + int chans=p_sample_data.stereo?2:1; + + if (p_sample_data.is16bit) { + + for (int c=0;c<chans;c++) { + + for (int i=0;i<p_sample_data.length;i++) { + + ptr16[i*chans+c]=file->get_word(); + } + } + } else { + + for (int c=0;c<chans;c++) { + + for (int i=0;i<p_sample_data.length;i++) { + + ptr8[i*chans+c]=file->get_byte(); + } + } + + } + + sm->unlock_data(id); + + } break; + case (IT_SAMPLE_COMPRESSED): { // 8 bits compressed + + + id=sm->create(false,false,p_sample_data.length); + if (id.is_null()) + break; + sm->lock_data(id); + + if ( load_sample_8bits_IT_compressed((void*)sm->get_data( id),p_sample_data.length) ) { + + sm->unlock_data(id); + sm->destroy(id); + + break; + } + + sm->unlock_data(id); + + + } break; + case (IT_SAMPLE_16BITS|IT_SAMPLE_COMPRESSED): { // 16 bits compressed + + + id=sm->create(true,false,p_sample_data.length); + if (id.is_null()) + break; + sm->lock_data(id); + + if ( load_sample_16bits_IT_compressed((void*)sm->get_data(id),p_sample_data.length) ) { + + sm->unlock_data(id); + sm->destroy(id); + break; + } + + sm->unlock_data(id); + + } break; + default: { + + // I dont know how to handle stereo compressed, does that exist? + } break; + + } + + + return id; +} + + +CPLoader::Error CPLoader_IT::load_samples() { + + for (int i=0;i<header.smpnum;i++) { + + //seek to sample + file->seek(0xC0+header.ordnum+header.insnum*4+i*4); + + uint32_t final_location=file->get_dword(); + file->seek( final_location ); + + + Error err=load_sample(song->get_sample(i)); + CP_ERR_COND_V(err,err); + + } + + if (file->eof_reached() || file->get_error()) + return FILE_CORRUPTED; + + return FILE_OK; +} +/* * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE + + -The following sample decompression code is based on xmp's code.(http://xmp.helllabs.org) which is based in openCP code. + +* NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE * NOTICE */ + +uint32_t CPLoader_IT::read_n_bits_from_IT_compressed_block (uint8_t p_bits_to_read) { + + uint32_t aux_return_value; + uint32_t val; + + uint8_t *buffer=(uint8_t*)source_position; + if ( p_bits_to_read <= source_remaining_bits ) { + + val=buffer[3]; + val<<=8; + val|=buffer[2]; + val<<=8; + val|=buffer[1]; + val<<=8; + val|=buffer[0]; + + aux_return_value = val & ((1 << p_bits_to_read) - 1); + val >>= p_bits_to_read; + source_remaining_bits -= p_bits_to_read; + + buffer[3]=val>>24; + buffer[2]=(val>>16)&0xFF; + buffer[1]=(val>>8)&0xFF; + buffer[0]=(val)&0xFF; + + } else { + aux_return_value=buffer[3]; + aux_return_value<<=8; + aux_return_value|=buffer[2]; + aux_return_value<<=8; + aux_return_value|=buffer[1]; + aux_return_value<<=8; + aux_return_value|=buffer[0]; + + uint32_t nbits = p_bits_to_read - source_remaining_bits; + source_position++; + + buffer+=4; + val=buffer[3]; + val<<=8; + val|=buffer[2]; + val<<=8; + val|=buffer[1]; + val<<=8; + val|=buffer[0]; + aux_return_value |= ((val & ((1 << nbits) - 1)) << source_remaining_bits); + val >>= nbits; + source_remaining_bits = 32 - nbits; + buffer[3]=val>>24; + buffer[2]=(val>>16)&0xFF; + buffer[1]=(val>>8)&0xFF; + buffer[0]=(val)&0xFF; + + } + + return aux_return_value; +} + +bool CPLoader_IT::read_IT_compressed_block (bool p_16bits) { + + uint16_t size; + + size=file->get_word(); + + if (file->eof_reached() || file->get_error()) return true; + + pat_data = (uint8_t*)CP_ALLOC( 4* ((size >> 2) + 2) ); + if (!pat_data) + return true; + + + source_buffer=(uint32_t*)pat_data; + file->get_byte_array((uint8_t*)source_buffer,size); + + if (file->eof_reached() || file->get_error()) { + + free_IT_compressed_block(); + return true; + } + + source_position = source_buffer; + source_remaining_bits = 32; + + return false; +} + +void CPLoader_IT::free_IT_compressed_block () { + + + if (pat_data) { + CP_FREE(pat_data); + pat_data=NULL; + } + +} + +bool CPLoader_IT::load_sample_8bits_IT_compressed(void *p_dest_buffer,int p_buffsize) { + + int8_t *dest_buffer; /* destination buffer which will be returned */ + uint16_t block_length; /* length of compressed data block in samples */ + uint16_t block_position; /* position in block */ + uint8_t bit_width; /* actual "bit width" */ + uint16_t aux_value; /* value read from file to be processed */ + int8_t d1, d2; /* integrator buffers (d2 for it2.15) */ + int8_t *dest_position; /* position in output buffer */ + int8_t v; /* sample value */ + bool it215; // is this an it215 module? + + dest_buffer = (int8_t *) p_dest_buffer; + + if (dest_buffer==NULL) + return true; + + for (int i=0;i<p_buffsize;i++) + dest_buffer[i]=0; + + + dest_position = dest_buffer; + + it215=(header.cmwt==0x215); + + /* now unpack data till the dest buffer is full */ + + while (p_buffsize) { + /* read a new block of compressed data and reset variables */ + if ( read_IT_compressed_block(false) ) { + CP_PRINTERR("Out of memory decompressing IT CPSample"); + return true; + } + + + block_length = (p_buffsize < 0x8000) ? p_buffsize : 0x8000; + + block_position = 0; + + bit_width = 9; /* start with width of 9 bits */ + + d1 = d2 = 0; /* reset integrator buffers */ + + /* now uncompress the data block */ + while ( block_position < block_length ) { + + aux_value = read_n_bits_from_IT_compressed_block(bit_width); /* read bits */ + + if ( bit_width < 7 ) { /* method 1 (1-6 bits) */ + + if ( aux_value == (1 << (bit_width - 1)) ) { /* check for "100..." */ + + aux_value = read_n_bits_from_IT_compressed_block(3) + 1; /* yes -> read new width; */ + bit_width = (aux_value < bit_width) ? aux_value : aux_value + 1; + /* and expand it */ + continue; /* ... next value */ + } + + } else if ( bit_width < 9 ) { /* method 2 (7-8 bits) */ + + uint8_t border = (0xFF >> (9 - bit_width)) - 4; + /* lower border for width chg */ + + if ( aux_value > border && aux_value <= (border + 8) ) { + + aux_value -= border; /* convert width to 1-8 */ + bit_width = (aux_value < bit_width) ? aux_value : aux_value + 1; + /* and expand it */ + continue; /* ... next value */ + } + + + } else if ( bit_width == 9 ) { /* method 3 (9 bits) */ + + if ( aux_value & 0x100 ) { /* bit 8 set? */ + + bit_width = (aux_value + 1) & 0xff; /* new width... */ + continue; /* ... and next value */ + } + + } else { /* illegal width, abort */ + + + free_IT_compressed_block(); + CP_PRINTERR("CPSample has illegal BitWidth "); + return true; + } + + /* now expand value to signed byte */ + if ( bit_width < 8 ) { + + uint8_t tmp_shift = 8 - bit_width; + + v=(aux_value << tmp_shift); + v>>=tmp_shift; + + } else v = (int8_t) aux_value; + + /* integrate upon the sample values */ + d1 += v; + d2 += d1; + + /* ... and store it into the buffer */ + *(dest_position++) = it215 ? d2 : d1; + block_position++; + + } + + /* now subtract block lenght from total length and go on */ + free_IT_compressed_block(); + p_buffsize -= block_length; + } + + + return false; +} + +bool CPLoader_IT::load_sample_16bits_IT_compressed(void *p_dest_buffer,int p_buffsize) { + + int16_t *dest_buffer; /* destination buffer which will be returned */ + uint16_t block_length; /* length of compressed data block in samples */ + uint16_t block_position; /* position in block */ + uint8_t bit_width; /* actual "bit width" */ + uint32_t aux_value; /* value read from file to be processed */ + int16_t d1, d2; /* integrator buffers (d2 for it2.15) */ + int16_t *dest_position; /* position in output buffer */ + int16_t v; /* sample value */ + + bool it215; // is this an it215 module? + + dest_buffer = (int16_t *) p_dest_buffer; + + if (dest_buffer==NULL) + return true; + + for (int i=0;i<p_buffsize;i++) + dest_buffer[i]=0; + + dest_position = dest_buffer; + + it215=(header.cmwt==0x215); + + + while (p_buffsize) { + /* read a new block of compressed data and reset variables */ + if ( read_IT_compressed_block(true) ) { + + return true; + } + + + block_length = (p_buffsize < 0x4000) ? p_buffsize : 0x4000; + + block_position = 0; + + bit_width = 17; /* start with width of 9 bits */ + + d1 = d2 = 0; /* reset integrator buffers */ + + while ( block_position < block_length ) { + + aux_value = read_n_bits_from_IT_compressed_block(bit_width); /* read bits */ + + if ( bit_width < 7 ) { /* method 1 (1-6 bits) */ + + if ( (signed)aux_value == (1 << (bit_width - 1)) ) { /* check for "100..." */ + + aux_value = read_n_bits_from_IT_compressed_block(4) + 1; /* yes -> read new width; */ + bit_width = (aux_value < bit_width) ? aux_value : aux_value + 1; + /* and expand it */ + continue; /* ... next value */ + } + + } else if ( bit_width < 17 ) { + + uint16_t border = (0xFFFF >> (17 - bit_width)) - 8; + + if ( (int)aux_value > (int)border && (int)aux_value <= ((int)border + 16) ) { + + aux_value -= border; /* convert width to 1-8 */ + bit_width = (aux_value < bit_width) ? aux_value : aux_value + 1; + /* and expand it */ + continue; /* ... next value */ + } + + + } else if ( bit_width == 17 ) { + + if ( aux_value & 0x10000 ) { /* bit 8 set? */ + + bit_width = (aux_value + 1) & 0xff; /* new width... */ + continue; /* ... and next value */ + } + + } else { /* illegal width, abort */ + + CP_PRINTERR("CPSample has illegal BitWidth "); + + free_IT_compressed_block(); + + return true; + } + + /* now expand value to signed byte */ + if ( bit_width < 16 ) { + + uint8_t tmp_shift = 16 - bit_width; + + v=(aux_value << tmp_shift); + v>>=tmp_shift; + + } else v = (int16_t) aux_value; + + /* integrate upon the sample values */ + d1 += v; + d2 += d1; + + /* ... and store it into the buffer */ + *(dest_position++) = it215 ? d2 : d1; + block_position++; + + } + + /* now subtract block lenght from total length and go on */ + free_IT_compressed_block(); + p_buffsize -= block_length; + } + + + return false; + +} + + + diff --git a/modules/chibi/cp_loader_mod.cpp b/modules/chibi/cp_loader_mod.cpp new file mode 100644 index 000000000..cfa3e3473 --- /dev/null +++ b/modules/chibi/cp_loader_mod.cpp @@ -0,0 +1,482 @@ +/*************************************************************************/ +/* cp_loader_mod.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_loader_mod.h" + + +static bool tag_equal_to(const char *p_tag, const char *p_string) { + + return( p_tag[0]==p_string[0] && + p_tag[1]==p_string[1] && + p_tag[2]==p_string[2] && + p_tag[3]==p_string[3]); +} +/* ProTracker period table */ +uint16_t period_table[6*12] = { + 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907, + 856,808,762,720,678,640,604,570,538,508,480,453, + 428,404,381,360,339,320,302,285,269,254,240,226, + 214,202,190,180,170,160,151,143,135,127,120,113, + 107,101,95,90,85,80,75,71,67,63,60,56, + 53,50,47,45,42,40,37,35,33,31,30,28 +}; + + +CPLoader::Error CPLoader_MOD::load_song(const char *p_file,CPSong *p_song,bool p_sampleset) { + + if (file->open(p_file,CPFileAccessWrapper::READ)) { + //printf("Can't open file! %s\n",p_file); + return FILE_CANNOT_OPEN; + }; + + /* FIRST OF ALL, one needs to read the .mod file format tag */ + file->seek( 1080 ); //located at 1080 + + char format_tag[4]; + + file->get_byte_array( (uint8_t*)format_tag, 4 ); + + int channels=-1; + + /** THE PAIN!! - COMPARE TAGS */ + + /* Classic 4-chan */ + if (tag_equal_to(format_tag,"M.K.") ) + channels=4; + if (tag_equal_to(format_tag,"FLT4") ) + channels=4; + if (tag_equal_to(format_tag,"M!K!") ) + channels=4; + + /* 8 Channel MODS */ + + if (tag_equal_to(format_tag,"FLT8") ) + channels=2; + + if (tag_equal_to(format_tag,"CD81") ) + channels=2; + + /* Custom channel MODS */ + + for (int i=1;i<=32;i++) { + + if (i<10) { // up to 9 channels mods + + /* Old Take Tracker */ + char old_take_tracker[4]={'T','D','Z',char('0'+i)}; + + if (tag_equal_to(format_tag,old_take_tracker)) { + + channels=i; + break; + } + + /* Contemplates many XCHN Formats */ + char xchn[4]={char('0'+i),'C','H','N'}; + + if (tag_equal_to(format_tag,xchn)) { + + channels=i; + break; + } + } + + /* Fast Tracker */ + char fast_tracker[4]={char('0'+(i/10)),char('0'+(i%10)),'C','H'}; + + if (tag_equal_to(format_tag,fast_tracker)) { + + channels=i; + break; + } + + } + + + if (channels==-1) { + + file->close(); + return FILE_UNRECOGNIZED; + } + + + + /** Load CPSong INFO */ + + file->seek( 0 ); //go to begining of file + + file->set_endian_conversion( true ); + p_song->reset(); + p_song->set_instruments( false ); + + char name[21]; + + file->get_byte_array( (uint8_t*)name,20); + name[20]=0; + + p_song->set_name(name); + p_song->set_old_effects( true ); + p_song->set_linear_slides( false ); + p_song->set_compatible_gxx( true ); + + + + CPSampleManager *sm=CPSampleManager::get_singleton(); + + int instruments=31; + + for (int i=0;i<instruments;i++) { + + char sample_name[23]; + file->get_byte_array( (uint8_t*)sample_name,22); + sample_name[22]=0; + + uint32_t sample_len=file->get_word(); + sample_len<<=1; + + uint8_t fine_nibble=file->get_byte()&0xF; + + + //(int8_t)(fine_nibble & 7) - (int8_t)(fine_nibble & 8); //yesso's genius trick + // boo, I can't use it :( but i leave it here because of how cool it is + uint8_t linear_volume=file->get_byte(); //0 .. ? + + uint32_t loop_begin=file->get_word(); //0 .. ? + loop_begin<<=1; + uint32_t loop_end=file->get_word(); //0 .. ? + loop_end<<=1; + + if (sample_len>0) { + + CPSample_ID sid=sm->create( false, false, sample_len ); + + if (sid.is_null()) { + + file->close(); + return FILE_OUT_OF_MEMORY; + } + + if (loop_end>2) { + sm->set_loop_begin( sid, loop_begin ); + sm->set_loop_end( sid, loop_end+loop_begin ); + sm->set_loop_type( sid,CP_LOOP_FORWARD ); + } + static const uint16_t fine_to_freq[16]={ + 8363,8413,8463,8529,8581,8651,8723,8757, + 7895,7941,7985,8046,8107,8169,8232,8280 + }; + + sm->set_c5_freq( sid, fine_to_freq[fine_nibble] ); + p_song->get_sample(i)->set_sample_data(sid); + } + + p_song->get_sample(i)->set_name(sample_name); + p_song->get_sample(i)->set_default_volume( linear_volume ); + + + + } + + /* pan for MODs */ + for (int i=0;i<channels;i++) + p_song->set_channel_pan( i, (((i&3)==1) || ((i&3)==2)) ? 0: 64); + + + uint8_t order_count=file->get_byte(); + //uint8_t loop_to=file->get_byte(); + + + int pattern_count=0; + + for (int i=0;i<128;i++) { + + uint8_t order=file->get_byte(); + + + if (i<order_count) { + p_song->set_order(i,order); + + /* Determine the amount of patterns */ + if ((order+1)>pattern_count) + pattern_count=order+1; + } else + p_song->set_order( i, CP_ORDER_NONE ); + } + + if (instruments==31) + file->get_dword(); // identiefier, now skip it + + for (int i=0;i<pattern_count;i++) { + + for(int line=0;line<64;line++) { + + for(int column=0;column<channels;column++) { + + uint32_t note_w=file->get_dword(); + + CPNote note; + + note.instrument=(note_w>>12)&0xF; + note.instrument|=(note_w>>24)&0xF0; + + if (note.instrument==0) + note.instrument=CPNote::EMPTY; + else + note.instrument--; + + note.parameter=note_w&0xFF; + + int cmd=(note_w>>8)&0xF; + + uint32_t period=(note_w>>16)&0xFFF; + + if (period>0 && period<0xFFF) { + + //period>>=2; + //period<<=1; + for (int n=0; n<6*12; n++) { + + if (period >= period_table[n]) { + + if ((period!=period_table[n]) && (n)) + { + uint32_t p1 = period_table[n-1]; + uint32_t p2 = period_table[n]; + if (p1 - period < (period - p2)) { + + note.note=n+36; + break; + } + } + note.note=n+1+36; + break; + } + } + if (note.note==CPNote::EMPTY) + note.note=6*12+36; + + note.note--; + } + + + switch(cmd) { + + case 0x0: { + + if (note.parameter>0) + note.command='J'-'A'; + } break; + case 0x1: { + note.command='F'-'A'; + } break; + case 0x2: { + + note.command='E'-'A'; + } break; + case 0x3: { + + note.command='G'-'A'; + } break; + case 0x4: { + + note.command='H'-'A'; + } break; + case 0x5: { + note.command='L'-'A'; + } break; + case 0x6: { + + note.command='K'-'A'; + } break; + case 0x7: { + note.command='R'-'A'; + } break; + case 0x8: { + + note.command='X'-'A'; + } break; + case 0x9: { + + note.command='O'-'A'; + + } break; + case 0xA: { + + note.command='D'-'A'; + + } break; + case 0xB: { + + note.command='B'-'A'; + + } break; + case 0xC: { + + note.volume=note.parameter; + if (note.volume>64) + note.volume=64; + note.parameter=0; + + } break; + case 0xD: { + + note.command='C'-'A'; + note.parameter=(note.parameter>>4)*10 + (note.parameter&0xF); + + } break; + case 0xE: { //SPECIAL EFFECT! + + note.command='S'-'A'; + + switch(note.parameter>>4) { + + case 0x1: { + + note.command='F'-'A'; + note.parameter=0xF0|(note.parameter&0xF); + } break; + case 0x2: { + + note.command='E'-'A'; + note.parameter=0xF0|(note.parameter&0xF); + } break; + case 0x4: { + + note.command='S'-'A'; + note.parameter=0x30|(note.parameter&0x3); + + } break; + case 0x6: { + + note.command='S'-'A'; + note.parameter=0xB0|(note.parameter&0xF); + + } break; + case 0x7: { + note.command='S'-'A'; + note.parameter=0x40|(note.parameter&0x3); + + } break; + case 0x8: { + + note.command='S'-'A'; // wow, it's the same! + + } break; + case 0x9: { + note.command='Q'-'A'; + note.parameter=(note.parameter&0xF); + + } break; + case 0xA: { + + note.command='D'-'A'; + note.parameter=0xF|((note.parameter&0xF)<<4); + + } break; + case 0xB: { + note.command='D'-'A'; + note.parameter=0xF0|(note.parameter&0xF); + + } break; + case 0xC: + case 0xD: { + + note.command='S'-'A'; //wow, they are the same! + + } break; + case 0xE: { + note.command='S'-'A'; + note.parameter=0x60|(note.parameter&0xF); + + } break; + + default: { + + note.command=CPNote::EMPTY; + note.parameter=0; + } break; + + } + } break; + case 0xF: { + + if (note.parameter<32) + note.command='A'-'A'; + else + note.command='T'-'A'; + + } break; + } + + p_song->get_pattern(i)->set_note( column,line, note ); + } + } + } + + + + for (int i=0;i<instruments;i++) { + + CPSample_ID sid=p_song->get_sample(i)->get_sample_data(); + if (sid.is_null()) { + continue; //empty sample, not stored? + } + sm->lock_data(sid); + uint8_t *dataptr = (uint8_t*)sm->get_data(sid); + + int len=sm->get_size(sid); + for (int s=0;s<len;s++) { + + uint8_t d=file->get_byte(); + //d-=128; //convert to signed + int8_t*ds=(int8_t*)&d; + dataptr[s]=*ds; + + } + sm->unlock_data(sid); + } + + file->close(); + + return FILE_OK; + + +} + + +CPLoader_MOD::CPLoader_MOD(CPFileAccessWrapper *p_file) { + + file=p_file; +} + + +CPLoader_MOD::~CPLoader_MOD() +{ +} + + diff --git a/modules/chibi/cp_loader_mod.h b/modules/chibi/cp_loader_mod.h new file mode 100644 index 000000000..57f7128bc --- /dev/null +++ b/modules/chibi/cp_loader_mod.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* cp_loader_mod.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_LOADER_MOD_H +#define CP_LOADER_MOD_H +#include "cp_loader.h" +/** + @author Juan Linietsky <reduz@gmail.com> +*/ +class CPLoader_MOD : public CPLoader { + + CPFileAccessWrapper *file; +public: + + bool can_load_song() { return true; } + bool can_load_sample() { return false; } + bool can_load_instrument() { return false; } + + Error load_song(const char *p_file,CPSong *p_song,bool p_sampleset); + Error load_sample(const char *p_file,CPSample *p_sample) { return FILE_UNRECOGNIZED; } + Error load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx) { return FILE_UNRECOGNIZED; } + + CPLoader_MOD(CPFileAccessWrapper *p_file); + ~CPLoader_MOD(); +}; + +#endif diff --git a/modules/chibi/cp_loader_s3m.cpp b/modules/chibi/cp_loader_s3m.cpp new file mode 100644 index 000000000..4943e6d86 --- /dev/null +++ b/modules/chibi/cp_loader_s3m.cpp @@ -0,0 +1,413 @@ +/*************************************************************************/ +/* cp_loader_s3m.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_loader_s3m.h" + +#define BITBOOL(m_exp) ((m_exp)?1:0) + + +CPLoader::Error CPLoader_S3M::load_header() { + + int i; + + + file->get_byte_array((uint8_t*)header.songname,28); + header.t1a=file->get_byte(); + header.type=file->get_byte(); + file->get_byte_array((uint8_t*)header.unused1,2); + header.ordnum=file->get_word(); + header.insnum=file->get_word(); + header.patnum=file->get_word(); + header.flags=file->get_word(); + header.tracker=file->get_word(); + header.fileformat=file->get_word(); + file->get_byte_array((uint8_t*)header.scrm,4); + header.scrm[4]=0; + + if (header.scrm[0]!='S' || header.scrm[1]!='C' || header.scrm[2]!='R' || header.scrm[3]!='M') + return FILE_UNRECOGNIZED; + + header.mastervol=file->get_byte(); + header.initspeed=file->get_byte(); + header.inittempo=file->get_byte(); + header.mastermult=file->get_byte(); + header.ultraclick=file->get_byte(); + header.pantable=file->get_byte(); + file->get_byte_array((uint8_t*)header.unused2,8); + header.special=file->get_word(); + file->get_byte_array((uint8_t*)header.channels,32); + + file->get_byte_array((uint8_t*)header.orderlist,header.ordnum); + + header.scrm[4]=0; + if (header.scrm[0]!='S' || header.scrm[1]!='C' || header.scrm[2]!='R' || header.scrm[3]!='M') //again? + return FILE_UNRECOGNIZED; + //sample parapointers + for (i=0;i<header.insnum;i++) { + + int parapointer; + parapointer=file->get_word(); + parapointer=(parapointer*16); + sample_parapointers[i]=parapointer; + } + //pattern + for (i=0;i<header.patnum;i++) { + + int parapointer; + parapointer=file->get_word(); + parapointer=(parapointer*16); + pattern_parapointers[i]=parapointer; + } + + if (header.pantable==252) { + + file->get_byte_array((uint8_t*)header.pannings,32); + } + + return FILE_OK; + + +} + + +void CPLoader_S3M::set_header() { + + + + + song->set_name( header.songname ); + //song->variables.filename= + + song->set_row_highlight_minor( 4 ); + song->set_row_highlight_major( 16 ); + song->set_mixing_volume( header.mastervol ); + song->set_linear_slides( false ); + song->set_old_effects( !(header.flags&64) ); + song->set_compatible_gxx( true ); + + song->set_global_volume( header.mastermult ); + song->set_speed( header.initspeed ); + song->set_tempo( header.inittempo ); + + //[TODO] Set Panning Positions.. ? + + for (int i=0;i<header.ordnum;i++) song->set_order(i,header.orderlist[i]); + +} + +CPLoader::Error CPLoader_S3M::load_sample(CPSample *p_sample) { + + + + int type=file->get_byte(); + + char filename[13]; + file->get_byte_array((uint8_t*)filename,12); + filename[12]=0; + + + uint32_t samplepos=(uint32_t)file->get_byte() << 16; + samplepos|=file->get_word(); + samplepos*=16; + //printf("sample at %i\n",samplepos); + /**/ + int sample_size=file->get_dword(); + + + int loop_begin=file->get_dword(); + int loop_end=file->get_dword(); + + int def_volume=file->get_byte();; + int dsk=file->get_byte(); + int pack=file->get_byte(); + + int flags=file->get_byte(); + int c2speed=file->get_dword(); + + file->get_dword(); //useless crap + file->get_dword(); + file->get_dword(); + + + char name[29]; + file->get_byte_array((uint8_t*)name,28); + name[28]=0; + + p_sample->set_default_volume(def_volume); + p_sample->set_name(name); + + char scrs[5]; + file->get_byte_array((uint8_t*)scrs,4); + scrs[4]=0; + + + + bool data_is_16bits=flags&4; + bool data_is_stereo=flags&2; + + if (type==0) { + //empty sample + return FILE_OK; + } + + + if ((type!=1) || scrs[0]!='S' || scrs[1]!='C' || scrs[2]!='R' || scrs[3]!='S' ) { + //printf("type: %i, %c%c%c%c\n",type,scrs[0],scrs[1],scrs[2],scrs[3]); + CP_PRINTERR("Not an S3M CPSample!"); + return FILE_CORRUPTED; + } + + //p_sample->data.set_c5_freq(p_sample->c2spd<<1); + + file->seek(samplepos); + + int real_sample_size=sample_size<<BITBOOL(data_is_16bits); + real_sample_size<<=BITBOOL(data_is_stereo); + + CPSampleManager *sm=CPSampleManager::get_singleton(); + + CPSample_ID id =sm->create( data_is_16bits, data_is_stereo, sample_size ); + + if (id.is_null()) + return FILE_OUT_OF_MEMORY; + + sm->lock_data(id); + void *dataptr = sm->get_data(id); + + int chans = (data_is_stereo?2:1); + for (int c=0;c<chans;c++) { + for (int i=0;i<sample_size;i++) { + + if (data_is_16bits) { + + uint16_t s=file->get_word(); + s-=32768; //toggle sign + + int16_t *v=(int16_t*)&s; + ((int16_t*)dataptr)[i*chans+c]=*v; + } else { + + + int8_t *v; + uint8_t s=file->get_byte(); + s-=128; //toggle sign + v=(int8_t*)&s; + ((int8_t*)dataptr)[i*chans+c]=*v; + + } + + } + + } + + sm->unlock_data(id); + + + sm->set_loop_begin( id, loop_begin ); + sm->set_loop_end( id, loop_end ); + sm->set_loop_type( id, (flags&1) ? CP_LOOP_FORWARD : CP_LOOP_NONE ); + sm->set_c5_freq( id, c2speed << 1 ); + p_sample->set_sample_data(id); + + /* Scream tracker previous to 3.10 seems to be buggy, as in, wont save what is after the sample loop, including the loop end point. Because of this I must fix it by habd */ + if (flags&1) { + + for (int c=0;c<(data_is_stereo?2:1);c++) { + sm->set_data( id, loop_end, sm->get_data( id, loop_begin,c ),c ); + + } + } + + + return FILE_OK; + +} + + +CPLoader::Error CPLoader_S3M::load_pattern(CPPattern *p_pattern) { + + int row=0,flag,ch; + CPNote n; + int length,accum=0; + + length=file->get_word(); + p_pattern->set_length(64); + + /* clear pattern data */ + while((row<64) && (accum<=length) ) { + flag=file->get_byte(); + accum++; + + n.clear(); + if(flag) { + // ch=remap[flag&31]; +// ch=remap[flag&31]; +// if(ch!=-1) +// n=s3mbuf[(64U*ch)+row]; +// else +// n=&dummy; + + ch=flag&31; + + if(flag&32) { + n.note=file->get_byte(); + if (n.note==255) { + + n.note=CPNote::EMPTY; + } else if (n.note==254) { + + n.note=CPNote::CUT; + } else { + + n.note=((n.note>>4)*12)+(n.note&0xF); + } + + n.instrument=file->get_byte()-1; + accum+=2; + + } + if(flag&64) { + n.volume=file->get_byte(); + if (n.volume>64) n.volume=64; + accum++; + + } + if(flag&128) { + n.command=file->get_byte()-1; + n.parameter=file->get_byte(); + accum+=2; + } + + p_pattern->set_note(ch,row,n); + } else row++; + } + return FILE_OK; + + +} + +CPLoader::Error CPLoader_S3M::load_sample(const char *p_file,CPSample *p_sample) { + + return FILE_UNRECOGNIZED; +} +CPLoader::Error CPLoader_S3M::load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx) { + + return FILE_UNRECOGNIZED; + +} + + +CPLoader::Error CPLoader_S3M::load_samples() { + + int i; + + for(i=0;i<header.insnum;i++) { + + file->seek(sample_parapointers[i]); + load_sample(song->get_sample(i)); + sample_count++; + } + + return FILE_OK; +} + +CPLoader::Error CPLoader_S3M::load_patterns() { + + int i; + + Error err; + for(i=0;i<header.patnum;i++) { + + file->seek(pattern_parapointers[i]); + + err=load_pattern(song->get_pattern(i) ); + CP_ERR_COND_V(err,err); + + + pattern_count++; + } + return FILE_OK; + +} + +CPLoader::Error CPLoader_S3M::load_song(const char *p_file,CPSong *p_song,bool p_sampleset) { + + song=p_song; + + if (file->open(p_file,CPFileAccessWrapper::READ)) { + //printf("Can't open file! %s\n",p_file); + return FILE_CANNOT_OPEN; + }; + + sample_count=0; + pattern_count=0; + + //printf("LOADING HEADER\n"); + CPLoader::Error err; + if ((err=load_header())) { + file->close(); + CP_ERR_COND_V(err,err); + + } + + song->reset(); //file type recognized, reset song! + + set_header(); + + //printf("LOADING SAMPLES\n"); + + if ((err=load_samples())) { + file->close(); + + CP_ERR_COND_V(err,err); + } + + //printf("LOADING PATTERNS\n"); + + if ((err=load_patterns())) { + + file->close(); + return err; + } + + file->close(); + + return FILE_OK; +} + + + +CPLoader_S3M::CPLoader_S3M(CPFileAccessWrapper *p_file){ + + file=p_file; + +} +CPLoader_S3M::~CPLoader_S3M(){ +} + diff --git a/modules/chibi/cp_loader_s3m.h b/modules/chibi/cp_loader_s3m.h new file mode 100644 index 000000000..04ee0b291 --- /dev/null +++ b/modules/chibi/cp_loader_s3m.h @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* cp_loader_s3m.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CP_LOADER_S3M_H +#define CP_LOADER_S3M_H + +#include "cp_loader.h" + +/** + *@author Juan Linietsky + */ +/****************************** + loader_s3m.h + ---------- +Scream Tracker Module CPLoader! +It lacks support for +individual sample loading +and reorganizing the columns. +********************************/ + + + + +class CPLoader_S3M : public CPLoader { + + struct S3M_Header { + char songname[28]; + uint8_t t1a; + uint8_t type; + uint8_t unused1[2]; + uint16_t ordnum; + uint16_t insnum; + uint16_t patnum; + uint16_t flags; + uint16_t tracker; + uint16_t fileformat; + char scrm[5]; + uint8_t mastervol; + uint8_t initspeed; + uint8_t inittempo; + uint8_t mastermult; + uint8_t ultraclick; + uint8_t pantable; + uint8_t unused2[8]; + uint16_t special; + uint8_t channels[32]; + uint8_t pannings[32]; + uint8_t orderlist[300]; + }; + + + int sample_parapointers[CPSong::MAX_SAMPLES]; + int pattern_parapointers[CPSong::MAX_PATTERNS]; + + Error load_header(); + void set_header(); + Error load_sample(CPSample *p_sample); + Error load_pattern(CPPattern *p_pattern); + Error load_patterns(); + + Error load_samples(); + + S3M_Header header; + int sample_count; + int pattern_count; + + CPFileAccessWrapper *file; + CPSong *song; +public: + + bool can_load_song() { return true; } + bool can_load_sample() { return false; } + bool can_load_instrument() { return false; } + + Error load_song(const char *p_file,CPSong *p_song,bool p_sampleset); + Error load_sample(const char *p_file,CPSample *p_sample); + Error load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx); + + CPLoader_S3M(CPFileAccessWrapper *p_file); + ~CPLoader_S3M(); +}; + + + +#endif diff --git a/modules/chibi/cp_loader_xm.cpp b/modules/chibi/cp_loader_xm.cpp new file mode 100644 index 000000000..cbd883642 --- /dev/null +++ b/modules/chibi/cp_loader_xm.cpp @@ -0,0 +1,752 @@ +/*************************************************************************/ +/* cp_loader_xm.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_loader_xm.h" +#include "cp_tables.h" + +#define ABORT_LOAD { file->close(); return FILE_CORRUPTED; } + + + + +CPLoader::Error CPLoader_XM::load_song(const char *p_file,CPSong *p_song,bool p_sampleset) { + + song=p_song; + + if (file->open(p_file,CPFileAccessWrapper::READ)) { + + return FILE_CANNOT_OPEN; + }; + + + /************************************** + LOAD HEADER + ***************************************/ + + file->get_byte_array(header.idtext,17); + header.idtext[17]=0; + + file->get_byte_array(header.songname,20); + + + + header.songname[20]=0; + header.hex1a=file->get_byte(); + if (header.hex1a!=0x1A) { //XM "magic" byte.. this sucks :) + + file->close(); + return FILE_UNRECOGNIZED; + + } + + + //magic byte sucks, but can't do much about it.. + + song->reset(); //must reset the song + + song->set_name( (const char*)header.songname ); + + file->get_byte_array(header.trackername,20); + header.trackername[20]=0; + + + header.version=file->get_word(); + + header.headersize=file->get_dword(); + + header.songlength=file->get_word(); + + header.restart_pos=file->get_word(); + + header.channels_used=file->get_word(); + + header.patterns_used=file->get_word(); + + header.instruments_used=file->get_word(); + + song->set_linear_slides( file->get_word() ); + + song->set_speed( file->get_word() ); + + song->set_tempo( file->get_word() ); + song->set_instruments( true ); + + file->get_byte_array(header.orderlist,256); + + for (int i=0;i<header.songlength;i++) { + + if (i>199) + break; + song->set_order(i,header.orderlist[i]); + } + + /************************************** + LOAD PATTERNS + ***************************************/ + + for (int i=0;i<header.patterns_used;i++) { + + uint32_t aux,rows; + + aux=file->get_dword(); //length + aux=file->get_byte(); //packing type + rows=aux=file->get_word(); //rows! + + song->get_pattern(i)->set_length( aux ); + + aux=file->get_word(); //packed size + if (aux==0) + continue; + //unpaaack! + for(int j=0;j<(int)rows;j++) + for(int k=0;k<header.channels_used;k++) { + + CPNote aux_note; + uint8_t aux_byte; + //uint8_t field; + aux_byte=file->get_byte(); //packing type + if (!(aux_byte&0x80)) { + + aux_note.note=aux_byte; + aux_byte=0xFE; //if bit 7 not set, read all of them except the note + } + + if (aux_byte&1) aux_note.note=file->get_byte(); + if (aux_byte&2) aux_note.instrument=file->get_byte(); + if (aux_byte&4) aux_note.volume=file->get_byte(); + if (aux_byte&8) aux_note.command=file->get_byte(); + if (aux_byte&16) aux_note.parameter=file->get_byte(); + + if (aux_note.note!=CPNote::EMPTY) { + + if (aux_note.note==97) aux_note.note=CPNote::OFF; + else { + aux_note.note+=11; //octave minus one (XM C-0 is 1, not zero ) + } + } + if (aux_note.instrument!=CPNote::EMPTY) { + + if ((aux_note.instrument>0) && (aux_note.instrument<100)) + aux_note.instrument--; + else + aux_note.instrument=CPNote::EMPTY; + } + if (aux_note.volume!=CPNote::EMPTY) { + + if (aux_note.volume<0x10) {} + else if (aux_note.volume<0x50) { + + aux_note.volume-=0x10; + + } else if (aux_note.volume<0x60) { + // + aux_note.volume=CPNote::EMPTY; + + } else if (aux_note.volume<0x70) { + //60 -- volume slide down + aux_note.volume-=0x60; + if (aux_note.volume>9) aux_note.volume=9; + aux_note.volume+=95; + + } else if (aux_note.volume<0x80) { + //70 -- volume slide up + aux_note.volume-=0x70; + if (aux_note.volume>9) aux_note.volume=9; + aux_note.volume+=85; + + + } else if (aux_note.volume<0x90) { + //80 -- fine volume slide down + aux_note.volume-=0x80; + if (aux_note.volume>9) aux_note.volume=9; + aux_note.volume+=75; + + + } else if (aux_note.volume<0xA0) { + //9 -- fine volume slide up + + aux_note.volume-=0x90; + if (aux_note.volume>9) aux_note.volume=9; + + aux_note.volume+=65; + + + + } else if (aux_note.volume<0xB0) { + //A -- set vibrato speed + aux_note.volume=CPNote::EMPTY; + + } else if (aux_note.volume<0xC0) { + //B -- vibrato + aux_note.volume-=0xB0; + if (aux_note.volume>9) aux_note.volume=9; + aux_note.volume+=203; + + + } else if (aux_note.volume<0xD0) { + //C -- set panning + int aux=aux_note.volume-=0xC0; + aux=aux*65/0xF; + aux_note.volume=128+aux; + + } else if (aux_note.volume<0xE0) { + aux_note.volume=CPNote::EMPTY; + + + } else if (aux_note.volume<0xF0) { + aux_note.volume=CPNote::EMPTY; + + + } else { + //F -- tone porta + aux_note.volume-=0xF0; + aux_note.volume*=9; + aux_note.volume/=0xF; + aux_note.volume+=193; + } + } + if (aux_note.command!=CPNote::EMPTY) { + + switch(aux_note.command) { + + case 0x0: + aux_note.command='J'-'A'; + break; + case 0x1: + aux_note.command='F'-'A'; + break; + case 0x2: + aux_note.command='E'-'A'; + break; + case 0x3: + aux_note.command='G'-'A'; + break; + case 0x4: + aux_note.command='H'-'A'; + break; + case 0x5: + aux_note.command='L'-'A'; + break; + case 0x6: + aux_note.command='K'-'A'; + break; + case 0x7: + aux_note.command='R'-'A'; + break; + case 0x8: + aux_note.command='X'-'A'; + break; + case 0x9: + aux_note.command='O'-'A'; + break; + case 0xa: + aux_note.command='D'-'A'; + break; + case 0xb: + aux_note.command='B'-'A'; + break; + case 0xc: + //printf("XM Import: Warning! effect C (set volume) not implemented!\n"); + break; + case 0xd: + aux_note.command='C'-'A'; + break; + + case 0xe: /* Extended effects */ + + aux_note.command='S'-'A'; + switch(aux_note.parameter>>4) { + case 0x1: /* XM fine porta up */ + if (!(aux_note.parameter&0xF)) { aux_note.command=CPNote::EMPTY; aux_note.parameter=0; break; } + aux_note.command='F'-'A'; + aux_note.parameter=0xF0|(aux_note.parameter&0xF); + break; + case 0x2: /* XM fine porta down */ + if (!(aux_note.parameter&0xF)) { aux_note.command=CPNote::EMPTY; aux_note.parameter=0; break; } + aux_note.command='E'-'A'; + aux_note.parameter=0xF0|(aux_note.parameter&0xF); + break; + case 0xa: /* XM fine volume up */ + if (!(aux_note.parameter&0xF)) { aux_note.command=CPNote::EMPTY; aux_note.parameter=0; break; } + aux_note.command='D'-'A'; + aux_note.parameter=0x0F|((aux_note.parameter&0xF)<<4); + + break; + case 0xb: /* XM fine volume down */ + if (!(aux_note.parameter&0xF)) { aux_note.command=CPNote::EMPTY; aux_note.parameter=0; break; } + aux_note.command='D'-'A'; + aux_note.parameter=0xF0|(aux_note.parameter&0xF); + + break; + case 0x9: /* XM fine volume down */ + if (!(aux_note.parameter&0xF)) { aux_note.command=CPNote::EMPTY; aux_note.parameter=0; break; } + aux_note.command='Q'-'A'; + aux_note.parameter=0x00|(aux_note.parameter&0xF); + break; + + case 0xc: //notecut + + aux_note.parameter=0xC0|(aux_note.parameter&0xF); + break; + + case 0xd: //notedelay + + aux_note.parameter=0xD0|(aux_note.parameter&0xF); + break; + + case 0xe: //patterndelay + + aux_note.parameter=0xE0|(aux_note.parameter&0xF); + break; + } + + break; + case 0xf: + if (aux_note.parameter<32) { + aux_note.command='A'-'A'; + } else { + aux_note.command='T'-'A'; + } + break; + case 'G'-55: + aux_note.command='V'-'A'; + break; + case 'H'-55: + aux_note.command='W'-'A'; + break; + case 'K'-55: + if (aux_note.note!=CPNote::EMPTY) break; + aux_note.note=CPNote::OFF; + break; + case 'P'-55: + aux_note.command='P'-'A'; + break; + case 'R'-55: + aux_note.command='Q'-'A'; + break; + case 'T'-55: + aux_note.command='I'-'A'; + break; + default: { + + aux_note.command=CPNote::EMPTY; + } + } + + + } + + song->get_pattern( i)->set_note( k,j,aux_note ); + } + } + + /************************************** + LOAD INSTRUMENTS! + ***************************************/ + + for (int i=0;i<header.instruments_used;i++) { + + + uint32_t aux; + int sampnum; + + CPInstrument &instrument=*song->get_instrument(i); + uint32_t cpos=file->get_pos(); + //printf("pos is %i\n",cpos); + + + +/* +4 */ uint32_t hsize=file->get_dword(); //header length + + char instrname[23]; + instrname[22]=0; + + file->get_byte_array((uint8_t*)instrname,22); +//XM_LOAD_DEBUG printf("name is %s\n",instrname); + +/* +27 */ aux=file->get_byte(); //byte that must be ignored +//XM_LOAD_DEBUG printf("header size is %i\n",hsize); + +/* +29 */ sampnum=file->get_word(); + +//XM_LOAD_DEBUG printf("samples %i\n",sampnum); + + + instrument.set_name( instrname ); + //printf("Header Len: %i, CPInstrument %i, %i samples , name: s,\n",hsize,i,sampnum,instrname); + + if (sampnum==0) { + //aux=file->get_dword(); //Why is this for? -- for nothing, skipped + if (hsize) { + + file->seek( cpos+hsize ); //skip header if size has been specified + } + continue; + } + +/* +33 */ file->get_dword(); + + if (Error result=load_instrument_internal(&instrument,false,cpos,hsize,sampnum)) { + + CP_PRINTERR("Error loading instrument"); + file->close(); + return result; + } + + } +// + file->close(); + return FILE_OK; +} + +CPLoader::Error CPLoader_XM::load_instrument_internal(CPInstrument *p_instr,bool p_xi,int p_cpos, int p_hsize, int p_sampnum) { + + int sampnum; + uint32_t aux; + uint8_t notenumb[96]; + uint16_t panenv[24],volenv[24]; + int volpoints,panpoints; + int vol_loop_begin,vol_loop_end,vol_sustain_loop; + int pan_loop_begin,pan_loop_end,pan_sustain_loop; + char instrname[23]; + int sample_index[16]={-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; //-1 means no index! + + instrname[22]=0; + + +/* +129 */ file->get_byte_array((uint8_t*)notenumb,96); + for (int j=0;j<24;j++) { + volenv[j]=file->get_word(); + } + for (int j=0;j<24;j++) { + panenv[j]=file->get_word(); + } + +/* +177 */ +/* +225 */ +/* +226 */ volpoints=file->get_byte(); +/* +227 */ panpoints=file->get_byte(); +/* +230 */ vol_sustain_loop=file->get_byte(); +/* +228 */ vol_loop_begin=file->get_byte(); +/* +229 */ vol_loop_end=file->get_byte(); + +//XM_LOAD_DEBUG printf("1- volpoints: %i, panpoints: %i, susloop: %i, loop begin: %i, loop end %i\n",volpoints,panpoints,vol_sustain_loop,vol_loop_begin,vol_loop_end); + pan_sustain_loop=file->get_byte(); +/* +231 */ pan_loop_begin=file->get_byte(); +/* +232 */ pan_loop_end=file->get_byte(); + + + +/* +234 */ aux=file->get_byte(); + p_instr->get_volume_envelope()->reset(); + p_instr->get_volume_envelope()->set_enabled(aux&1); + p_instr->get_volume_envelope()->set_sustain_loop_enabled((aux&2)?true:false); + p_instr->get_volume_envelope()->set_loop_enabled((aux&4)?true:false); +/* +235 */ aux=file->get_byte(); + p_instr->get_pan_envelope()->reset(); + p_instr->get_pan_envelope()->set_enabled(aux&1); + p_instr->get_pan_envelope()->set_sustain_loop_enabled((aux&2)?true:false); + p_instr->get_pan_envelope()->set_loop_enabled((aux&4)?true:false); + +/* +239 */ aux=file->get_dword(); // sadly, cant use those +/* +241 */ p_instr->set_volume_fadeout( file->get_word() >> 4 ); +/* +243 */ aux=file->get_word(); // reserved! + + + + for (int j=0;j<volpoints;j++) { + int ofs=volenv[j*2]; + int val=volenv[j*2+1]; + p_instr->get_volume_envelope()->add_position(ofs,val); + + } + + //make sure minimum is 2 + while (p_instr->get_volume_envelope()->get_node_count()<2) { + + p_instr->get_volume_envelope()->add_position( p_instr->get_volume_envelope()->get_node_count()*20,64 ); + } + + for (int j=0;j<panpoints;j++) { + int ofs=panenv[j*2]; + int val=panenv[j*2+1]; + p_instr->get_pan_envelope()->add_position(ofs,val-32); + } + + //make sure minimum is 2 + while (p_instr->get_pan_envelope()->get_node_count()<2) { + + p_instr->get_pan_envelope()->add_position( p_instr->get_pan_envelope()->get_node_count()*20,0 ); + } + + + p_instr->get_volume_envelope()->set_loop_begin(vol_loop_begin); + p_instr->get_volume_envelope()->set_loop_end(vol_loop_end); + p_instr->get_volume_envelope()->set_sustain_loop_end(vol_sustain_loop); + p_instr->get_volume_envelope()->set_sustain_loop_begin(vol_sustain_loop); + p_instr->get_pan_envelope()->set_loop_begin(pan_loop_begin); + p_instr->get_pan_envelope()->set_loop_end(pan_loop_end); + p_instr->get_pan_envelope()->set_sustain_loop_end(pan_sustain_loop); + p_instr->get_pan_envelope()->set_sustain_loop_begin(pan_sustain_loop); + + + if (!p_xi) { + + if ((file->get_pos()-p_cpos)<p_hsize) { + + uint8_t junkbuster[500]; + + //printf("extra junk XM instrument in header! hsize is %i, extra junk: %i\n",p_hsize,(file->get_pos()-p_cpos)); + //printf("extra: %i\n",p_hsize-(file->get_pos()-p_cpos)); + file->get_byte_array((uint8_t*)junkbuster,p_hsize-(file->get_pos()-p_cpos)); + } + + sampnum=p_sampnum; + } else { + + uint8_t junkbuster[500]; + file->get_byte_array((uint8_t*)junkbuster,20); //14 bytes? + + sampnum=file->get_word(); + + } + + + CPSampleManager *sm=CPSampleManager::get_singleton(); + + /*SAMPLE!!*/ + + for (int j=0;j<sampnum;j++) { + + if (j>16) ABORT_LOAD; + + + int s_idx=-1; + for (int s=0;s<CPSong::MAX_SAMPLES;s++) { + + if (song->get_sample(s)->get_sample_data().is_null()) { + //empty sample! + s_idx=s; + break; + } + } + + if (s_idx==-1) ABORT_LOAD; + //printf("free sample: %i\n",s_idx); + + + CPSample& sample=*song->get_sample(s_idx); + + int sample_size=file->get_dword(); + int tmp_loop_begin=file->get_dword(); + + int tmp_loop_end=file->get_dword(); + + sample.set_default_volume(file->get_byte()); + + uint8_t ftb=file->get_byte(); + int8_t *fts=(int8_t*)&ftb; + int finetune=*fts; + uint32_t flags=file->get_byte(); + + if (flags&16) { // is 16 bits.. at flag 16.. fun :) + + tmp_loop_end/=2; + tmp_loop_begin/=2; + sample_size/=2; + } + + + CPSample_ID sample_data=sm->create( flags&16, false, sample_size ); + + sample.set_sample_data(sample_data); + sm->set_loop_begin(sample_data,tmp_loop_begin); + sm->set_loop_end(sample_data,tmp_loop_end+tmp_loop_begin); + + sm->set_loop_type( sample_data, (flags&3)?( (flags&2) ? CP_LOOP_BIDI : CP_LOOP_FORWARD ):CP_LOOP_NONE ); + + + + sample.set_pan_enabled(true); + sample.set_pan(file->get_byte()*64/255); + uint8_t noteb=file->get_byte(); + int8_t *notes=(int8_t*)¬eb; + int note_offset=*notes; + note_offset+=48; + //note_offset+=60; + + + + //int linear_period=10*12*16*4 - (note_offset)*16*4 - finetune/2; + //int freq=(int)(8363*pow(2.0,(double)(6*12*16*4 - linear_period) / (double)(12*16*4))); + + //sm->set_c5_freq( sample_data, freq); + sm->set_c5_freq( sample_data, CPTables::get_linear_frequency(CPTables::get_linear_period(note_offset<<1,finetune)) ); + //printf("NOTE %i,fine %i\n",note_offset,finetune); + + char auxb; + auxb=file->get_byte(); //reserved? + file->get_byte_array((uint8_t*)instrname,22); + sample.set_name(instrname); + + sample_index[j]=s_idx; + } + + /*SAMPLE __DATA__!!*/ + + for (int j=0;j<sampnum;j++) { + + if (sample_index[j]==-1) continue; + + CPSample *sample=song->get_sample(sample_index[j]); + CPSample_ID sid=sample->get_sample_data(); + + sm->lock_data(sid); + + void*dataptr=sm->get_data(sid); + + if (sm->is_16bits( sid)) { + + int16_t old=0; + + + for (int k=0;k<sm->get_size(sid);k++) { + + int16_t newsample; + int16_t sampleval=file->get_word(); + newsample=sampleval+old; + old=newsample; + + ((int16_t*)dataptr)[k]=newsample; + //sm->set_data( sid, k, newsample ); + } + } else { + + int8_t old=0; + + + for (int k=0;k<sm->get_size(sid);k++) { + + int8_t newsample; + int8_t sampleval=file->get_byte(); + newsample=sampleval+old; + old=newsample; + + ((int8_t*)dataptr)[k]=newsample; + + //sm->set_data( sid, k, (int16_t)newsample << 8 ); + + } + } + + sm->unlock_data(sid); + + } + + for (int j=0;j<96;j++) { + + int val=notenumb[j]; + if ((val<0) || (val>15)) continue; + else val=sample_index[val]; + if (val==-1) continue; + p_instr->set_sample_number( 12+j,val ); + } + + + return FILE_OK; +} + + + +CPLoader::Error CPLoader_XM::load_sample(const char *p_file,CPSample *p_sample) { + + return FILE_UNRECOGNIZED; +} + + +/* Compute CPInstrument Info */ +CPLoader::Error CPLoader_XM::load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx) { + + if ( file->open(p_file,CPFileAccessWrapper::READ) ) return FILE_CANNOT_OPEN; + //int i; + song=p_song; + CPInstrument& instr=*p_song->get_instrument( p_instr_idx ); + int aux; + + + char buffer[500]; + file->get_byte_array((uint8_t*)buffer,0x15); + buffer[8]=0; + if ( buffer[0]!='E' || + buffer[1]!='x' || + buffer[2]!='t' || + buffer[3]!='e' || + buffer[4]!='n' || + buffer[5]!='d' || + buffer[6]!='e' || + buffer[7]!='d') { + file->close(); + return FILE_UNRECOGNIZED; + } + + file->get_byte_array((uint8_t*)buffer,0x16); + buffer[0x16]=0; + instr.set_name(buffer); + aux=file->get_byte(); //says ignore ti + /*if(aux!=0x1a) { I'm not sure. this is supposed to be ignored... + + file->close(); + return FILE_UNRECOGNIZED; + } */ + + file->get_byte_array((uint8_t*)buffer,0x14); //somethingaboutthename + aux=file->get_word(); //version or blahblah + + if (load_instrument_internal(&instr,true,0,0)) { + + file->close(); + return FILE_CORRUPTED; + } + + file->close(); //ook, we got it.. + + + return FILE_OK; + +} + + + +CPLoader_XM::CPLoader_XM(CPFileAccessWrapper *p_file){ + + file=p_file; +} +CPLoader_XM::~CPLoader_XM(){ +} + diff --git a/modules/chibi/cp_loader_xm.h b/modules/chibi/cp_loader_xm.h new file mode 100644 index 000000000..0889569b3 --- /dev/null +++ b/modules/chibi/cp_loader_xm.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* cp_loader_xm.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CP_LOADER_XM_H +#define CP_LOADER_XM_H + +#include "cp_loader.h" + + +/** + *@author red + */ + + + + +class CPLoader_XM : public CPLoader { + + + struct XM_Header { + + uint8_t idtext[18]; + uint8_t songname[21]; + uint8_t hex1a; // ? + uint8_t trackername[21]; + uint16_t version; + uint32_t headersize; //from here + + uint16_t songlength; //pattern ordertable + uint16_t restart_pos; + uint16_t channels_used; + uint16_t patterns_used; + uint16_t instruments_used; + uint16_t use_linear_freq; + uint16_t tempo; + uint16_t speed; + uint8_t orderlist[256]; + + } header; + + CPFileAccessWrapper *file; + + Error load_instrument_internal(CPInstrument *pint,bool p_xi,int p_cpos, int p_hsize, int p_sampnumb=-1); + CPSong *song; + +public: + + bool can_load_song() { return true; } + bool can_load_sample() { return false; } + bool can_load_instrument() { return true; } + + Error load_song(const char *p_file,CPSong *p_song,bool p_sampleset); + Error load_sample(const char *p_file,CPSample *p_sample); + Error load_instrument(const char *p_file,CPSong *p_song,int p_instr_idx); + + + CPLoader_XM(CPFileAccessWrapper *p_file); + ~CPLoader_XM(); +}; + + + +#endif diff --git a/modules/chibi/cp_mixer.h b/modules/chibi/cp_mixer.h new file mode 100644 index 000000000..d8564bae0 --- /dev/null +++ b/modules/chibi/cp_mixer.h @@ -0,0 +1,115 @@ +/*************************************************************************/ +/* cp_mixer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CP_MIXER_H +#define CP_MIXER_H + +#include "cp_sample_defs.h" + +/**Abstract base class representing a mixer + *@author Juan Linietsky + */ + + +/****************************** + mixer.h + ---------- + +Abstract base class for the mixer. +This is what the player uses to setup +voices and stuff.. this way +it can be abstracted to hardware +devices or other stuff.. +********************************/ + +class CPSample_ID; /* need this */ + +class CPMixer { +public: + + enum { + + FREQUENCY_BITS=8 + + }; + + enum ReverbMode { + REVERB_MODE_ROOM, + REVERB_MODE_STUDIO_SMALL, + REVERB_MODE_STUDIO_MEDIUM, + REVERB_MODE_STUDIO_LARGE, + REVERB_MODE_HALL, + REVERB_MODE_SPACE_ECHO, + REVERB_MODE_ECHO, + REVERB_MODE_DELAY, + REVERB_MODE_HALF_ECHO + }; + + /* Callback */ + + virtual void set_callback_interval(int p_interval_us)=0; //in usecs, for tracker it's 2500000/tempo + virtual void set_callback(void (*p_callback)(void*),void *p_userdata)=0; + + /* Voice Control */ + + virtual void setup_voice(int p_voice_index,CPSample_ID p_sample_id,int32_t p_start_index) =0; + virtual void stop_voice(int p_voice_index) =0; + virtual void set_voice_frequency(int p_voice_index,int32_t p_freq) =0; //in freq*FREQUENCY_BITS + virtual void set_voice_panning(int p_voice_index,int p_pan) =0; + virtual void set_voice_volume(int p_voice_index,int p_vol) =0; + virtual void set_voice_filter(int p_filter,bool p_enabled,uint8_t p_cutoff, uint8_t p_resonance )=0; + virtual void set_voice_reverb_send(int p_voice_index,int p_reverb)=0; + virtual void set_voice_chorus_send(int p_voice_index,int p_chorus)=0; /* 0 - 255 */ + + virtual void set_reverb_mode(ReverbMode p_mode)=0; + virtual void set_chorus_params(unsigned int p_delay_ms,unsigned int p_separation_ms,unsigned int p_depth_ms10,unsigned int p_speed_hz10)=0; + + + /* Info retrieving */ + + virtual int32_t get_voice_sample_pos_index(int p_voice_index) =0; + virtual int get_voice_panning(int p_voice_index) =0; + virtual int get_voice_volume(int p_voice_index) =0; + virtual CPSample_ID get_voice_sample_id(int p_voice_index) =0; + virtual bool is_voice_active(int p_voice_index) =0; + virtual int get_active_voice_count()=0; + virtual int get_total_voice_count()=0; + + + virtual uint32_t get_mix_frequency()=0; //if mixer is not software, return 0 + + /* Methods below only work with software mixers, meant for software-based sound drivers, hardware mixers ignore them */ + virtual int32_t process(int32_t p_frames)=0; /* Call this to process N frames, returns how much it was processed */ + virtual int32_t *get_mixdown_buffer_ptr()=0; /* retrieve what was mixed */ + virtual void set_mix_frequency(int32_t p_mix_frequency)=0; + + virtual ~CPMixer() {} +}; + +#endif diff --git a/modules/ik/ik.h b/modules/chibi/cp_note.h index 9daddb229..f9a3ef39f 100644 --- a/modules/ik/ik.h +++ b/modules/chibi/cp_note.h @@ -1,12 +1,11 @@ /*************************************************************************/ -/* ik.h */ +/* cp_note.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ -/* This file is (c) 2016 Sergey Lapin <slapinid@gmail.com> */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -27,48 +26,77 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef IK_H -#define IK_H +#ifndef CP_NOTE_H +#define CP_NOTE_H -#include "scene/3d/skeleton.h" -class InverseKinematics : public Spatial { - OBJ_TYPE(InverseKinematics, Spatial); - bool bound; - String ik_bone; - int ik_bone_no; - int tail_bone; - int chain_size; - Skeleton *skel; - List<int> chain; - void _check_bind(); - void _check_unbind(); - int iterations; - float precision; - float speed; - bool changed; +#include "cp_config.h" -protected: - bool _set(const StringName& p_name, const Variant& p_value); - bool _get(const StringName& p_name,Variant &r_ret) const; - void _get_property_list( List<PropertyInfo> *p_list) const; +struct CPNote { - void _notification(int p_what); - static void _bind_methods(); - void update_parameters(); -public: - Skeleton *get_skeleton(); - void set_bone_name(const String& p_name); - String get_bone_name() const; - void set_iterations(int itn); - int get_iterations() const; - void set_chain_size(int cs); - int get_chain_size() const; - void set_precision(float p); - float get_precision() const; - void set_speed(float p); - float get_speed() const; - InverseKinematics(); + enum { + + NOTES=120, + OFF=254, + CUT=253, + EMPTY=255, + SCRIPT=252, + }; + + + uint8_t note; + uint8_t instrument; + uint8_t volume; + uint8_t command; + uint8_t parameter; + unsigned int script_source_sign; + bool cloned; + + void clear() { + + note=EMPTY; + instrument=EMPTY; + volume=EMPTY; + command=EMPTY; + parameter=0; + script_source_sign='\0'; + cloned=false; + } + + void raise() { + + if (note<(NOTES-1)) + note++; + else if (note==SCRIPT && parameter<0xFF) + parameter++; + } + + void lower() { + + if ((note>0) && (note<NOTES)) + note--; + else if (note==SCRIPT && parameter>0) + parameter--; + + } + + bool operator== (const CPNote &rvalue) { + + return ( + (note==rvalue.note) && + (instrument==rvalue.instrument) && + (volume==rvalue.volume) && + (command==rvalue.command) && + (parameter==rvalue.parameter) + ); + } + + bool is_empty() const { return (note==EMPTY && instrument==EMPTY && volume==EMPTY && command==EMPTY && parameter==0 && !cloned); } + CPNote() { + + clear(); + } }; + #endif diff --git a/modules/chibi/cp_order.h b/modules/chibi/cp_order.h new file mode 100644 index 000000000..8df67df40 --- /dev/null +++ b/modules/chibi/cp_order.h @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* cp_order.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_ORDER_H +#define CP_ORDER_H + + +#include "cp_config.h" + +enum CPOrderType { + CP_ORDER_NONE=255, + CP_ORDER_BREAK=254 +}; + +typedef uint8_t CPOrder; + +#endif + diff --git a/modules/chibi/cp_pattern.cpp b/modules/chibi/cp_pattern.cpp new file mode 100644 index 000000000..8671b6247 --- /dev/null +++ b/modules/chibi/cp_pattern.cpp @@ -0,0 +1,574 @@ +/*************************************************************************/ +/* cp_pattern.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_pattern.h" + +void CPPattern::clear() { + + if (event_count>0) { + + + CP_FREE(events); + events=NULL; + event_count=0; + } + + length=DEFAULT_LEN; + +} + + +bool CPPattern::resize_event_list_to(uint32_t p_events) { + + + //Module is slow in some cpus, so this should be fast enough + uint32_t new_size=((p_events-1)&(~((1<<RESIZE_EVERY_BITS)-1)))+(1<<RESIZE_EVERY_BITS); + + CP_ERR_COND_V(new_size<p_events,true); //bugARM_INFO + + if (event_count==0 && new_size==0) + return false; //nothing to do + + if (event_count==0) { + + events=(Event*)CP_ALLOC( new_size*sizeof(Event) ); + + } else if (new_size==0) { + + CP_FREE(events); + events=NULL; + } else { + + CP_ERR_COND_V(events==NULL,true); + events=(Event*)CP_REALLOC(events, new_size*sizeof(Event)); + + } + + event_count=p_events; + + return false; +} + + +int32_t CPPattern::get_event_pos(uint16_t p_target_pos) { + + + if (event_count==0) + return -1; + + int low = 0; + int high = event_count -1; + int middle; + + while( low <= high ) + { + middle = ( low + high ) / 2; + + if( p_target_pos == events[middle].pos ) { //match + break; + } else if( p_target_pos < events[middle].pos ) + high = middle - 1; //search low end of array + else + low = middle + 1; //search high end of array + } + + /* adapt so we are behind 2 */ + + if (events[middle].pos<p_target_pos) + middle++; + return middle; + + /* Linear search for now */ + + /* + int32_t pos_idx=0; + + for (;pos_idx<event_count;pos_idx++) { + + if (event_list[pos_idx].pos>=p_target_pos) + break; + + } */ + + //return pos_idx; +} + +bool CPPattern::erase_event_at_pos(uint16_t p_pos) { + + if (event_count==0) + return false; + + + + Event *event_list=events; + + int32_t pos_idx = get_event_pos(p_pos); + if (pos_idx==-1) { + CP_ERR_COND_V(pos_idx==-1,true); + } + + if (pos_idx==event_count || event_list[pos_idx].pos!=p_pos) { + /* Nothing to Erase */ + return false; + } + + for (int32_t i=pos_idx;i<(event_count-1);i++) { + + event_list[i]=event_list[i+1]; + } + + + resize_event_list_to(event_count-1); + + return false; +} + +bool CPPattern::set_note(uint8_t p_column, uint16_t p_row,const CPNote& p_note) { + + CP_ERR_COND_V(p_column>=WIDTH,true); + CP_ERR_COND_V(p_row>=length,true); + + int32_t new_pos; + uint16_t target_pos=p_row*WIDTH+p_column; + + + + if (p_note.is_empty()) { + bool res=erase_event_at_pos(target_pos); + + return res;; + } + + Event *event_list=0; + + if (event_count==0) { + /* If no events, create the first */ + + if (resize_event_list_to(1)) { + + CP_PRINTERR("Can't resize event list to 1"); + return true; + } + + event_list=events; + if (event_list==0) { + + + CP_PRINTERR("Can't get event list"); + return true; + } + + new_pos=0; + + } else { + /* Prepare to add */ + + event_list=events; + if (event_list==0) { + + + CP_PRINTERR("Can't get event list"); + return true; + } + + int32_t pos_idx = get_event_pos(target_pos); + + if (pos_idx==-1) { + + + CP_PRINTERR("Can't find add position"); + return true; + } + + + if (pos_idx==event_count || event_list[pos_idx].pos!=target_pos) { + /* If the note being modified didnt exist, then we add it */ + + //resize, and return if out of mem + if (resize_event_list_to( event_count+1)) { + + + CP_PRINTERR("Can't resize event list"); + return true; + } + event_list=events; + if (event_list==0) { + + + CP_PRINTERR("Can't get event list"); + return true; + } + + //make room for new pos, this wont do a thing if pos_idx was ==event_count + for(int32_t i=(event_count-1);i>pos_idx;i--) { + event_list[i]=event_list[i-1]; + + } + + } /* Else it means that position is taken, so we just modify it! */ + + + new_pos=pos_idx; + } + + event_list[new_pos].pos=target_pos; + event_list[new_pos].note=p_note.note; + event_list[new_pos].instrument=p_note.instrument; + event_list[new_pos].volume=p_note.volume; + event_list[new_pos].command=p_note.command; + event_list[new_pos].parameter=p_note.parameter; + event_list[new_pos].script_source_sign=p_note.script_source_sign; + event_list[new_pos].cloned=p_note.cloned; + + + + + return false; + +} +CPNote CPPattern::get_note(uint8_t p_column,uint16_t p_row) { + + if (p_column==CPNote::EMPTY) return CPNote(); + + CP_ERR_COND_V(p_column>=WIDTH,CPNote()); + CP_ERR_COND_V(p_row>=length,CPNote()); + + if (event_count==0) + return CPNote(); + + + Event *event_list=events; + + CP_ERR_COND_V(event_list==0,CPNote()); + + uint16_t target_pos=p_row*WIDTH+p_column; + int32_t pos_idx = get_event_pos(target_pos); + if (pos_idx==-1) { + + CP_PRINTERR("Can't find event pos"); + return CPNote(); + } + + if (pos_idx>=event_count || event_list[pos_idx].pos!=target_pos) { + /* no note found */ + + return CPNote(); + } + + CPNote n; + n.note=event_list[pos_idx].note; + n.instrument=event_list[pos_idx].instrument; + n.volume=event_list[pos_idx].volume; + n.command=event_list[pos_idx].command; + n.parameter=event_list[pos_idx].parameter; + n.script_source_sign=event_list[pos_idx].script_source_sign; + n.cloned=event_list[pos_idx].cloned; + + + return n; + +} + +CPNote CPPattern::get_transformed_script_note(uint8_t p_column,uint16_t p_row ) { + + CPNote n = get_note( p_column, p_row ); + + // get source channel and note + + int channel = get_scripted_note_target_channel( p_column, p_row ); + CPNote src_n = get_note( channel, 0 ); + + if ( src_n.note == CPNote::SCRIPT ) return CPNote(); + + script_transform_note( src_n, n ); + + return src_n; + +} + +int CPPattern::get_scripted_note_target_channel(uint8_t p_column, uint16_t p_row) { + + CPNote n = get_note( p_column, p_row ); + + if ( n.note != CPNote::SCRIPT ) return CPNote::EMPTY; + + int channel = n.instrument; + + if ( n.script_source_sign == '\0' ) { + + if ( channel < 0 || channel >= CPPattern::WIDTH ) return CPNote::EMPTY; + + } else { + + channel = p_column + ( ( n.script_source_sign=='+') ? 1 : -1 ) * (channel+1); + if ( channel < 0 || channel >= CPPattern::WIDTH ) return CPNote::EMPTY; + + } + + return channel; +} + +void CPPattern::scripted_clone(uint8_t p_column, uint16_t p_row) { + + int channel = get_scripted_note_target_channel( p_column, p_row ); + int src_row = 1; + CPNote script_n = get_note( p_column, p_row ); + + for ( int row = p_row+1; row < length; ++row ) { + + CPNote src_n = get_note( channel, src_row ); + CPNote target_n = get_note( p_column, row ); + + if ( target_n.note != CPNote::SCRIPT ) { + if ( src_n.note == CPNote::SCRIPT ) { + src_n = CPNote(); + channel = CPNote::EMPTY; + } + + script_transform_note( src_n, script_n ); + + src_n.cloned = true; + set_note( p_column, row, src_n ); + + } else { + + return; + + } + + src_row++; + } + +} + +void CPPattern::scripted_clone_remove(uint8_t p_column, uint16_t p_row) { + + if ( get_note( p_column, p_row ).cloned ) + set_note( p_column, p_row, CPNote() ); + + for ( int row = p_row+1; row < length; ++row ) { + + CPNote target_n = get_note( p_column, row ); + + if ( target_n.note != CPNote::SCRIPT ) { + + set_note( p_column, row, CPNote() ); + + } else { + + return; + + } + + } + +} + +void CPPattern::script_transform_note(CPNote& n, const CPNote& p_note) { + + // set instrument + + if ( n.note < CPNote::NOTES && p_note.volume != CPNote::EMPTY ) { + + n.instrument = p_note.volume; + + } + + // transpose + + if ( n.note < CPNote::NOTES && p_note.command != CPNote::EMPTY ) { + + int transpose = ( p_note.parameter & 0xF ) + ( p_note.parameter / 0x10 ) * 12; + + if ( p_note.command == '^' ) { + + if ( n.note >= CPNote::NOTES-transpose ) + n.note = CPNote::NOTES-1; + else + n.note += transpose; + + } else if ( p_note.command == 'v' ) { + + if ( n.note <= transpose ) + n.note = 0; + else + n.note -= transpose; + + } + } + +} + +bool CPPattern::update_scripted_clones_sourcing_channel( int channel ) { + + bool updated = false; + + for ( int x = 0; x < WIDTH; ++x ) { + + for (int y = 0; y < length; ++y ) { + + if ( channel == get_scripted_note_target_channel( x, y ) ) { + + scripted_clone( x, y ); + updated = true; + } + + } + + } + + return updated; +} + +void CPPattern::set_length(uint16_t p_rows) { + + + + if (event_count==0) { + + if (p_rows>=MIN_ROWS) + length=p_rows; + + + return; + + } + + if (p_rows<MIN_ROWS) { + + return; + } + + if (p_rows<length) { + + Event* event_list=events; + if (event_list==0) { + + CP_PRINTERR("get_event_list() Failed"); + return; + } + + + uint16_t target_pos=p_rows*WIDTH; + int32_t pos_idx = get_event_pos(target_pos); + + + if (pos_idx==-1) { + + CP_ERR_COND(pos_idx==-1); + } + + if (resize_event_list_to(pos_idx)) { + + CP_PRINTERR("resize_event_list_to(pos_idx) Failed"); + return; + } + + } + + length=p_rows; + + +} +#if 0 +void CPPattern::copy_to(CPPattern *p_pattern) const { + + + + + p_pattern->clear(); + p_pattern->length=length; + + + if (!event_count) + return; + + + + int bufsiz=MemPool_Wrapper::get_singleton()->get_mem_size( mem_handle ); + MemPool_Handle aux_mem_handle=MemPool_Wrapper::get_singleton()->alloc_mem( bufsiz ); + + if (aux_mem_handle.is_null()) { + + CP_PRINTERR("own handle is null"); + + return; + } + + + if (MemPool_Wrapper::get_singleton()->lock_mem(aux_mem_handle)) { + CP_PRINTERR("Unable to lock aux new handle"); + + return; + + } + + if (MemPool_Wrapper::get_singleton()->lock_mem(mem_handle)) { + + CP_PRINTERR("Unable to lock own handle"); + + return; + } + + uint8_t* srcuint8_tt8_t*)MemPool_Wrapper::get_singleton()->get_mem(mem_handle); + uint8_t* dstuint8_tt8_t*)MemPool_Wrapper::get_singleton()->get_mem(aux_mem_handle); + + for (int i=0;i<bufsiz;i++) + dst[i]=src[i]; + + MemPool_Wrapper::get_singleton()->unlock_mem(mem_handle); + MemPool_Wrapper::get_singleton()->unlock_mem(aux_mem_handle); + + p_pattern->mem_handle=aux_mem_handle; + p_pattern->event_count=event_count; + + +} +#endif +uint16_t CPPattern::get_length() { + + + return length; +} +CPPattern::CPPattern() { + + + length=DEFAULT_LEN; + event_count=0; + clear(); + +} +bool CPPattern::is_empty() { + + return events==NULL; +} + +CPPattern::~CPPattern() { + + clear(); +} diff --git a/modules/chibi/cp_pattern.h b/modules/chibi/cp_pattern.h new file mode 100644 index 000000000..fc3b03252 --- /dev/null +++ b/modules/chibi/cp_pattern.h @@ -0,0 +1,94 @@ +/*************************************************************************/ +/* cp_pattern.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_PATTERN_H +#define CP_PATTERN_H + +#include "cp_note.h" + +class CPPattern { +public: + + + enum { + WIDTH=64, + DEFAULT_LEN=64, + RESIZE_EVERY_BITS=4, + MIN_ROWS=1, //otherwise clipboard wont work + MAX_LEN=256 + + }; + +private: + struct Event { + + uint16_t pos; //column*WIDTH+row + uint8_t note; + uint8_t instrument; + uint8_t volume; + uint8_t command; + uint8_t parameter; + unsigned int script_source_sign; + bool cloned; + }; + + uint16_t length; + uint32_t event_count; + Event* events; + + int32_t get_event_pos(uint16_t p_target_pos); + bool erase_event_at_pos(uint16_t p_pos); + + bool resize_event_list_to(uint32_t p_events); + + void operator=(const CPPattern& p_pattern); //no operator= +public: + + bool is_empty(); + void clear(); + + bool set_note(uint8_t p_column, uint16_t p_row,const CPNote& p_note); //true if no more memory + CPNote get_note(uint8_t p_column,uint16_t p_row); + + CPNote get_transformed_script_note(uint8_t p_column, uint16_t p_row); + int get_scripted_note_target_channel(uint8_t p_column, uint16_t p_row); + void scripted_clone(uint8_t p_column, uint16_t p_row); + void scripted_clone_remove(uint8_t p_column, uint16_t p_row); + void script_transform_note(CPNote& n, const CPNote& p_note); + bool update_scripted_clones_sourcing_channel(int channel); + + //void copy_to(CPPattern *p_pattern) const; + void set_length(uint16_t p_rows); + uint16_t get_length(); + CPPattern(); + ~CPPattern(); + + +}; + +#endif diff --git a/modules/chibi/cp_player_data.cpp b/modules/chibi/cp_player_data.cpp new file mode 100644 index 000000000..c8cbfbd06 --- /dev/null +++ b/modules/chibi/cp_player_data.cpp @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* cp_player_data.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" +#include <stdio.h> + + +CPPlayer::CPPlayer(CPMixer *p_mixer,CPSong *p_song){ + + song=p_song; + mixer=p_mixer; + control.max_voices=p_mixer->get_total_voice_count()-1; //leave one for the sample + control.force_no_nna=false; + control.external_vibrato=false; + control.filters=true; + control.random_seed=128364; //anything + control.play_mode=0; + set_virtual_channels(p_mixer->get_total_voice_count()); + mixer->set_callback( &CPPlayer::callback_function, this ); + + reset(); +} +CPPlayer::~CPPlayer(){ +} + +void CPPlayer::set_virtual_channels(int p_amount) { + + if (p_amount<1) return; + if (p_amount>mixer->get_total_voice_count()) + return; + + control.max_voices=p_amount; + +} + + +void CPPlayer::callback_function(void *p_userdata) { + + CPPlayer*pd=(CPPlayer*)p_userdata; + pd->process_tick(); + +} + +void CPPlayer::process_tick() { + + handle_tick(); + mixer->set_callback_interval( 2500000/control.tempo ); + song_usecs+=2500000/control.tempo; +} + +void CPPlayer::reset() { + + if ( mixer==NULL ) return ; + if ( song==NULL ) return ; + + int i; + + for (i=0;i<control.max_voices;i++) { + + voice[i].reset(); + mixer->stop_voice(i); + } + + for (i=0;i<CPPattern::WIDTH;i++) { + + control.channel[i].reset(); + control.channel[i].channel_volume=song->get_channel_volume(i); + control.channel[i].channel_panning=((int)song->get_channel_pan( i)*PAN_RIGHT/64); + if (song->is_channel_surround(i)) + control.channel[i].channel_panning=PAN_SURROUND; + control.channel[i].mute=song->is_channel_mute( i ); + control.channel[i].chorus_send=song->get_channel_chorus(i)*0xFF/64; + control.channel[i].reverb_send=song->get_channel_reverb(i)*0xFF/64; + } + + + control.speed=song->get_speed(); + control.tempo=song->get_tempo(); + control.global_volume=song->get_global_volume(); + + control.position.current_pattern=0; + control.position.current_row=0; + control.position.current_order=0; + control.position.force_next_order=-1; + control.ticks_counter=control.speed; + control.position.forbid_jump=false; + + song_usecs=0; + +} + +int64_t CPPlayer::get_channel_last_note_time_usec(int p_channel) const { + + CP_FAIL_INDEX_V(p_channel,64,-1); + return control.channel[p_channel].last_event_usecs; + +} + +void CPPlayer::set_channel_global_volume(int p_channel,int p_volume) { + + CP_FAIL_INDEX(p_channel,64); + control.channel[p_channel].channel_global_volume=CLAMP(p_volume,0,255); + +} + +int CPPlayer::get_channel_global_volume(int p_channel) const{ + + CP_FAIL_INDEX_V(p_channel,64,-1); + return control.channel[p_channel].channel_global_volume; + +} + +bool CPPlayer::reached_end_of_song() { + + return control.reached_end; + +} +void CPPlayer::set_force_external_vibratos(bool p_force) { + + control.external_vibrato=p_force; +} +void CPPlayer::set_force_no_nna(bool p_force) { + + control.force_no_nna=p_force; +} diff --git a/modules/chibi/cp_player_data.h b/modules/chibi/cp_player_data.h new file mode 100644 index 000000000..215731985 --- /dev/null +++ b/modules/chibi/cp_player_data.h @@ -0,0 +1,582 @@ +/*************************************************************************/ +/* cp_player_data.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CP_PLAYER_DATA_H +#define CP_PLAYER_DATA_H + +#include "cp_config.h" +#include "cp_song.h" +#include "cp_mixer.h" +#include "cp_tables.h" + +/**CPPlayer Data + *@author Juan Linietsky + */ + +/****************************** + player_data.h + ------------------------ + +The player and its data. +I hope you dont get sick reading this +********************************/ + +//Default pan values + + +class CPPlayer { + + enum { + PAN_SURROUND=512, + PAN_RIGHT=255, + PAN_LEFT=0, + PAN_CENTER=128 + }; + + + CPSong *song; + + CPMixer *mixer; + + struct Filter_Control { + + int32_t it_reso; + int32_t it_cutoff; + int32_t envelope_cutoff; + int32_t final_cutoff; + + void process(); + void set_filter_parameters(int *p_cutoff,uint8_t *p_reso); + + }; + + //tells you if a channel is doing + //noteoff/notekill/notefade/etc + enum { + + END_NOTE_NOTHING=0, + END_NOTE_OFF=1, + END_NOTE_FADE=2, + END_NOTE_KILL=4 + }; + + //Tells you what should a channel restart + enum { + + KICK_NOTHING, + KICK_NOTE, + KICK_NOTEOFF, + KICK_ENVELOPE + }; + + enum { + + MAX_VOICES=256 + }; + + struct Channel_Control; + + struct Voice_Control { + + struct Envelope_Control { + + int pos_index; + int status; + int value; + bool sustain_looping; + bool looping; + bool terminated; + bool active; + bool kill; + + }; + + Filter_Control filter; + uint16_t reverb_send; + uint16_t chorus_send; + + CPInstrument* instrument_ptr; + CPSample* sample_ptr; + + //Sample_Data *sample_data; + + int32_t period; + + int32_t sample_start_index; /* The starting byte index in the sample */ + + bool has_master_channel; + int master_channel_index; + int instruement_index; + + int instrument_index; + int sample_index; + int8_t NNA_type; + + int note_end_flags; + + uint8_t sample; /* which instrument number */ + + int16_t output_volume; /* output volume (vol + sampcol + instvol) */ + int8_t channel_volume; /* channel's "global" volume */ + uint16_t fadeout_volume; /* fading volume rate */ + int32_t total_volume; /* total volume of channel (before global mixings) */ + uint8_t kick; /* if true = sample has to be restarted */ + + uint8_t note; /* the audible note (as heard, direct rep of period) */ + + int16_t panning; /* panning position */ + + uint8_t nna; /* New note action type + master/slave flags */ + uint8_t volflg; /* volume envelope settings */ + uint8_t panflg; /* panning envelope settings */ + uint8_t pitflg; /* pitch envelope settings */ + uint8_t keyoff; /* if true = fade out and stuff */ + int16_t handle; /* which sample-handle */ + int32_t start; /* The start byte index in the sample */ + + /* Below here is info NOT in MP_CONTROL!! */ + //ENVPR venv; + //ENVPR penv; + //ENVPR cenv; + + Envelope_Control volume_envelope_ctrl; + Envelope_Control panning_envelope_ctrl; + Envelope_Control pitch_envelope_ctrl; + + uint16_t auto_vibrato_pos; /* autovibrato pos */ + uint16_t auto_vibrato_sweep_pos; /* autovibrato sweep pos */ + + int16_t masterchn; + uint16_t masterperiod; + + Channel_Control* master_channel; /* index of "master" effects channel */ + + void start_envelope(CPEnvelope *p_envelope,Envelope_Control *p_envelope_ctrl,Envelope_Control *p_from_env); + bool process_envelope(CPEnvelope *p_envelope,Envelope_Control *p_envelope_ctrl); + + uint16_t display_volume; + + Voice_Control() { + + reset(); + } + + void reset(); + void update_info_from_master_channel(); + + + }; + + + struct Channel_Control { + + /* NOTE info */ + uint8_t note; /* the audible note as heard, direct rep of period */ + uint8_t real_note; /* the note that indexes the audible */ + int32_t sample_start_index; /* The starting byte index in the sample */ + uint8_t old_note; + + uint8_t kick; + + Filter_Control filter; + uint16_t reverb_send; + uint16_t chorus_send; + + + int note_end_flags; + + /* INSTRUMENT INFO */ + + CPInstrument* instrument_ptr; + CPSample* sample_ptr; + + uint8_t instrument_index; + uint8_t sample_index; + bool new_instrument; + + /* SAMPLE SPECIFIC INFO */ + int32_t base_speed; /* what finetune to use */ + + /* INSTRUMENT SPECIFIC INFO */ + + int8_t NNA_type; + int8_t duplicate_check_type; + int8_t duplicate_check_action; + + bool volume_envelope_on; + bool panning_envelope_on; + bool pitch_envelope_on; + + bool has_own_period; + + bool row_has_note; + + /* VOLUME COLUMN */ + + int16_t volume; /* amiga volume (0 t/m 64) to play the sample at */ + int16_t aux_volume; + bool has_own_volume; + bool mute; + int16_t random_volume_variation; /* 0-100 - 100 has no effect */ + + /* VOLUME/PAN/PITCH MODIFIERS */ + + int8_t default_volume; // CHANNEL default volume (0-64) + int16_t channel_volume; // CHANNEL current volume //chanvol - current! + int16_t output_volume; /* output volume (vol + sampcol + instvol) //volume */ + int16_t channel_global_volume; + + uint16_t fadeout_volume; /* fading volume rate */ + + int32_t period; /* period to play the sample at */ + + /* PAN */ + + int16_t panning; /* panning position */ + int16_t channel_panning; + int8_t sliding; + + uint16_t aux_period; /* temporary period */ + + + + /* TIMING */ + uint8_t note_delay; /* (used for note delay) */ + + /* Slave Voice Control */ + + Voice_Control *slave_voice; /* Audio Slave of current effects control channel */ + + struct Carry { + + Voice_Control::Envelope_Control vol; + Voice_Control::Envelope_Control pan; + Voice_Control::Envelope_Control pitch; + bool maybe; + + } carry; + + + + uint8_t slave_voice_index; /* Audio Slave of current effects control channel */ + + uint8_t* row; /* row currently playing on this channel */ + + /* effect memory variables */ + + uint8_t current_command; + uint8_t current_parameter; + uint8_t current_volume_command; + uint8_t current_volume_parameter; + uint8_t volcol_volume_slide; + + /* CPSample Offset */ + + int32_t lo_offset; + int32_t hi_offset; + + /* Panbrello waveform */ + uint8_t panbrello_type; /* current panbrello waveform */ + uint8_t panbrello_position; /* current panbrello position */ + int8_t panbrello_speed; /* "" speed */ + uint8_t panbrello_depth; /* "" depth */ + uint8_t panbrello_info; + /* Arpegio */ + + uint8_t arpegio_info; + /* CPPattern Loop */ + + int pattern_loop_position; + int8_t pattern_loop_count; + + /* Vibrato */ + bool doing_vibrato; + int8_t vibrato_position; /* current vibrato position */ + uint8_t vibrato_speed; /* "" speed */ + uint8_t vibrato_depth; /* "" depth */ + uint8_t vibrato_type; + /* Tremor */ + int8_t tremor_position; + uint8_t tremor_speed; /* s3m tremor ontime/offtime */ + uint8_t tremor_depth; + uint8_t tremor_info; + + /* Tremolo */ + int8_t tremolo_position; + uint8_t tremolo_speed; /* s3m tremor ontime/offtime */ + uint8_t tremolo_depth; + uint8_t tremolo_info; + uint8_t tremolo_type; + + /* Retrig */ + int8_t retrig_counter; /* retrig value (0 means don't retrig) */ + uint8_t retrig_speed; /* last used retrig speed */ + uint8_t retrig_volslide; /* last used retrig slide */ + + /* CPSample Offset */ + int32_t sample_offset_hi; /* last used high order of sample offset */ + uint16_t sample_offset; /* last used low order of sample-offset (effect 9) */ + uint16_t sample_offset_fine; /* fine sample offset memory */ + + /* Portamento */ + uint16_t slide_to_period; /* period to slide to (with effect 3 or 5) */ + uint8_t portamento_speed; + + /* Volume Slide */ + + uint8_t volume_slide_info; + + /* Channel Volume Slide */ + + uint8_t channel_volume_slide_info; + + /* Global Volume Slide */ + + uint8_t global_volume_slide_info; + + /* Channel Pan Slide */ + + uint8_t channel_pan_slide_info; + + /* Pitch Slide */ + + uint8_t pitch_slide_info; + /* Tempo Slide */ + + uint8_t tempo_slide_info; + + /* S effects memory */ + + uint8_t current_S_effect; + uint8_t current_S_data; + + /* Volume column memory */ + + uint8_t volume_column_effect_mem; + uint8_t volume_column_data_mem; + + int64_t last_event_usecs; + bool reserved; + + void reset(); + + Channel_Control() { channel_global_volume=255; last_event_usecs=-1; } + }; + + struct Control_Variables { // control variables (dynamic version) of initial variables + + bool reached_end; + + char play_mode; + bool filters; + int global_volume; + int speed; + int tempo; + + int ticks_counter; + + int pattern_delay_1; + int pattern_delay_2; + + Channel_Control channel[CPPattern::WIDTH]; + + int max_voices; + + int voices_used; /* reference value */ + + bool force_no_nna; + bool external_vibrato; + + struct Position { + + int current_order; + int current_pattern; + int current_row; + int force_next_order; + bool forbid_jump; + }; + + int32_t random_seed; + + Position position; + Position previous_position; + + }; + + + Voice_Control voice[MAX_VOICES]; + + Control_Variables control; + + /* VOICE SETUP */ + + void setup_voices(); + + /* MIXER SETUP */ + void handle_tick(); + void update_mixer(); + + /* NOTE / INSTRUMENT PROCESSING */ + + void process_new_note(int p_track,uint8_t p_note); + bool process_new_instrument(int p_track,uint8_t p_instrument); + bool process_note_and_instrument(int p_track,int p_note,int p_instrument); + + /* EFFECT PROCESSING */ + void do_effect_S(int p_track); + void do_panbrello(int p_track); + void do_global_volume_slide(int p_track); + void do_tremolo(int p_track); + void do_retrig(int p_track); + void do_pan_slide(int p_track); + void do_channel_volume_slide(int p_track); + void do_volume_slide(int p_track,int inf); + void do_pitch_slide_down(int p_track,uint8_t inf); + void do_pitch_slide_up(int p_track,uint8_t inf); + void do_tremor(int p_track); + void do_vibrato(int p_track,bool fine); + void do_pitch_slide_to_note(int p_track); + void run_effects(int p_track); + void run_volume_column_effects(int p_track); + void pre_process_effects(); + void do_arpegio(int p_track); + uint64_t song_usecs; + /* NNA */ + + void process_NNAs(); + + + /* MISC UTILS */ + + + int find_empty_voice(); + void process_volume_column(int p_track,uint8_t p_volume); + void process_note(int p_track,CPNote p_note); + + /* CPTables */ + static uint8_t auto_vibrato_table[128]; + static uint8_t vibrato_table[32]; + static int8_t panbrello_table[256]; + + static void callback_function(void *p_userdata); + +public: + //Play modes + + enum { + + PLAY_NOTHING =0, + PLAY_PATTERN =1, + PLAY_SONG =2 + }; + + + int32_t get_frequency(int32_t period); + int32_t get_period(uint16_t note,int32_t p_c5freq); + + + int get_current_tempo() { return control.tempo; }; + int get_current_speed() { return control.speed; }; + + int get_voices_used() { return control.voices_used;}; + int get_voice_envelope_pos(int p_voice,CPEnvelope *p_envelope); + int get_voice_amount_limit() { return control.max_voices; }; + void set_voice_amount_limit(int p_limit); + void set_reserved_voices(int p_amount); + int get_reserved_voices_amount(); + + bool is_voice_active(int p_voice); + int get_channel_voice(int p_channel); + const char* get_voice_sample_name(int p_voice); + const char* get_voice_instrument_name(int p_voice); + CPEnvelope* get_voice_envelope(int p_voice,CPInstrument::EnvelopeType p_env_type); + int get_voice_envelope_pos(int p_voice,CPInstrument::EnvelopeType p_env_type); + int get_voice_volume(int p_voice); + + int get_voice_sample_index(int p_voice); + + void set_virtual_channels(int p_amount); + int get_virtual_channels() { return control.max_voices; }; + + + /* Play Info/Position */ + bool is_playing() { return (control.play_mode>0); }; + int get_play_mode() {return (control.play_mode);}; + int get_current_order() { return control.position.current_order; }; + int get_current_row() { return control.position.current_row; }; + int get_current_pattern() { return control.position.current_pattern; }; + + void goto_next_order(); + void goto_previous_order(); + + void process_tick(); + + + CPMixer* get_mixer_ptr() { + + return mixer; + } + + + void reset(); + + + + /* External player control - editor - */ + + void play_start_pattern(int p_pattern); + void play_start_song(); + void play_start_song_from_order(int p_order); + void play_start_song_from_order_and_row(int p_order,int p_row); + void play_start(int p_pattern, int p_order, int p_row,bool p_lock=true); + + void play_stop(); + void play_note(int p_channel,CPNote note,bool p_reserve=false); + + bool reached_end_of_song(); + + void set_force_no_nna(bool p_force); + void set_force_external_vibratos(bool p_force); + + void set_filters_enabled(bool p_enable); + bool are_filters_enabled() { return control.filters; } + + void set_channel_global_volume(int p_channel,int p_volume); //0-255 + int get_channel_global_volume(int p_channel) const; + + int64_t get_channel_last_note_time_usec(int p_channel) const; + + CPSong *get_song() { return song; }; + + + CPPlayer(CPMixer *p_mixer,CPSong *p_song); + ~CPPlayer(); +}; + +#endif diff --git a/modules/chibi/cp_player_data_control.cpp b/modules/chibi/cp_player_data_control.cpp new file mode 100644 index 000000000..2ef1c1de8 --- /dev/null +++ b/modules/chibi/cp_player_data_control.cpp @@ -0,0 +1,324 @@ +/*************************************************************************/ +/* cp_player_data_control.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" + +void CPPlayer::play_start_pattern(int p_pattern) { + + play_start(p_pattern,-1,-1); +} + +void CPPlayer::play_start_song() { + + play_start(-1,-1,-1); +} + +void CPPlayer::play_start_song_from_order(int p_order) { + + play_start(-1,p_order,-1); +} + +void CPPlayer::play_start_song_from_order_and_row(int p_order,int p_row) { + + play_start(-1,p_order,p_row); +} + +void CPPlayer::play_start(int p_pattern, int p_order, int p_row,bool p_lock) { + + + if (control.play_mode!=PLAY_NOTHING) play_stop(); + + + reset(); + + if (p_pattern!=-1) { + + control.play_mode=PLAY_PATTERN; + control.position.current_pattern=p_pattern; + control.position.current_row=(p_row!=-1)?p_row:0; + + } else { + + control.position.current_order=get_song_next_order_idx(song,(p_order==-1)?p_order:p_order-1); + if (control.position.current_order!=-1) { + + control.play_mode=PLAY_SONG; + control.position.current_pattern=song->get_order(control.position.current_order); + control.position.current_row=(p_row!=-1)?p_row:0; + } + } + + + control.reached_end=(control.play_mode==PLAY_NOTHING); + + +} + +void CPPlayer::play_stop() { + + int i; + + + control.play_mode=PLAY_NOTHING; + + for (i=0;i<control.max_voices;i++) { + + voice[i].reset(); + mixer->stop_voice(i); + } + + for (i=0;i<CPPattern::WIDTH;i++) { + + control.channel[i].reset(); + } + + reset(); + +} + +void CPPlayer::play_note(int p_channel,CPNote note,bool p_reserve) { + + + + if (control.play_mode==PLAY_NOTHING) { + + control.ticks_counter=0; + } + + /*control.channel[p_channel].reset(); + control.channel[p_channel].channel_volume=song->get_channel_volume(p_channel); + control.channel[p_channel].channel_panning=((int)song->get_channel_pan( p_channel)*255/64);*/ + if (p_reserve) { + control.channel[p_channel].mute=false; + control.channel[p_channel].reserved=true; + } else { + + control.channel[p_channel].reserved=false; + + } + process_note(p_channel,note); + + + +} + + +int CPPlayer::get_voice_volume(int p_voice) { + + return voice[p_voice].display_volume; +} + + +int CPPlayer::get_voice_envelope_pos(int p_voice,CPEnvelope *p_envelope) { + + int i,tmp_index=-1; + + i=p_voice; + + + + + if ((song->has_instruments()) && (voice[i].instrument_ptr!=NULL) && (voice[i].fadeout_volume>0)) { + + if ((p_envelope==voice[i].instrument_ptr->get_volume_envelope()) && (voice[i].instrument_ptr->get_volume_envelope()->is_enabled())) { + + tmp_index=voice[i].volume_envelope_ctrl.pos_index; + } + + if ((p_envelope==voice[i].instrument_ptr->get_pan_envelope()) && (voice[i].instrument_ptr->get_pan_envelope()->is_enabled())) { + + tmp_index=voice[i].panning_envelope_ctrl.pos_index; + } + + if ((p_envelope==voice[i].instrument_ptr->get_pitch_filter_envelope()) && (voice[i].instrument_ptr->get_pitch_filter_envelope()->is_enabled())) { + + + tmp_index=voice[i].pitch_envelope_ctrl.pos_index; + } + + } + + + + return tmp_index; +} + +void CPPlayer::goto_next_order() { + + + if (control.play_mode!=PLAY_SONG) return; + + + + control.position.current_row=0; + + + control.position.current_order=get_song_next_order_idx(song, control.position.current_order); + + + + if (control.position.current_order==-1) { + + reset(); + } + + control.position.current_pattern=song->get_order(control.position.current_order); + + +} +void CPPlayer::goto_previous_order() { + + if (control.play_mode!=PLAY_SONG) return; + + + int next_order,current_order; + + control.position.current_row=0; + + current_order=control.position.current_order; + + next_order=get_song_next_order_idx(song, current_order); + + while ((next_order!=control.position.current_order) && (next_order!=-1)) { + + current_order=next_order; + next_order=get_song_next_order_idx(song, current_order); + } + + if (next_order==-1) { + + reset(); + } else { + + control.position.current_order=current_order; + control.position.current_pattern=song->get_order(control.position.current_order); + + } + + + +} + +int CPPlayer::get_channel_voice(int p_channel) { + + if (control.channel[p_channel].slave_voice==NULL) return -1; + else return control.channel[p_channel].slave_voice_index; +} + +const char* CPPlayer::get_voice_sample_name(int p_voice) { + + const char *name = NULL; + + + + if (!voice[p_voice].sample_ptr) name=voice[p_voice].sample_ptr->get_name(); + + + + return name; + +} + + +bool CPPlayer::is_voice_active(int p_voice) { + + return !( ((voice[p_voice].kick==KICK_NOTHING)||(voice[p_voice].kick==KICK_ENVELOPE))&&!mixer->is_voice_active(p_voice) ); + +} + + + +int CPPlayer::get_voice_envelope_pos(int p_voice,CPInstrument::EnvelopeType p_env_type) { + + if (!is_voice_active(p_voice)) + return -1; + + Voice_Control::Envelope_Control *env=0; + + switch (p_env_type) { + + case CPInstrument::VOLUME_ENVELOPE: env=&voice[p_voice].volume_envelope_ctrl; break; + case CPInstrument::PAN_ENVELOPE: env=&voice[p_voice].panning_envelope_ctrl; break; + case CPInstrument::PITCH_ENVELOPE: env=&voice[p_voice].pitch_envelope_ctrl; break; + + } + + if (!env) + return -1; + + if (!env->active || env->terminated) + return -1; + + return env->pos_index; +} + + +CPEnvelope* CPPlayer::get_voice_envelope(int p_voice,CPInstrument::EnvelopeType p_env_type) { + + CPInstrument *ins=voice[p_voice].instrument_ptr; + + if (!ins) + return 0; + + switch( p_env_type ) { + + + case CPInstrument::VOLUME_ENVELOPE: return ins->get_volume_envelope(); + case CPInstrument::PAN_ENVELOPE: return ins->get_pan_envelope(); + case CPInstrument::PITCH_ENVELOPE: return ins->get_pitch_filter_envelope(); + }; + + return 0; + +} + +const char * CPPlayer::get_voice_instrument_name(int p_voice) { + + + + const char *name = NULL; + + + + if (voice[p_voice].instrument_ptr!=NULL) name=voice[p_voice].instrument_ptr->get_name(); + + + + return name; + +} +void CPPlayer::set_filters_enabled(bool p_enable){ + + control.filters=p_enable; +} + +int CPPlayer::get_voice_sample_index(int p_voice) { + + return voice[p_voice].sample_index; +} diff --git a/modules/chibi/cp_player_data_effects.cpp b/modules/chibi/cp_player_data_effects.cpp new file mode 100644 index 000000000..56fb7a290 --- /dev/null +++ b/modules/chibi/cp_player_data_effects.cpp @@ -0,0 +1,1232 @@ +/*************************************************************************/ +/* cp_player_data_effects.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" + + + +/********************** + complex effects +***********************/ +#define RANDOM_MAX 2147483647 + +static inline int32_t cp_random_generate(int32_t *seed) { + int32_t k; + int32_t s = (int32_t)(*seed); + if (s == 0) + s = 0x12345987; + k = s / 127773; + s = 16807 * (s - k * 127773) - 2836 * k; + if (s < 0) + s += 2147483647; + (*seed) = (int32_t)s; + return (int32_t)(s & RANDOM_MAX); +} + + + +void CPPlayer::do_vibrato(int p_track,bool fine) { + + uint8_t q; + uint16_t temp=0; + + if ((control.ticks_counter==0) && control.channel[p_track].row_has_note) control.channel[p_track].vibrato_position=0; + + q=(control.channel[p_track].vibrato_position>>2)&0x1f; + + switch (control.channel[p_track].vibrato_type) { + case 0: /* sine */ + temp=vibrato_table[q]; + break; + case 1: /* square wave */ + temp=255; + break; + case 2: /* ramp down */ + q<<=3; + if (control.channel[p_track].vibrato_position<0) q=255-q; + temp=q; + break; + case 3: /* random */ + temp=cp_random_generate(&control.random_seed) %256;//getrandom(256); + break; + } + + temp*=control.channel[p_track].vibrato_depth; + + if (song->has_old_effects()) { + + temp>>=7; + } else { + + temp>>=8; + } + + if (!fine) temp<<=2; + + if (control.channel[p_track].vibrato_position>=0) { + + control.channel[p_track].period=control.channel[p_track].aux_period+temp; + } else { + + control.channel[p_track].period=control.channel[p_track].aux_period-temp; + } + + if (!song->has_old_effects() || control.ticks_counter) control.channel[p_track].vibrato_position+=control.channel[p_track].vibrato_speed; +} + + +void CPPlayer::do_pitch_slide_down(int p_track,uint8_t inf) { + + uint8_t hi,lo; + + if (inf) control.channel[p_track].pitch_slide_info=inf; + else inf=control.channel[p_track].pitch_slide_info; + + hi=inf>>4; + lo=inf&0xf; + + if (hi==0xf) { + + if (!control.ticks_counter) control.channel[p_track].aux_period+=(uint16_t)lo<<2; + } else if (hi==0xe) { + + if (!control.ticks_counter) control.channel[p_track].aux_period+=lo; + } else { + + if (control.ticks_counter) control.channel[p_track].aux_period+=(uint16_t)inf<<2; + } +} + +void CPPlayer::do_pitch_slide_up(int p_track,uint8_t inf) { + + uint8_t hi,lo; + + if (inf) control.channel[p_track].pitch_slide_info=inf; + else inf=control.channel[p_track].pitch_slide_info; + + hi=inf>>4; + lo=inf&0xf; + + if (hi==0xf) { + + if (!control.ticks_counter) control.channel[p_track].aux_period-=(uint16_t)lo<<2; + } else if (hi==0xe) { + + if (!control.ticks_counter) control.channel[p_track].aux_period-=lo; + } else { + + if (control.ticks_counter) control.channel[p_track].aux_period-=(uint16_t)inf<<2; + } +} + +void CPPlayer::do_pitch_slide_to_note(int p_track) { + + if (control.ticks_counter) { + int dist; + + /* We have to slide a->period towards a->wantedperiod, compute the + difference between those two values */ + dist=control.channel[p_track].period-control.channel[p_track].slide_to_period; + + /* if they are equal or if portamentospeed is too big... */ + if ((!dist)||((control.channel[p_track].portamento_speed<<2)>cp_intabs(dist))) { + /* ... make tmpperiod equal tperiod */ + control.channel[p_track].aux_period=control.channel[p_track].period=control.channel[p_track].slide_to_period; + } else { + + if (dist>0) { + + control.channel[p_track].aux_period-=control.channel[p_track].portamento_speed<<2; + control.channel[p_track].period-=control.channel[p_track].portamento_speed<<2; /* dist>0 slide up */ + } else { + control.channel[p_track].aux_period+=control.channel[p_track].portamento_speed<<2; + control.channel[p_track].period+=control.channel[p_track].portamento_speed<<2; /* dist<0 slide down */ + } + } + + } else { + + control.channel[p_track].aux_period=control.channel[p_track].period; + } +} + +void CPPlayer::do_tremor(int p_track) { + + uint8_t on,off,inf; + + inf=control.channel[p_track].current_parameter; + + if (inf) { + control.channel[p_track].tremor_info=inf; + } else { + inf= control.channel[p_track].tremor_info; + if (!inf) return; + } + + //if (!control.ticks_counter) return; + + on=(inf>>4); + off=(inf&0xf); + + control.channel[p_track].tremor_position%=(on+off); + control.channel[p_track].volume=(control.channel[p_track].tremor_position<on)?control.channel[p_track].aux_volume:0; + control.channel[p_track].tremor_position++; +} + +void CPPlayer::do_pan_slide(int p_track) { + + uint8_t lo,hi,inf; + int16_t pan; + + inf=control.channel[p_track].current_parameter; + + if (inf) control.channel[p_track].channel_pan_slide_info=inf; + else inf=control.channel[p_track].channel_pan_slide_info; + + lo=inf&0xf; + hi=inf>>4; + + pan=(control.channel[p_track].panning==PAN_SURROUND)?PAN_CENTER:control.channel[p_track].panning; + + if (!hi) + pan+=lo<<2; + else + if (!lo) { + pan-=hi<<2; + } else + if (hi==0xf) { + if (!control.ticks_counter) pan+=lo<<2; + } else + if (lo==0xf) { + if (!control.ticks_counter) pan-=hi<<2; + } + //this sets both chan & voice paning + control.channel[p_track].panning=(pan<PAN_LEFT)?PAN_LEFT:(pan>PAN_RIGHT?PAN_RIGHT:pan); + control.channel[p_track].channel_panning=control.channel[p_track].panning; +} + +void CPPlayer::do_volume_slide(int p_track,int inf) { + + uint8_t hi,lo; + + lo=inf&0xf; + hi=inf>>4; + + if (!lo) { + + if ((control.ticks_counter)) control.channel[p_track].aux_volume+=hi; + + } else if (!hi) { + + if ((control.ticks_counter)) control.channel[p_track].aux_volume-=lo; + + } else if (lo==0xf) { + + if (!control.ticks_counter) control.channel[p_track].aux_volume+=(hi?hi:0xf); + } else if (hi==0xf) { + + if (!control.ticks_counter) control.channel[p_track].aux_volume-=(lo?lo:0xf); + } else return; + + if (control.channel[p_track].aux_volume<0) { + + control.channel[p_track].aux_volume=0; + } else if (control.channel[p_track].aux_volume>64) { + + control.channel[p_track].aux_volume=64; + } +} + +void CPPlayer::do_channel_volume_slide(int p_track) { + + uint8_t lo, hi,inf; + + inf=control.channel[p_track].current_parameter; + + if (inf) control.channel[p_track].channel_volume_slide_info=inf; + inf=control.channel[p_track].channel_volume_slide_info; + + lo=inf&0xf; + hi=inf>>4; + + if (!hi) + control.channel[p_track].channel_volume-=lo; + else + if (!lo) { + control.channel[p_track].channel_volume+=hi; + } else + if (hi==0xf) { + if (!control.ticks_counter) control.channel[p_track].channel_volume-=lo; + } else + if (lo==0xf) { + if (!control.ticks_counter) control.channel[p_track].channel_volume+=hi; + } + + if (control.channel[p_track].channel_volume<0) control.channel[p_track].channel_volume=0; + if (control.channel[p_track].channel_volume>64) control.channel[p_track].channel_volume=64; +} + +void CPPlayer::do_tremolo(int p_track) { + + uint8_t q; + int16_t temp=0; + + if ((control.ticks_counter==0) && control.channel[p_track].row_has_note) control.channel[p_track].tremolo_position=0; + + q=(control.channel[p_track].tremolo_position>>2)&0x1f; + + switch (control.channel[p_track].tremolo_type) { + case 0: /* sine */ + temp=vibrato_table[q]; + break; + case 1: /* ramp down */ + q<<=3; + if (control.channel[p_track].tremolo_position<0) q=255-q; + temp=q; + break; + case 2: /* square wave */ + temp=255; + break; + case 3: /* random */ + temp=cp_random_generate(&control.random_seed) % 256;//getrandom(256); + break; + } + + temp*=control.channel[p_track].tremolo_depth; + temp>>=7; + + + + if (control.channel[p_track].tremolo_position>=0) { + + + control.channel[p_track].volume=control.channel[p_track].aux_volume+temp; + if (control.channel[p_track].volume>64) control.channel[p_track].volume=64; + } else { + + control.channel[p_track].volume=control.channel[p_track].aux_volume-temp; + if (control.channel[p_track].volume<0) control.channel[p_track].volume=0; + } + + /*if (control.ticks_counter)*/ control.channel[p_track].tremolo_position+=control.channel[p_track].tremolo_speed; + +} + +void CPPlayer::do_arpegio(int p_track) { + + uint8_t note,dat; + //note=control.channel[p_track].note; + note=0; + + if (control.channel[p_track].current_parameter) { + + control.channel[p_track].arpegio_info=control.channel[p_track].current_parameter; + } + + dat=control.channel[p_track].arpegio_info; + + if (dat) { + + switch (control.ticks_counter%3) { + + case 1: { + + note+=(dat>>4); + + } break; + case 2: { + + note+=(dat&0xf); + } break; + } + + if (song->has_linear_slides()) { + + control.channel[p_track].period=control.channel[p_track].aux_period-cp_intabs(get_period((uint16_t)46,0)-get_period((uint16_t)44,0))*note; + } else if (control.channel[p_track].sample_ptr) { + + control.channel[p_track].period=get_period( (((uint16_t)control.channel[p_track].note)+note)<<1,CPSampleManager::get_singleton()->get_c5_freq( (control.channel[p_track].sample_ptr->get_sample_data()))); + } + + control.channel[p_track].has_own_period=true; + } + + +} + + +void CPPlayer::do_retrig(int p_track) { + + uint8_t inf; + + inf=control.channel[p_track].current_parameter; + + if (inf) { + + control.channel[p_track].retrig_volslide=inf>>4; + control.channel[p_track].retrig_speed=inf&0xf; + } + + /* only retrigger if low nibble > 0 */ + if ( control.channel[p_track].retrig_speed>0) { + + if ( !control.channel[p_track].retrig_counter ) { + /* when retrig counter reaches 0, reset counter and restart the + sample */ + if (control.channel[p_track].kick!=KICK_NOTE) control.channel[p_track].kick=KICK_NOTEOFF; + control.channel[p_track].retrig_counter=control.channel[p_track].retrig_speed; + + + if ((control.ticks_counter)/*||(pf->flags&UF_S3MSLIDES)*/) { + switch (control.channel[p_track].retrig_volslide) { + case 1: + case 2: + case 3: + case 4: + case 5: + control.channel[p_track].aux_volume-=(1<<(control.channel[p_track].retrig_volslide-1)); + break; + case 6: + control.channel[p_track].aux_volume=(2*control.channel[p_track].aux_volume)/3; + break; + case 7: + control.channel[p_track].aux_volume>>=1; + break; + case 9: + case 0xa: + case 0xb: + case 0xc: + case 0xd: + control.channel[p_track].aux_volume+=(1<<(control.channel[p_track].retrig_volslide-9)); + break; + case 0xe: + control.channel[p_track].aux_volume=(3*control.channel[p_track].aux_volume)>>1; + break; + case 0xf: + control.channel[p_track].aux_volume=control.channel[p_track].aux_volume<<1; + break; + } + if (control.channel[p_track].aux_volume<0) control.channel[p_track].aux_volume=0; + else if (control.channel[p_track].aux_volume>64) control.channel[p_track].aux_volume=64; + } + } + control.channel[p_track].retrig_counter--; /* countdown */ + } +} + +void CPPlayer::do_global_volume_slide(int p_track) { + + uint8_t lo,hi,inf; + + inf=control.channel[p_track].current_parameter; + + if (inf) control.channel[p_track].global_volume_slide_info=inf; + inf=control.channel[p_track].global_volume_slide_info; + + lo=inf&0xf; + hi=inf>>4; + + if (!lo) { + if (control.ticks_counter) control.global_volume+=hi; + } else + if (!hi) { + if (control.ticks_counter) control.global_volume-=lo; + } else + if (lo==0xf) { + if (!control.ticks_counter) control.global_volume+=hi; + } else + if (hi==0xf) { + if (!control.ticks_counter) control.global_volume-=lo; + } + + if (control.global_volume<0) control.global_volume=0; + if (control.global_volume>128) control.global_volume=128; +} + +void CPPlayer::do_panbrello(int p_track) { + + uint8_t q; + int32_t temp=0; + + q=control.channel[p_track].panbrello_position; + + switch (control.channel[p_track].panbrello_type) { + case 0: {/* sine */ + temp=panbrello_table[q]; + } break; + case 1: {/* square wave */ + temp=(q<0x80)?64:0; + } break; + case 2: {/* ramp down */ + q<<=3; + temp=q; + } break; + case 3: {/* random */ + if (control.channel[p_track].panbrello_position>=control.channel[p_track].panbrello_speed) { + control.channel[p_track].panbrello_position=0; + temp=cp_random_generate(&control.random_seed)%256;//getrandom(256); + } + } break; + } + + + + temp=temp*(int)control.channel[p_track].panbrello_depth/0xF; + temp<<=1; + if (control.channel[p_track].channel_panning!=PAN_SURROUND) + temp+=control.channel[p_track].channel_panning; + + control.channel[p_track].panning=(temp<PAN_LEFT)?PAN_LEFT:(temp>PAN_RIGHT?PAN_RIGHT:temp); + control.channel[p_track].panbrello_position+=control.channel[p_track].panbrello_speed; +} + +/****************** + S effect +*******************/ + + +void CPPlayer::do_effect_S(int p_track) { + + uint8_t inf,c,dat; + + dat=control.channel[p_track].current_parameter; + + inf=dat&0xf; + c=dat>>4; + + if (!dat) { + c=control.channel[p_track].current_S_effect; + inf=control.channel[p_track].current_S_data; + } else { + control.channel[p_track].current_S_effect=c; + control.channel[p_track].current_S_data=inf; + } + + switch (c) { + case 1: {/* S1x set glissando voice */ + // this is unsupported in IT! + + control.channel[p_track].chorus_send=inf*0xFF/0xF; + + }break; + case 2: /* S2x set finetune */ + //Also not supported! + break; + case 3: /* S3x set vibrato waveform */ + if (inf<4) control.channel[p_track].vibrato_type=inf; + break; + case 4: /* S4x set tremolo waveform */ + if (inf<4) control.channel[p_track].tremolo_type=inf; + break; + case 5: /* S5x panbrello */ + if (inf<4) control.channel[p_track].panbrello_type=inf; + break; + case 6: {/* S6x delay x number of frames (patdly) */ + + if (control.ticks_counter) break; + if (!control.pattern_delay_2) control.pattern_delay_1=inf+1; /* only once, when vbtick=0 */ + + } break; + case 7: /* S7x instrument / NNA commands */ + + if (!song->has_instruments()) + break; + switch(inf) { + + case 0x3: { + + control.channel[p_track].NNA_type=CPInstrument::NNA_NOTE_CUT; + } break; + case 0x4: { + + control.channel[p_track].NNA_type=CPInstrument::NNA_NOTE_CONTINUE; + } break; + case 0x5: { + + control.channel[p_track].NNA_type=CPInstrument::NNA_NOTE_OFF; + } break; + case 0x6: { + + control.channel[p_track].NNA_type=CPInstrument::NNA_NOTE_FADE; + } break; + case 0x7: { + + if (control.channel[p_track].slave_voice) + control.channel[p_track].slave_voice->volume_envelope_ctrl.active=false; + } break; + case 0x8: { + + if (control.channel[p_track].slave_voice) + control.channel[p_track].slave_voice->volume_envelope_ctrl.active=true; + + } break; + case 0x9: { + + if (control.channel[p_track].slave_voice) + control.channel[p_track].slave_voice->panning_envelope_ctrl.active=false; + + } break; + case 0xA: { + + if (control.channel[p_track].slave_voice) + control.channel[p_track].slave_voice->panning_envelope_ctrl.active=true; + + } break; + case 0xB: { + if (control.channel[p_track].slave_voice) + control.channel[p_track].slave_voice->pitch_envelope_ctrl.active=false; + + } break; + case 0xC: { + + if (control.channel[p_track].slave_voice) + control.channel[p_track].slave_voice->pitch_envelope_ctrl.active=true; + + } break; + + } break; + + break; + case 8: {/* S8x set panning position */ + + //if (pf->panflag) { + if (inf<=8) inf<<=4; + else inf*=17; + control.channel[p_track].panning=control.channel[p_track].channel_panning=inf; +// } + } break; + + case 9: { /* S9x set surround sound */ + //if (pf->panflag) + control.channel[p_track].panning=control.channel[p_track].channel_panning=PAN_SURROUND; + } break; + case 0xA:{ /* SAy set high order sample offset yxx00h */ + + if (control.channel[p_track].current_parameter) control.channel[p_track].hi_offset=(int32_t)inf<<16; + control.channel[p_track].sample_start_index=control.channel[p_track].hi_offset|control.channel[p_track].lo_offset; + } break; + case 0xB: { /* SBx pattern loop */ + if (control.ticks_counter) break; + + if (inf) { /* set reppos or repcnt ? */ + /* set repcnt, so check if repcnt already is set, which means we + are already looping */ + if (control.channel[p_track].pattern_loop_count>0) + control.channel[p_track].pattern_loop_count--; /* already looping, decrease counter */ + else { + control.channel[p_track].pattern_loop_count=inf; /* not yet looping, so set repcnt */ + } + + if (control.channel[p_track].pattern_loop_count>0) { /* jump to reppos if repcnt>0 */ + + control.position=control.previous_position; // This will also anulate any Cxx or break.. + + control.position.current_row=control.channel[p_track].pattern_loop_position; + control.position.forbid_jump=true; + } + + } else { + + + control.channel[p_track].pattern_loop_position=control.position.current_row-1; + } + + } break; + case 0xC: { /* SCx notecut */ + + if (control.ticks_counter>=inf) { + + control.channel[p_track].aux_volume=0; + control.channel[p_track].note_end_flags|=END_NOTE_OFF; + control.channel[p_track].note_end_flags|=END_NOTE_KILL; + } + } break; + case 0xD: {/* SDx notedelay */ + + if (!control.ticks_counter) { + + control.channel[p_track].note_delay=inf; + + } else if (control.channel[p_track].note_delay) { + + control.channel[p_track].note_delay--; + } + + } break; + case 0xF: {/* SEx patterndelay */ + + if (control.ticks_counter) break; + if (!control.pattern_delay_2) control.pattern_delay_1=inf+1; /* only once, when vbtick=0 */ + + } break; + } +} + + + + + + + + +/********************* + volume effects +**********************/ + +void CPPlayer::run_volume_column_effects(int p_track) { + + uint8_t param=control.channel[p_track].current_volume_parameter; + + + switch ('A'+control.channel[p_track].current_volume_command) { + + case 'A': { + + if (param>0) control.channel[p_track].volcol_volume_slide=param; + else param=control.channel[p_track].volcol_volume_slide; + + do_volume_slide(p_track,param*0x10+0xF); + + } break; + case 'B': { + + if (param>0) control.channel[p_track].volcol_volume_slide=param; + else param=control.channel[p_track].volcol_volume_slide; + + do_volume_slide(p_track,0xF0+param); + + } break; + case 'C': { + + if (param>0) control.channel[p_track].volcol_volume_slide=param; + else param=control.channel[p_track].volcol_volume_slide; + + do_volume_slide(p_track,param*0x10); + } break; + case 'D': { + + if (param>0) control.channel[p_track].volcol_volume_slide=param; + else param=control.channel[p_track].volcol_volume_slide; + do_volume_slide(p_track,param); + + } break; + case 'E': { + + do_pitch_slide_down(p_track,param<<2); + } break; + case 'F': { + + do_pitch_slide_up(p_track,param<<2); + } break; + case 'G': { + + const uint8_t slide_table[]={0,1,4,8,16,32,64,96,128,255}; + if (param) { + + control.channel[p_track].portamento_speed=slide_table[param]; + } + + if (control.channel[p_track].period && (control.channel[p_track].old_note<=120)) { + + if ( (!control.ticks_counter) && (control.channel[p_track].new_instrument) ){ + + //control.channel[p_track].kick=KICK_NOTE; + //control.channel[p_track].sample_start_index=0; // < am i stupid? + } else { + + control.channel[p_track].kick=(control.channel[p_track].kick==KICK_NOTE)?KICK_ENVELOPE:KICK_NOTHING; + do_pitch_slide_to_note(p_track); + control.channel[p_track].has_own_period=true; + } + + } + } break; + case 'H': { + + + if (!control.ticks_counter) { + if (param&0x0f) control.channel[p_track].vibrato_depth=param; + } + control.channel[p_track].doing_vibrato=true; + if (control.external_vibrato) break; + if (control.channel[p_track].period) { + + do_vibrato(p_track,false); + control.channel[p_track].has_own_period=true; + } + + } break; + } +} +/********************* + table +**********************/ + + +void CPPlayer::run_effects(int p_track) { + + switch ('A'+control.channel[p_track].current_command) { + + case 'A': { + + if ((control.ticks_counter>0) || (control.pattern_delay_2>0)) break; + + int new_speed; + + new_speed=control.channel[p_track].current_parameter % 128; + + if (new_speed>0) { + control.speed=new_speed; + control.ticks_counter=0; + } + } break; + case 'B': { + + int next_order; + + if (control.ticks_counter || control.position.forbid_jump) break; + + control.position.current_row=0; + + if (control.play_mode==PLAY_PATTERN) break; + + next_order=get_song_next_order_idx(song, (int)control.channel[p_track].current_parameter-1); + + if (next_order!=-1) { + // Do we have a "next order?" + control.position.current_pattern=song->get_order(next_order); + control.position.force_next_order=next_order; + + } else { + // no, probably the user deleted the orderlist. + control.play_mode=PLAY_NOTHING; + reset(); + } + } break; + case 'C': { + + int next_order; + + if (control.ticks_counter || control.position.forbid_jump) break; + + control.position.current_row=control.channel[p_track].current_parameter; + + if (control.play_mode==PLAY_PATTERN) { + + if (control.position.current_row>=song->get_pattern(control.position.current_pattern)->get_length()) { + + control.position.current_row=0; + } + + break; + } + + next_order=get_song_next_order_idx(song, (int)control.position.current_order); + + if (next_order!=-1) { + // Do we have a "next order?" + control.position.current_pattern=song->get_order(next_order); + + if (control.position.current_row>=song->get_pattern(song->get_order(next_order))->get_length()) { + + control.position.current_row=0; + } + + control.position.force_next_order=next_order; + + } else { + // no, probably the user deleted the orderlist. + control.play_mode=PLAY_NOTHING; + reset(); + } + + } break; + case 'D': { + + uint8_t inf ; + //explicitslides=1; + inf=control.channel[p_track].current_parameter; + + if (inf) control.channel[p_track].volume_slide_info=inf; + else inf=control.channel[p_track].volume_slide_info; + + do_volume_slide(p_track,inf); + + } break; + case 'E': { + + uint8_t inf; + + inf=control.channel[p_track].current_parameter; + do_pitch_slide_down(p_track,inf); + + } break; + case 'F': { + + uint8_t inf; + + inf=control.channel[p_track].current_parameter; + do_pitch_slide_up(p_track,inf); + + } break; + case 'G': { + + if (control.channel[p_track].current_parameter) { + + control.channel[p_track].portamento_speed=control.channel[p_track].current_parameter; + } + + if (control.channel[p_track].period && (control.channel[p_track].old_note<=120)) { + + if ( (!control.ticks_counter) && (control.channel[p_track].new_instrument) ){ + + + control.channel[p_track].kick=KICK_NOTE; + control.channel[p_track].sample_start_index=0; + + } else { + + control.channel[p_track].kick=(control.channel[p_track].kick==KICK_NOTE)?KICK_ENVELOPE:KICK_NOTHING; + } + + do_pitch_slide_to_note(p_track); + control.channel[p_track].has_own_period=true; + } + + } break; + case 'H': { + + uint8_t dat; + + control.channel[p_track].doing_vibrato=true; + + dat=control.channel[p_track].current_parameter; + + if (!control.ticks_counter) { + if (dat&0x0f) control.channel[p_track].vibrato_depth=dat&0xf; + if (dat&0xf0) control.channel[p_track].vibrato_speed=(dat&0xf0)>>2; + } + + if (control.external_vibrato) break; + + if (control.channel[p_track].period) { + + do_vibrato(p_track,false); + control.channel[p_track].has_own_period=true; + } + + } break; + case 'I': { + + do_tremor(p_track); + control.channel[p_track].has_own_volume=true; + } break; + case 'J': { + + do_arpegio(p_track); + } break; + case 'K': { + + uint8_t inf ; + //explicitslides=1; + inf=control.channel[p_track].current_parameter; + + control.channel[p_track].doing_vibrato=true; + + + if (inf) control.channel[p_track].volume_slide_info=inf; + else inf=control.channel[p_track].volume_slide_info; + + do_volume_slide(p_track,inf); + + if (control.external_vibrato) break; + + if (control.channel[p_track].period) { + + do_vibrato(p_track,false); + control.channel[p_track].has_own_period=true; + } + + } break; + case 'L': { + uint8_t inf ; + //explicitslides=1; + inf=control.channel[p_track].current_parameter; + + if (inf) control.channel[p_track].volume_slide_info=inf; + else inf=control.channel[p_track].volume_slide_info; + + do_volume_slide(p_track,inf); + + if (control.channel[p_track].period && (control.channel[p_track].old_note<=120)) { + if ( (!control.ticks_counter) && (control.channel[p_track].new_instrument) ){ + + control.channel[p_track].kick=KICK_NOTE; + control.channel[p_track].sample_start_index=0; + + } else { + + control.channel[p_track].kick=(control.channel[p_track].kick==KICK_NOTE)?KICK_ENVELOPE:KICK_NOTHING; + } + + do_pitch_slide_to_note(p_track); + control.channel[p_track].has_own_period=true; + } + } break; + case 'M': { + control.channel[p_track].channel_volume=control.channel[p_track].current_parameter; + if (control.channel[p_track].channel_volume>64) control.channel[p_track].channel_volume=64; + else if (control.channel[p_track].channel_volume<0) control.channel[p_track].channel_volume=0; + } break; + case 'N': { + + do_channel_volume_slide(p_track); + } + case 'O': { + + if (!control.ticks_counter) { + + if (control.channel[p_track].current_parameter) control.channel[p_track].lo_offset=(uint16_t)control.channel[p_track].current_parameter<<8; + control.channel[p_track].sample_start_index=control.channel[p_track].hi_offset|control.channel[p_track].lo_offset; + + //if ((control.channel[p_track].sample_ptr!=NULL)&&(control.channel[p_track].sample_start_index>control.channel[p_track].sample_ptr->data.size)) { + //TODO, O effect + //a->start=a->s->flags&(SF_LOOP|SF_BIDI)?a->s->loopstart:a->s->length; + //} + } + } break; + case 'P': { + + do_pan_slide(p_track); + } break; + case 'Q': { + do_retrig(p_track); + + } break; + case 'R': { + + + uint8_t dat; + + if (control.channel[p_track].current_parameter) { + + control.channel[p_track].tremolo_info=control.channel[p_track].current_parameter; + } + + dat=control.channel[p_track].tremolo_info; + + if (!control.ticks_counter && dat) { + + if (dat&0x0f) control.channel[p_track].tremolo_depth=dat&0xf; + if (dat&0xf0) control.channel[p_track].tremolo_speed=(dat&0xf0)>>2; + } + + do_tremolo(p_track); + control.channel[p_track].has_own_volume=true; + + } break; + case 'S': { + + do_effect_S(p_track); + } break; + case 'T': { + uint8_t dat; + int16_t temp=control.tempo; + + if (control.pattern_delay_2) return; + + if (control.channel[p_track].current_parameter) { + + control.channel[p_track].tempo_slide_info=control.channel[p_track].current_parameter; + } + + dat=control.channel[p_track].tempo_slide_info; + + if (dat>=0x20) { + + if (control.ticks_counter) break; + control.tempo=dat; + } else { + + if (!control.ticks_counter) break; + + if (dat&0x10) { + + temp+=(dat&0x0f); + } else { + + temp-=dat; + } + control.tempo=(temp>255)?255:(temp<0x20?0x20:temp); + } + + } break; + case 'U': { + + uint8_t dat; + + dat=control.channel[p_track].current_parameter; + control.channel[p_track].doing_vibrato=true; + if (!control.ticks_counter) { + if (dat&0x0f) control.channel[p_track].vibrato_depth=dat&0xf; + if (dat&0xf0) control.channel[p_track].vibrato_speed=(dat&0xf0)>>2; + } + + if (control.external_vibrato) break; + + if (control.channel[p_track].period) { + + do_vibrato(p_track,true); + control.channel[p_track].has_own_period=true; + } + } break; + case 'V': { + + control.global_volume=control.channel[p_track].current_parameter; + if (control.global_volume>128) control.global_volume=128; + } break; + case 'W': { + do_global_volume_slide(p_track); + } break; + case 'X': { + //sets both channel and current + control.channel[p_track].channel_panning=control.channel[p_track].current_parameter; + control.channel[p_track].panning=control.channel[p_track].current_parameter; + } break; + case 'Y': { + + uint8_t dat; + + if (control.channel[p_track].current_parameter) { + + control.channel[p_track].panbrello_info=control.channel[p_track].current_parameter; + } + + dat=control.channel[p_track].panbrello_info; + + if (!control.ticks_counter) { + + if (dat&0x0f) control.channel[p_track].panbrello_depth=(dat&0xf); + if (dat&0xf0) control.channel[p_track].panbrello_speed=(dat&0xf0)>>4; + } + + //if (pf->panflag) + if (control.channel[p_track].panning!=PAN_SURROUND)do_panbrello(p_track); + + } break; + case 'Z': { + //I DO! cuttoff! + uint16_t dat=control.channel[p_track].current_parameter; + + if (dat<0x80) { + + control.channel[p_track].filter.it_cutoff=dat*2; + if (control.channel[p_track].filter.it_cutoff>0x80) + control.channel[p_track].filter.it_cutoff++; + } else if (dat<0x90) { + + control.channel[p_track].filter.it_reso=(dat-0x80)*0x10; + } else { + + control.channel[p_track].reverb_send=(dat-0x90)*255/0x6F; + } + + } break; + + } + +} + +void CPPlayer::pre_process_effects() { + +// MP_VOICE *aout; + int i; + + for (i=0;i<CPPattern::WIDTH;i++) { + + //a=&pf->control[mp_channel]; + + // if ((aout=a->slave)) { + // a->fadevol=aout->fadevol; + // a->period=aout->period; + // if (a->kick==KICK_KEYOFF) a->keyoff=aout->keyoff; + //} + + //if (!a->row) continue; + //UniSetRow(a->row); + control.channel[i].has_own_period=false; + control.channel[i].has_own_volume=false; + control.channel[i].doing_vibrato=false; + //explicitslides=0; + //pt_playeffects(); + if (control.ticks_counter<control.speed) { + + run_effects(i); + run_volume_column_effects(i); + } + + /* continue volume slide if necessary for XM and IT */ + //if (pf->flags&UF_BGSLIDES) { + // if (!explicitslides && a->sliding) + // DoS3MVolSlide(0); + // else if (a->tmpvolume) a->sliding=explicitslides; + //} + + if (!control.channel[i].has_own_period) control.channel[i].period=control.channel[i].aux_period; + if (!control.channel[i].has_own_volume) control.channel[i].volume=control.channel[i].aux_volume; + + if ((control.channel[i].sample_ptr!=NULL) && !(song->has_instruments() && (control.channel[i].instrument_ptr==NULL))) { + + if (song->has_instruments()) { + + control.channel[i].output_volume= + (control.channel[i].volume*control.channel[i].sample_ptr->get_global_volume()*control.channel[i].instrument_ptr->get_volume_global_amount())/2048; + control.channel[i].output_volume=control.channel[i].output_volume*control.channel[i].random_volume_variation/100; + + } else { + + control.channel[i].output_volume= + (control.channel[i].volume*control.channel[i].sample_ptr->get_global_volume())>>4; + + } + + if (control.channel[i].output_volume>256) { + + control.channel[i].output_volume=256; + + } else if (control.channel[i].output_volume<0) { + + control.channel[i].output_volume=0; + } + + + } + } + +} diff --git a/modules/chibi/cp_player_data_envelopes.cpp b/modules/chibi/cp_player_data_envelopes.cpp new file mode 100644 index 000000000..a720eaf73 --- /dev/null +++ b/modules/chibi/cp_player_data_envelopes.cpp @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* cp_player_data_envelopes.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" + + +void CPPlayer::Voice_Control::start_envelope(CPEnvelope *p_envelope,Envelope_Control *p_envelope_ctrl,Envelope_Control *p_from_env) { + + + if (p_from_env && p_envelope->is_carry_enabled() && !p_from_env->terminated) { + + + *p_envelope_ctrl=*p_from_env; + } else { + p_envelope_ctrl->pos_index=0; + p_envelope_ctrl->status=1; + p_envelope_ctrl->sustain_looping=p_envelope->is_sustain_loop_enabled(); + p_envelope_ctrl->looping=p_envelope->is_loop_enabled(); + p_envelope_ctrl->terminated=false; + p_envelope_ctrl->kill=false; + p_envelope_ctrl->value=p_envelope->get_height_at_pos(p_envelope_ctrl->pos_index); + } +} + +bool CPPlayer::Voice_Control::process_envelope(CPEnvelope *p_envelope,Envelope_Control *p_envelope_ctrl) { + + if (!p_envelope_ctrl->active) + return false; + + if (note_end_flags&END_NOTE_OFF) p_envelope_ctrl->sustain_looping=false; + + p_envelope_ctrl->value=p_envelope->get_height_at_pos(p_envelope_ctrl->pos_index); + if (p_envelope_ctrl->value==CPEnvelope::NO_POINT) + return false; + + + p_envelope_ctrl->pos_index++; + + if (p_envelope_ctrl->sustain_looping) { + + if (p_envelope_ctrl->pos_index>p_envelope->get_node(p_envelope->get_sustain_loop_end()).tick_offset) { + + p_envelope_ctrl->pos_index=p_envelope->get_node(p_envelope->get_sustain_loop_begin()).tick_offset; + } + + } else if (p_envelope_ctrl->looping) { + + if (p_envelope_ctrl->pos_index>p_envelope->get_node(p_envelope->get_loop_end()).tick_offset) { + + p_envelope_ctrl->pos_index=p_envelope->get_node(p_envelope->get_loop_begin()).tick_offset; + } + + } + + if (p_envelope_ctrl->pos_index>p_envelope->get_node(p_envelope->get_node_count()-1).tick_offset) { + + p_envelope_ctrl->terminated=true; + p_envelope_ctrl->pos_index=p_envelope->get_node(p_envelope->get_node_count()-1).tick_offset; + if (p_envelope->get_node(p_envelope->get_node_count()-1).value==0) p_envelope_ctrl->kill=true; + } + + return true; +} diff --git a/modules/chibi/cp_player_data_events.cpp b/modules/chibi/cp_player_data_events.cpp new file mode 100644 index 000000000..94a048ab2 --- /dev/null +++ b/modules/chibi/cp_player_data_events.cpp @@ -0,0 +1,681 @@ +/*************************************************************************/ +/* cp_player_data_events.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" +#include "cp_sample_manager.h" +#include "stdio.h" +/* + setup_voices(): + +This will go throught all the REAL channels, if it finds a channel +that needs to be restarted or assigned a new VIRTUAL channel, then it +will just find one and do it. + +*/ + + +#define C5FREQ 261.6255653006 +static const int32_t C5FREQ_MIXER = ((int32_t)(C5FREQ*(float)(1<<CPMixer::FREQUENCY_BITS))); + + +void CPPlayer::setup_voices() { + + int i,voice_index; + + + + + for (i=0;i<CPPattern::WIDTH;i++) { + + voice_index=-1; + + if (control.channel[i].note_delay) continue; + + + // check if we need a new empty voice + if (control.channel[i].kick==KICK_NOTE) { + + /* if no channel was cut above, find an empty or quiet channel + here */ + if ( song->has_instruments() && !control.force_no_nna) { + + if (control.channel[i].slave_voice==NULL) { // no slave?? + + int newchn; + if ((newchn=find_empty_voice())!=-1) { + + control.channel[i].slave_voice_index=newchn; + control.channel[i].slave_voice=&voice[newchn]; + } + } + + } else { + if (i<control.max_voices) { + + control.channel[i].slave_voice_index=i; + control.channel[i].slave_voice=&voice[i]; + } else { + //This is a _DIRTY_ hack, but i cant think a better way. + control.channel[i].slave_voice_index=control.max_voices-1; + control.channel[i].slave_voice=&voice[control.max_voices-1]; + } + + } + + + /* assign parts of MP_VOICE only done for a KICK_NOTE */ + if ( ( control.channel[i].slave_voice!=NULL ) ) { + + voice_index=control.channel[i].slave_voice_index; + Voice_Control &v=voice[voice_index]; + + if (v.has_master_channel && (v.master_channel!=NULL) ) { + // If this voice already has a master channel, make sure to remove the reference to it. + v.master_channel->slave_voice=NULL; + + } + //notify the voice that the current channel is the master + v.master_channel=&control.channel[i]; + //set the voice as slave of the current channel + control.channel[i].slave_voice=&v; + //master channel index of the voice + v.master_channel_index=i; + v.has_master_channel=true; + } + + } else { + // nope.. + // so if we DO have a slave voice then use it. + if ( control.channel[i].slave_voice!=NULL ) { + + voice_index=control.channel[i].slave_voice_index; + } + } + //assuming this channel has a slave voice.. + if (voice_index>=0) { + + // IMPROVE: Code a method for this: + voice[voice_index].update_info_from_master_channel(); + } + + control.channel[i].kick=KICK_NOTHING; + } +} +void CPPlayer::Voice_Control::reset() { + + cp_memzero(this,sizeof(*this)); + + instrument_ptr=NULL; + sample_ptr=NULL; + has_master_channel=false; + instrument_index=-1; + reverb_send=0; + chorus_send=0; + filter.it_cutoff=255; + filter.it_reso=0; + display_volume=0; + + +} + +void CPPlayer::Channel_Control::reset() { + + int prev_gv =channel_global_volume; + cp_memzero(this,sizeof(*this)); + + slave_voice=NULL; + slave_voice_index=255; + + mute=false; + old_note=255; + real_note=255; + instrument_index=255; + filter.it_cutoff=255; + filter.it_reso=0; + reverb_send=0; + chorus_send=0; + reserved=false; + carry.maybe=false; + last_event_usecs=-1; + channel_global_volume=prev_gv; +} + +void CPPlayer::Voice_Control::update_info_from_master_channel() { + + instrument_ptr=master_channel->instrument_ptr; + sample_ptr=master_channel->sample_ptr; + + instrument_index=master_channel->instrument_index; + sample_index=master_channel->sample_index; + + note=master_channel->note; + output_volume=master_channel->output_volume; + + channel_volume=master_channel->channel_volume; + + panning=master_channel->panning; + + kick=master_channel->kick; + note_end_flags=master_channel->note_end_flags; + period=master_channel->period; + + volume_envelope_ctrl.active=master_channel->volume_envelope_on; + panning_envelope_ctrl.active=master_channel->panning_envelope_on; + pitch_envelope_ctrl.active=master_channel->pitch_envelope_on; + + + NNA_type=master_channel->NNA_type; + reverb_send=master_channel->reverb_send; + chorus_send=master_channel->chorus_send; + + //last_note_type=master_channel->last_note_type; + + sample_start_index=master_channel->sample_start_index; + filter=master_channel->filter; + +} + + +void CPPlayer::update_mixer() { + + int tmp_mixer_period; + int32_t tmp_vibrato_value,tmp_vibrato_depth,tmp_volenv_value; + uint64_t tmpvol; // 64bits should be the only way to avoid getting notes raped out + int i; + + + control.voices_used=0; + + for (i=0;i<control.max_voices;i++) { + + + int filter_env=-1; + Voice_Control &v=voice[i]; + + if ( !((v.kick==KICK_NOTE)||(v.kick==KICK_NOTEOFF)) && !is_voice_active(i)) + continue; + + //if voice doesnt have a sample set or size is 0.. forget it + if ( v.sample_ptr==NULL) continue; + + + //TODO set limits somewhere else + + if (v.period<40) { + + v.period=40; + + } else if (v.period>50000) { + + v.period=50000; + } + + + if ((v.kick==KICK_NOTE)||(v.kick==KICK_NOTEOFF)) { + + int real_start_index; + + if (v.sample_start_index==-1) { + + real_start_index=0; + + } else { + + real_start_index=v.sample_start_index; + } + + mixer->setup_voice(i,v.sample_ptr->get_sample_data(),real_start_index); + v.fadeout_volume=1024; //IT Docs it is 1024 internally + v.auto_vibrato_sweep_pos=0; + + + + } + + + /* Start Envelopes */ + if ( song->has_instruments() && ((v.kick==KICK_NOTE)||(v.kick==KICK_ENVELOPE))) { + + //Voice_Control *carry=0; + + + if (v.has_master_channel && v.master_channel->carry.maybe) { + + v.start_envelope(v.instrument_ptr->get_volume_envelope(),&v.volume_envelope_ctrl,&v.master_channel->carry.vol); + v.start_envelope(v.instrument_ptr->get_pan_envelope(),&v.panning_envelope_ctrl,&v.master_channel->carry.pan); + v.start_envelope(v.instrument_ptr->get_pitch_filter_envelope(),&v.pitch_envelope_ctrl,&v.master_channel->carry.pitch); + + } else { + + v.start_envelope(v.instrument_ptr->get_volume_envelope(),&v.volume_envelope_ctrl,NULL); + v.start_envelope(v.instrument_ptr->get_pan_envelope(),&v.panning_envelope_ctrl,NULL); + v.start_envelope(v.instrument_ptr->get_pitch_filter_envelope(),&v.pitch_envelope_ctrl,NULL); + + } + + + } + + v.kick=KICK_NOTHING; + + if (song->has_instruments()) { + + if (!v.process_envelope(v.instrument_ptr->get_volume_envelope(),&v.volume_envelope_ctrl)) + v.volume_envelope_ctrl.value=64; + + if (!v.process_envelope(v.instrument_ptr->get_pan_envelope(),&v.panning_envelope_ctrl)) + v.panning_envelope_ctrl.value=0; + + if (!v.process_envelope(v.instrument_ptr->get_pitch_filter_envelope(),&v.pitch_envelope_ctrl)) + v.pitch_envelope_ctrl.value=0; + + + if (v.volume_envelope_ctrl.terminated) { + + if (v.volume_envelope_ctrl.kill) { + + v.fadeout_volume=0; + } else { + + v.note_end_flags|=END_NOTE_FADE; + } + } + + } + + if (song->has_instruments()) { + + tmp_volenv_value=v.volume_envelope_ctrl.value; + } else { + + tmp_volenv_value=64; + + } + + /*printf("fadeout %i\n",(int)v.fadeout_volume); + printf("channel %i\n",(int)v.channel_volume); + printf("output %i\n",(int)v.output_volume); + printf("env %i\n",(int)tmp_volenv_value); + printf("cgb %i\n",(int)v.master_channel->channel_global_volume); +*/ + + + tmpvol=(uint64_t)v.fadeout_volume; /* max 1024 - 10 bits */ + tmpvol*=(uint64_t)v.channel_volume; /* * max 64 - 6 bits */ + tmpvol*=(uint64_t)v.output_volume; /* * max 256 - 8 bits */ + tmpvol*=(uint64_t)tmp_volenv_value; /* max 64 - 6 bits*/ + tmpvol*=(uint64_t)v.master_channel->channel_global_volume; + v.display_volume=tmpvol>>22; //volume used for display purposes , 0 -- 256 + + tmpvol*=(uint64_t)song->get_mixing_volume(); /* max 128 - 7 bits */ + tmpvol*=(uint64_t)control.global_volume; /* max 128 - 7 bits*/ + /* total 10+6+8+6+7+7=44 bits */ + + tmpvol>>=43; /* Move back to 8 bits range , 44-19+8=43*/ + + if (tmpvol>CP_VOL_MAX) + tmpvol=CP_VOL_MAX; + + //printf("volume check - fade %i, channel %i, output %i, env %i, mix %i, global %i -- final %i\n",v.fadeout_volume, v.channel_volume,v.output_volume,tmp_volenv_value, song->get_mixing_volume(),control.global_volume,tmpvol); + + v.total_volume=tmpvol; + + + if ((v.master_channel!=NULL) && song->is_channel_mute( v.master_channel_index ) && !v.master_channel->reserved) { + + mixer->set_voice_volume(i,0); + } else { + mixer->set_voice_volume(i,tmpvol); + if (v.fadeout_volume>0) control.voices_used++; + } + + + if (!song->is_stereo()) { + + mixer->set_voice_panning(i,PAN_CENTER); + + } else if (v.panning==PAN_SURROUND) { + + mixer->set_voice_panning(i,PAN_SURROUND); + } else if (song->has_instruments()) { + + int newpan,real_modifier; + + + real_modifier=(v.panning_envelope_ctrl.value*(PAN_CENTER-cp_intabs(v.panning-PAN_CENTER)))/32; + + newpan=v.panning+real_modifier; + + newpan=(newpan<PAN_LEFT)?PAN_LEFT:(newpan>PAN_RIGHT)?PAN_RIGHT:newpan; + //printf("panenv val: %i, finalpan val %i\n",v.panning_envelope_ctrl.value,newpan); + + mixer->set_voice_panning(i,newpan); + } else { + mixer->set_voice_panning(i,v.panning); + } + + + + /* VIBRATO */ + + if ( (v.period>0) && (v.sample_ptr->get_vibrato_depth()>0) ) { + + switch (v.sample_ptr->get_vibrato_type()) { + case CPSample::VIBRATO_SINE: + tmp_vibrato_value=auto_vibrato_table[v.auto_vibrato_pos&127]; + if (v.auto_vibrato_pos & 0x80) tmp_vibrato_value=-tmp_vibrato_value; + break; + case CPSample::VIBRATO_SQUARE: + tmp_vibrato_value=64; + if (v.auto_vibrato_pos & 0x80) tmp_vibrato_value=-tmp_vibrato_value; + break; + case CPSample::VIBRATO_SAW: + tmp_vibrato_value=63-(((v.auto_vibrato_pos+128)&255)>>1); + break; + default: + tmp_vibrato_value=(((v.auto_vibrato_pos+128)&255)>>1)-64; + break; + } + } else { + + tmp_vibrato_value=0; + } + + if ((v.auto_vibrato_sweep_pos>>8)<v.sample_ptr->get_vibrato_depth()) { + + v.auto_vibrato_sweep_pos+=v.sample_ptr->get_vibrato_speed(); //FIXME - speed? i think so + tmp_vibrato_depth=v.auto_vibrato_sweep_pos; + + } else { + + tmp_vibrato_depth=v.sample_ptr->get_vibrato_depth()<<8; + } + + tmp_vibrato_value=(tmp_vibrato_value*tmp_vibrato_depth)>>16; + if (song->has_linear_slides()) + tmp_vibrato_value>>=1; + v.period-=tmp_vibrato_value; + + + /* update vibrato position */ + v.auto_vibrato_pos=(v.auto_vibrato_pos+v.sample_ptr->get_vibrato_rate())&0xff; + + + /* process pitch envelope */ + tmp_mixer_period=v.period; + + if (v.pitch_envelope_ctrl.active) { + + long aux_pitch_diff; + int pe_value=v.pitch_envelope_ctrl.value; + + if (!v.instrument_ptr->is_pitch_use_as_filter()) { + + + if (((uint16_t)v.note<<1)+pe_value<=0) + pe_value=-(v.note<<1); + + int smp_c5=CPSampleManager::get_singleton()->get_c5_freq( v.sample_ptr->get_sample_data()); + + int base=get_period(((uint16_t)v.note<<1),smp_c5); + int env=get_period(((uint16_t)v.note<<1)+pe_value,smp_c5); + /* + int env_next=(pe_value<0)?get_period(((uint16_t)(v.note-1)<<1)+pe_value,smp_c5):get_period(((uint16_t)(v.note+1)<<1)+pe_value,smp_c5); + + env=env+(abs(v.pitch_envelope_ctrl.value)&((1<<CPEnvelope::FX_HEIGHT_BITS)-1))*(env_next-env)/(1<<CPEnvelope::FX_HEIGHT_BITS); + + printf("env %i\n",env); + */ + aux_pitch_diff=env-base; + + + if ( ((int)tmp_mixer_period-aux_pitch_diff)<0 ) aux_pitch_diff=0; + + tmp_mixer_period+=aux_pitch_diff; + + } else { + + filter_env=pe_value+32; //max 64 + //printf("pitch envelope at %i",filter_env); + + } + } + + if (v.fadeout_volume==0 || (v.note_end_flags & END_NOTE_KILL)) { /* check for a dead note (fadevol=0) */ + + mixer->stop_voice(i); + + } else { + + + int32_t freq=get_frequency(tmp_mixer_period); + int32_t tracker_c5=get_frequency(get_period(60<<1,CPSampleManager::get_singleton()->get_c5_freq( v.sample_ptr->get_sample_data()))); + + freq=(int32_t)((uint64_t)freq*(uint64_t)C5FREQ_MIXER/(uint64_t)tracker_c5); //numbers may become very high + mixer->set_voice_frequency(i,freq); + + /* if keyfade, start substracting fadeoutspeed from fadevol: */ + if ((song->has_instruments())&&(v.note_end_flags & END_NOTE_FADE)) { + + if (v.fadeout_volume>=(v.instrument_ptr->get_volume_fadeout())) { + + v.fadeout_volume-=(v.instrument_ptr->get_volume_fadeout()); + } else { + + v.fadeout_volume=0; + } + } + + /*FILTARSSSSSSSS*/ + + + + v.filter.envelope_cutoff=filter_env; + v.filter.process(); + + if ((v.filter.final_cutoff<0xFF) && (control.filters)) { + + //int final_cutoff; + //uint8_t final_reso; + + //v.filter.set_filter_parameters( &final_cutoff, &final_reso ); + + mixer->set_voice_filter(i,true,v.filter.final_cutoff,v.filter.it_reso); + } else { + + + mixer->set_voice_filter(i,false,0,0); + } + + /* RAIVERV */ + + mixer->set_voice_reverb_send(i,v.reverb_send); + + /* CHAURUZ */ + + mixer->set_voice_chorus_send(i,v.chorus_send); + + } + } + + + switch(song->get_reverb_mode()) { + + case CPSong::REVERB_MODE_ROOM: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_ROOM ); + } break; + case CPSong::REVERB_MODE_STUDIO_SMALL: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_STUDIO_SMALL ); + + } break; + case CPSong::REVERB_MODE_STUDIO_MEDIUM: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_STUDIO_MEDIUM ); + + } break; + case CPSong::REVERB_MODE_STUDIO_LARGE: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_STUDIO_LARGE ); + + } break; + case CPSong::REVERB_MODE_HALL: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_HALL ); + + } break; + case CPSong::REVERB_MODE_SPACE_ECHO: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_SPACE_ECHO ); + + } break; + case CPSong::REVERB_MODE_ECHO: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_ECHO ); + + } break; + case CPSong::REVERB_MODE_DELAY: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_DELAY ); + + } break; + case CPSong::REVERB_MODE_HALF_ECHO: { + + mixer->set_reverb_mode( CPMixer::REVERB_MODE_HALF_ECHO ); + + } break; + + } + + mixer->set_chorus_params(song->get_chorus_delay_ms(),song->get_chorus_separation_ms(),song->get_chorus_depth_ms10(),song->get_chorus_speed_hz10() ); + + +} + + + + + + + + +void CPPlayer::handle_tick() { + + int i; + + + if ( mixer==NULL ) return; + if ( song==NULL ) return; + + + /* update time counter (sngtime is in milliseconds (in fact 2^-10)) */ + + if (control.ticks_counter>=control.speed) { // time to process... ***THE ROW***! + + /* process pattern-delay. pf->patdly2 is the counter and pf->patdly is + the command memory. */ + + /* + if (control.pattern_delay_1) { + + control.pattern_delay_2=control.pattern_delay_1; + control.pattern_delay_1=0; + } + if (control.pattern_delay_2) { + patterndelay active + if (--control.pattern_delay_2) + // so turn back pf->patpos by 1 + if (pf->patpos) pf->patpos--; + } + */ + + if (control.play_mode!=PLAY_NOTHING) { + + control.ticks_counter=0; + + + if (control.position.force_next_order>=0) { + + control.position.current_order=control.position.force_next_order; + } + + control.position.force_next_order=-1; + + control.previous_position=control.position; // for those special cases... + control.position.forbid_jump=false; + + for (i=0;i<CPPattern::WIDTH;i++) { + + process_note(i,song->get_pattern(control.position.current_pattern)->get_note(i,control.position.current_row)); + } + + control.position.current_row++; + + if ( control.position.current_row>=song->get_pattern(control.position.current_pattern)->get_length() ) { + + if (control.play_mode==PLAY_SONG) { + + int next_order; + + next_order=get_song_next_order_idx(song,control.position.current_order); + + if (next_order!=-1) { + // Do we have a "next order?" + control.position.current_pattern=song->get_order(next_order); + if (next_order<=control.position.current_order) + control.reached_end=true; + control.position.current_order=next_order; + + } else { + // no, probably the user deleted the orderlist. + control.play_mode=PLAY_NOTHING; + reset(); + control.reached_end=true; + } + } + control.position.current_row=0; + } + + } + + + } + + + + pre_process_effects(); + process_NNAs(); + setup_voices(); + + /* now set up the actual hardware channel playback information */ + update_mixer(); + + control.ticks_counter++; +} diff --git a/modules/chibi/cp_player_data_filter.cpp b/modules/chibi/cp_player_data_filter.cpp new file mode 100644 index 000000000..e04ae126f --- /dev/null +++ b/modules/chibi/cp_player_data_filter.cpp @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* cp_player_data_filter.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + + +#include "cp_player_data.h" + +static float filter_cutoff[256] = { + 130, 132, 134, 136, 138, 140, 142, 144, + 146, 148, 151, 153, 155, 157, 160, 162, + 164, 167, 169, 172, 174, 177, 179, 182, + 184, 187, 190, 193, 195, 198, 201, 204, + 207, 210, 213, 216, 220, 223, 226, 229, + 233, 236, 239, 243, 246, 250, 254, 257, + 261, 265, 269, 273, 277, 281, 285, 289, + 293, 297, 302, 306, 311, 315, 320, 324, + 329, 334, 339, 344, 349, 354, 359, 364, + 369, 375, 380, 386, 391, 397, 403, 409, + 415, 421, 427, 433, 440, 446, 452, 459, + 466, 472, 479, 486, 493, 501, 508, 515, + 523, 530, 538, 546, 554, 562, 570, 578, + 587, 595, 604, 613, 622, 631, 640, 649, + 659, 668, 678, 688, 698, 708, 718, 729, + 739, 750, 761, 772, 783, 795, 806, 818, + 830, 842, 854, 867, 880, 892, 905, 918, + 932, 945, 959, 973, 987, 1002, 1016, 1031, + 1046, 1061, 1077, 1092, 1108, 1124, 1141, 1157, + 1174, 1191, 1209, 1226, 1244, 1262, 1280, 1299, + 1318, 1337, 1357, 1376, 1396, 1417, 1437, 1458, + 1479, 1501, 1523, 1545, 1567, 1590, 1613, 1637, + 1661, 1685, 1709, 1734, 1760, 1785, 1811, 1837, + 1864, 1891, 1919, 1947, 1975, 2004, 2033, 2062, + 2093, 2123, 2154, 2185, 2217, 2249, 2282, 2315, + 2349, 2383, 2418, 2453, 2489, 2525, 2561, 2599, + 2637, 2675, 2714, 2753, 2793, 2834, 2875, 2917, + 2959, 3003, 3046, 3091, 3135, 3181, 3227, 3274, + 3322, 3370, 3419, 3469, 3520, 3571, 3623, 3675, + 3729, 3783, 3838, 3894, 3951, 4008, 4066, 4125, + 4186, 4246, 4308, 4371, 4434, 4499, 4564, 4631, + 4698, 4766, 4836, 4906, 4978, 5050, 5123, 5198 +}; + + +void CPPlayer::Filter_Control::process() { + + + final_cutoff=it_cutoff; + if (envelope_cutoff>=0) { + + envelope_cutoff=envelope_cutoff*255/64; + final_cutoff=final_cutoff*envelope_cutoff/255; + if (final_cutoff>=0xFF) final_cutoff=0xFE; + + } + +} + +void CPPlayer::Filter_Control::set_filter_parameters(int *p_cutoff,uint8_t *p_reso) { + + + + *p_cutoff=filter_cutoff[final_cutoff]; + *p_reso=it_reso; +} diff --git a/modules/chibi/cp_player_data_nna.cpp b/modules/chibi/cp_player_data_nna.cpp new file mode 100644 index 000000000..3c50bfb01 --- /dev/null +++ b/modules/chibi/cp_player_data_nna.cpp @@ -0,0 +1,144 @@ +/*************************************************************************/ +/* cp_player_data_nna.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" + +void CPPlayer::process_NNAs() { + + int i; + + if (!song->has_instruments()) return; + + for (i=0;i<CPPattern::WIDTH;i++) { + + Channel_Control *aux_chn_ctrl = &control.channel[i]; + + if (aux_chn_ctrl->kick==KICK_NOTE) { + + bool k=false; + + if (aux_chn_ctrl->slave_voice!=NULL) { + + Voice_Control *aux_voc_ctrl; + + aux_voc_ctrl=aux_chn_ctrl->slave_voice; + + if (aux_chn_ctrl->instrument_index==aux_chn_ctrl->slave_voice->instrument_index) { //maybe carry + + aux_chn_ctrl->carry.pan=aux_chn_ctrl->slave_voice->panning_envelope_ctrl; + aux_chn_ctrl->carry.vol=aux_chn_ctrl->slave_voice->volume_envelope_ctrl; + aux_chn_ctrl->carry.pitch=aux_chn_ctrl->slave_voice->pitch_envelope_ctrl; + aux_chn_ctrl->carry.maybe=true; + } else + aux_chn_ctrl->carry.maybe=false; + + if (aux_voc_ctrl->NNA_type != CPInstrument::NNA_NOTE_CUT) { + /* Make sure the old MP_VOICE channel knows it has no + master now ! */ + + + + aux_chn_ctrl->slave_voice=NULL; + /* assume the channel is taken by NNA */ + aux_voc_ctrl->has_master_channel=false; + + switch (aux_voc_ctrl->NNA_type) { + case CPInstrument::NNA_NOTE_CONTINUE: { + + } break; + case CPInstrument::NNA_NOTE_OFF: { + + + aux_voc_ctrl->note_end_flags|=END_NOTE_OFF; + + if (!aux_voc_ctrl->volume_envelope_ctrl.active || aux_voc_ctrl->instrument_ptr->get_volume_envelope()->is_loop_enabled()) { + aux_voc_ctrl->note_end_flags|=END_NOTE_FADE; + } + } break; + case CPInstrument::NNA_NOTE_FADE: { + + aux_voc_ctrl->note_end_flags|=END_NOTE_FADE; + } break; + } + } + } + + if (aux_chn_ctrl->duplicate_check_type!=CPInstrument::DCT_DISABLED) { + int i; + + for (i=0;i<control.max_voices;i++) { + if (!mixer->is_voice_active(i)|| + (voice[i].master_channel!=aux_chn_ctrl) || + (aux_chn_ctrl->instrument_index!=voice[i].instrument_index)) + continue; + + Voice_Control *aux_voc_ctrl; + + aux_voc_ctrl=&voice[i]; + + k=false; + switch (aux_chn_ctrl->duplicate_check_type) { + case CPInstrument::DCT_NOTE: + if (aux_chn_ctrl->note==aux_voc_ctrl->note) + k=true; + break; + case CPInstrument::DCT_SAMPLE: + if (aux_chn_ctrl->sample_ptr==aux_voc_ctrl->sample_ptr) + k=true; + break; + case CPInstrument::DCT_INSTRUMENT: + k=true; + break; + } + if (k) { + switch (aux_chn_ctrl->duplicate_check_action) { + case CPInstrument::DCA_NOTE_CUT: { + aux_voc_ctrl->fadeout_volume=0; + } break; + case CPInstrument::DCA_NOTE_OFF: { + + aux_voc_ctrl->note_end_flags|=END_NOTE_OFF; + + if (!aux_voc_ctrl->volume_envelope_ctrl.active || aux_chn_ctrl->instrument_ptr->get_volume_envelope()->is_loop_enabled()) { + + aux_voc_ctrl->note_end_flags|=END_NOTE_FADE; + } + + } break; + case CPInstrument::DCA_NOTE_FADE: { + aux_voc_ctrl->note_end_flags|=END_NOTE_FADE; + } break; + } + } + } + + } + } /* if (aux_chn_ctrl->kick==KICK_NOTE) */ + } +} diff --git a/modules/chibi/cp_player_data_notes.cpp b/modules/chibi/cp_player_data_notes.cpp new file mode 100644 index 000000000..189403343 --- /dev/null +++ b/modules/chibi/cp_player_data_notes.cpp @@ -0,0 +1,345 @@ +/*************************************************************************/ +/* cp_player_data_notes.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" +#include "cp_sample_manager.h" + +#define RANDOM_MAX 2147483647 + +static inline int32_t cp_random_generate(int32_t *seed) { + int32_t k; + int32_t s = (int32_t)(*seed); + if (s == 0) + s = 0x12345987; + k = s / 127773; + s = 16807 * (s - k * 127773) - 2836 * k; + if (s < 0) + s += 2147483647; + (*seed) = (int32_t)s; + return (int32_t)(s & RANDOM_MAX); +} + + +void CPPlayer::process_new_note(int p_track,uint8_t p_note) { // if there's really a new note.... + + if (control.channel[p_track].real_note!=255) { + control.channel[p_track].old_note=control.channel[p_track].real_note; + + } + + control.channel[p_track].real_note=p_note; + + control.channel[p_track].kick=KICK_NOTE; + + control.channel[p_track].sample_start_index=-1; + control.channel[p_track].sliding=0; + control.channel[p_track].row_has_note=true; + control.channel[p_track].last_event_usecs=song_usecs; + + if (control.channel[p_track].panbrello_type) control.channel[p_track].panbrello_position=0; +} + +bool CPPlayer::process_new_instrument(int p_track,uint8_t p_instrument) { + + //bool different_instrument=false; + ERR_FAIL_INDEX_V(p_instrument,CPSong::MAX_INSTRUMENTS,false); + + if ( song->has_instruments() ) { + + + control.channel[p_track].instrument_ptr=song->get_instrument(p_instrument); + } else { + + control.channel[p_track].instrument_ptr=NULL; + } + + control.channel[p_track].retrig_counter=0; + control.channel[p_track].tremor_position=0; + control.channel[p_track].sample_offset_fine=0; + int old_instr_index=control.channel[p_track].instrument_index; + control.channel[p_track].instrument_index=p_instrument; + + return (old_instr_index!=p_instrument); + + +} + + + // returns if it was able to process +bool CPPlayer::process_note_and_instrument(int p_track,int p_note,int p_instrument) { + + bool aux_result; + aux_result=false; + CPSample *aux_sample=0; // current sample + int dest_sample_index; + bool new_instrument=false; + + control.channel[p_track].row_has_note=false; // wise man says.. "we dont have a note... until we really know we have a note". + control.channel[p_track].new_instrument=false; + + if ( (p_note<0) && (p_instrument<0) ) return aux_result; // nothing to do here + if ( (p_note==255) && (p_instrument==255) ) return aux_result; + + if ( (p_note>=0) && (p_note<120) ) { + + process_new_note(p_track,p_note); + + } else if (p_note==CPNote::CUT) { + + control.channel[p_track].aux_volume=0; + control.channel[p_track].note_end_flags|=END_NOTE_OFF; + control.channel[p_track].note_end_flags|=END_NOTE_KILL; + return aux_result; + + } else if ((p_note==CPNote::OFF) && (song->has_instruments())) { + + if (control.channel[p_track].instrument_ptr!=NULL) { + + control.channel[p_track].note_end_flags|=END_NOTE_OFF; + + if (!control.channel[p_track].instrument_ptr->get_volume_envelope()->is_enabled() || control.channel[p_track].instrument_ptr->get_volume_envelope()->is_loop_enabled()) { + + control.channel[p_track].note_end_flags|=END_NOTE_FADE; + } + } + + return aux_result; + } else return aux_result; // invalid note! + + + if ( (p_instrument>=0) && (p_instrument<CPSong::MAX_INSTRUMENTS)) { + new_instrument=process_new_instrument(p_track,p_instrument); + + if ( song->has_instruments() ) { + // If we're in instrument mode... + if ( control.channel[p_track].instrument_ptr->get_sample_number(control.channel[p_track].real_note) >= CPSong::MAX_SAMPLES) { + + control.channel[p_track].kick=KICK_NOTHING; + return aux_result; + + } else { + dest_sample_index=control.channel[p_track].instrument_ptr->get_sample_number(control.channel[p_track].real_note); + control.channel[p_track].note=control.channel[p_track].instrument_ptr->get_note_number(control.channel[p_track].real_note); + } + + } else { + // If we're in sample mode... + dest_sample_index=control.channel[p_track].instrument_index; + control.channel[p_track].note=control.channel[p_track].real_note; + } + + control.channel[p_track].sample_index=dest_sample_index; + aux_sample=song->get_sample(dest_sample_index); + + if (!CPSampleManager::get_singleton()->check( aux_sample->get_sample_data() )) { + /* INVALID SAMPLE */ + control.channel[p_track].kick=KICK_NOTHING; + return aux_result; + + } + + aux_sample=song->get_sample(dest_sample_index); + } else { + + + if (!control.channel[p_track].sample_ptr) + return aux_result; + + if (song->has_instruments()) { + + if (!control.channel[p_track].instrument_ptr) + return aux_result; + + control.channel[p_track].note=control.channel[p_track].instrument_ptr->get_note_number(control.channel[p_track].real_note); + + } else { + + control.channel[p_track].note=control.channel[p_track].real_note; + + } + + aux_sample=control.channel[p_track].sample_ptr; + + } + + + + if (p_instrument>=CPSong::MAX_INSTRUMENTS && control.channel[p_track].sample_ptr!=aux_sample) { + + control.channel[p_track].new_instrument=(control.channel[p_track].period>0); + } + + control.channel[p_track].sample_ptr=aux_sample; + + /* channel or instrument determined panning ? */ + + control.channel[p_track].panning=control.channel[p_track].channel_panning; + + /* set filter,if any ? */ + + + if (aux_sample->is_pan_enabled()) { + + control.channel[p_track].panning=(int)aux_sample->get_pan()*255/64; + + } else if ( song->has_instruments() && (control.channel[p_track].instrument_ptr->is_pan_default_enabled()) ) { + + control.channel[p_track].panning=(int)control.channel[p_track].instrument_ptr->get_pan_default_amount()*255/64; + } + + + if (song->has_instruments()) { + + + /* Pitch-Pan Separation */ + if ((control.channel[p_track].instrument_ptr->get_pan_pitch_separation()!=0) && (control.channel[p_track].channel_panning!=PAN_SURROUND)){ + + control.channel[p_track].panning+=((control.channel[p_track].real_note-control.channel[p_track].instrument_ptr->get_pan_pitch_center())*control.channel[p_track].instrument_ptr->get_pan_pitch_separation())/8; + + if (control.channel[p_track].panning<PAN_LEFT) control.channel[p_track].panning=PAN_LEFT; + if (control.channel[p_track].panning>PAN_RIGHT) control.channel[p_track].panning=PAN_RIGHT; + } + + /* Random Volume Variation */ + if (control.channel[p_track].instrument_ptr->get_volume_random_variation()>0) { + + control.channel[p_track].random_volume_variation=100-(cp_random_generate(&control.random_seed) % control.channel[p_track].instrument_ptr->get_volume_random_variation()); + + } else { + + control.channel[p_track].random_volume_variation=100; + } + + + /* Random Pan Variation */ + if ((control.channel[p_track].instrument_ptr->get_pan_random_variation()>0) && (control.channel[p_track].panning!=PAN_SURROUND)){ + + int aux_pan_modifier; + + aux_pan_modifier=(cp_random_generate(&control.random_seed) % (control.channel[p_track].instrument_ptr->get_pan_random_variation() << 2)); + if ((cp_random_generate(&control.random_seed) % 2)==1) aux_pan_modifier=0-aux_pan_modifier; /* it's 5am, let me sleep :) */ + + control.channel[p_track].panning+=aux_pan_modifier; + + if (control.channel[p_track].panning<PAN_LEFT) control.channel[p_track].panning=PAN_LEFT; + if (control.channel[p_track].panning>PAN_RIGHT) control.channel[p_track].panning=PAN_RIGHT; + + + } + + /*filter*/ + + if (control.channel[p_track].instrument_ptr->filter_use_default_cutoff()) { + + control.channel[p_track].filter.it_cutoff=control.channel[p_track].instrument_ptr->get_filter_default_cutoff()*2; + + } + + if (control.channel[p_track].instrument_ptr->filter_use_default_resonance()) { + + control.channel[p_track].filter.it_reso=control.channel[p_track].instrument_ptr->get_filter_default_resonance()*2; + + } + + /*envelopes*/ + + + control.channel[p_track].volume_envelope_on=control.channel[p_track].instrument_ptr->get_volume_envelope()->is_enabled(); + control.channel[p_track].panning_envelope_on=control.channel[p_track].instrument_ptr->get_pan_envelope()->is_enabled(); + control.channel[p_track].pitch_envelope_on=control.channel[p_track].instrument_ptr->get_pitch_filter_envelope()->is_enabled(); + control.channel[p_track].NNA_type=control.channel[p_track].instrument_ptr->get_NNA_type(); + control.channel[p_track].duplicate_check_type=control.channel[p_track].instrument_ptr->get_DC_type(); + control.channel[p_track].duplicate_check_action=control.channel[p_track].instrument_ptr->get_DC_action(); + + + } else { + + control.channel[p_track].NNA_type=CPInstrument::NNA_NOTE_CUT; + control.channel[p_track].duplicate_check_type=CPInstrument::DCT_DISABLED; + control.channel[p_track].duplicate_check_action=CPInstrument::DCA_NOTE_CUT; + } + + + if (p_instrument<CPSong::MAX_INSTRUMENTS) { // instrument change + + control.channel[p_track].volume=control.channel[p_track].aux_volume=aux_sample->get_default_volume(); + + } + + + control.channel[p_track].slide_to_period=control.channel[p_track].aux_period=get_period((uint16_t)(control.channel[p_track].note)<<1,CPSampleManager::get_singleton()->get_c5_freq( (aux_sample->get_sample_data()))); + + control.channel[p_track].note_end_flags=END_NOTE_NOTHING; /* clears flags */ + + return true; +} + +void CPPlayer::process_volume_column(int p_track,uint8_t p_volume) { + + control.channel[p_track].current_volume_command=CPNote::EMPTY; + control.channel[p_track].current_volume_parameter=CPNote::EMPTY; + + if (p_volume<65) { // VOLUME + + control.channel[p_track].aux_volume=p_volume; + } else if (p_volume<125) { // Volume Command + + + control.channel[p_track].current_volume_command=(p_volume-65) / 10; + control.channel[p_track].current_volume_parameter=(p_volume-65) % 10; + } else if (p_volume<193) { // PAN + + control.channel[p_track].channel_panning=(p_volume-128)*PAN_RIGHT/64; + control.channel[p_track].panning=control.channel[p_track].channel_panning; + + } else if (p_volume<213) { //More volume Commands + + control.channel[p_track].current_volume_command=((p_volume-193) / 10)+6; + control.channel[p_track].current_volume_parameter=(p_volume-193) % 10; + } +} + + +void CPPlayer::process_note(int p_track,CPNote p_note) { + + if ( p_note.note!=CPNote::SCRIPT ) { + + process_note_and_instrument(p_track,p_note.note,p_note.instrument); + process_volume_column(p_track,p_note.volume); + control.channel[p_track].current_command=p_note.command; + control.channel[p_track].current_parameter=p_note.parameter; + + } else { + + CPNote n = song->get_pattern( control.position.current_pattern )->get_transformed_script_note( p_track, control.position.current_row ); + process_note( p_track, n ); + + song->get_pattern( control.position.current_pattern )->scripted_clone( p_track, control.position.current_row ); + } +} diff --git a/modules/chibi/cp_player_data_utils.cpp b/modules/chibi/cp_player_data_utils.cpp new file mode 100644 index 000000000..b99dbfcb9 --- /dev/null +++ b/modules/chibi/cp_player_data_utils.cpp @@ -0,0 +1,140 @@ +/*************************************************************************/ +/* cp_player_data_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_player_data.h" +uint8_t CPPlayer::vibrato_table[32]={ + 0, 24, 49, 74, 97,120,141,161,180,197,212,224,235,244,250,253, + 255,253,250,244,235,224,212,197,180,161,141,120, 97, 74, 49, 24 +}; + +uint8_t CPPlayer::auto_vibrato_table[128]={ + 0, 1, 3, 4, 6, 7, 9,10,12,14,15,17,18,20,21,23, + 24,25,27,28,30,31,32,34,35,36,38,39,40,41,42,44, + 45,46,47,48,49,50,51,52,53,54,54,55,56,57,57,58, + 59,59,60,60,61,61,62,62,62,63,63,63,63,63,63,63, + 64,63,63,63,63,63,63,63,62,62,62,61,61,60,60,59, + 59,58,57,57,56,55,54,54,53,52,51,50,49,48,47,46, + 45,44,42,41,40,39,38,36,35,34,32,31,30,28,27,25, + 24,23,21,20,18,17,15,14,12,10, 9, 7, 6, 4, 3, 1 +}; + + +int8_t CPPlayer::panbrello_table[256]={ + 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23, + 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, + 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60, + 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, + 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26, + 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2, + 0,- 2,- 3,- 5,- 6,- 8,- 9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44, + -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59, + -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64, + -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60, + -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46, + -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26, + -24,-23,-22,-20,-19,-17,-16,-14,-12,-11,- 9,- 8,- 6,- 5,- 3,- 2 +}; + + + + + + +int32_t CPPlayer::get_period(uint16_t p_note,int32_t p_c5speed) { + + if (song->has_linear_slides()) { + + return CPTables::get_linear_period(p_note,0); + } else { + + + return CPTables::get_log_period(p_note>>1,p_c5speed >>1); + } +} + + +int32_t CPPlayer::get_frequency(int32_t period) { + + if (song->has_linear_slides()) { + + return CPTables::get_linear_frequency(period); + } else { + + return CPTables::get_old_frequency(period); + } +} + +int CPPlayer::find_empty_voice() { + + int i; + int min_priority,min_priority_chan=0,priority; + + for (i=0;i<control.max_voices;i++) { + + if ( ((voice[i].kick==KICK_NOTHING)||(voice[i].kick==KICK_ENVELOPE))&&!mixer->is_voice_active(i) ) { + + return i; + + } + } + + // todo more + + for (i=0;i<control.max_voices;i++) { + /* allow us to take over a nonexisting sample */ + /* + if ((voice[i].s==NULL) + return k; + */ + + if ((voice[i].kick==KICK_NOTHING)||(voice[i].kick==KICK_ENVELOPE)) { + + priority=voice[i].total_volume<<((CPSampleManager::get_singleton()->get_loop_type( voice[i].sample_ptr->get_sample_data())!=CP_LOOP_NONE)?1:0); + + if ((voice[i].has_master_channel)&&(&voice[i]==voice[i].master_channel->slave_voice)) { + + priority<<=2; + + } + + if ((i==0) || (priority<min_priority)) { + min_priority=priority; + min_priority_chan=i; + } + } + } + + if (min_priority>8000*7) return -1; /* what the fuck is this? */ + + return min_priority_chan; +} + diff --git a/modules/chibi/cp_sample.cpp b/modules/chibi/cp_sample.cpp new file mode 100644 index 000000000..bea883554 --- /dev/null +++ b/modules/chibi/cp_sample.cpp @@ -0,0 +1,203 @@ +/*************************************************************************/ +/* cp_sample.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_sample.h" + +const char * CPSample::get_name() const { + + return name; +} +void CPSample::set_name(const char *p_name) { + + if (p_name==NULL) { + name[0]=0; + return; + } + + + bool done=false; + for (int i=0;i<NAME_MAX_LEN;i++) { + + + name[i]=done?0:p_name[i]; + if (!done && p_name[i]==0) + done=true; + } + + name[NAME_MAX_LEN-1]=0; /* just in case */ + +} + +void CPSample::set_default_volume(uint8_t p_vol) { + + default_volume=p_vol; +} +uint8_t CPSample::get_default_volume() const{ + + return default_volume; +} + +void CPSample::set_global_volume(uint8_t p_vol) { + + global_volume=p_vol; +} +uint8_t CPSample::get_global_volume() const{ + + return global_volume; +} + +void CPSample::set_pan_enabled(bool p_vol) { + + pan_enabled=p_vol; +} +bool CPSample::is_pan_enabled() const{ + + return pan_enabled; +} + +void CPSample::set_pan(uint8_t p_pan) { + + pan=p_pan; + +} +uint8_t CPSample::get_pan() const{ + + return pan; +} + + +void CPSample::set_vibrato_type(VibratoType p_vibrato_type) { + + vibrato_type=p_vibrato_type; +} +CPSample::VibratoType CPSample::get_vibrato_type() const{ + + return vibrato_type; +} + +void CPSample::set_vibrato_speed(uint8_t p_vibrato_speed) { + + vibrato_speed=p_vibrato_speed; +} +uint8_t CPSample::get_vibrato_speed() const { + + return vibrato_speed; +} + +void CPSample::set_vibrato_depth(uint8_t p_vibrato_depth) { + + vibrato_depth=p_vibrato_depth; +} +uint8_t CPSample::get_vibrato_depth() const{ + + return vibrato_depth; +} + +void CPSample::set_vibrato_rate(uint8_t p_vibrato_rate) { + + vibrato_rate=p_vibrato_rate; +} +uint8_t CPSample::get_vibrato_rate() const{ + + return vibrato_rate; +} + +void CPSample::set_sample_data(CPSample_ID p_ID) { + + id=p_ID; +} +CPSample_ID CPSample::get_sample_data() const{ + + return id; +} + +void CPSample::operator=(const CPSample &p_sample) { + + copy_from(p_sample); +} +void CPSample::copy_from(const CPSample &p_sample) { + + reset(); + set_name(p_sample.get_name()); + + default_volume=p_sample.default_volume; + global_volume=p_sample.global_volume; + + pan_enabled=p_sample.pan_enabled; + pan=p_sample.pan; + + vibrato_type=p_sample.vibrato_type; + vibrato_speed=p_sample.vibrato_speed; + vibrato_depth=p_sample.vibrato_depth; + vibrato_rate=p_sample.vibrato_rate; + + if (CPSampleManager::get_singleton() && !p_sample.id.is_null()) + CPSampleManager::get_singleton()->copy_to( p_sample.id, id ); +} + + + + + +void CPSample::reset() { + + + name[0]=0; + + default_volume=64; + global_volume=64; + + pan_enabled=false; + pan=32; + + vibrato_type=VIBRATO_SINE; + vibrato_speed=0; + vibrato_depth=0; + vibrato_rate=0; + + if (!id.is_null() && CPSampleManager::get_singleton()) + CPSampleManager::get_singleton()->destroy( id ); + + id=CPSample_ID(); + +} + +CPSample::CPSample(const CPSample&p_from) { + + reset(); + copy_from(p_from); +} +CPSample::CPSample() { + + reset(); +} + +CPSample::~CPSample() { + + reset(); +} diff --git a/modules/chibi/cp_sample.h b/modules/chibi/cp_sample.h new file mode 100644 index 000000000..c02b220c8 --- /dev/null +++ b/modules/chibi/cp_sample.h @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* cp_sample.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CPSAMPLE_H +#define CPSAMPLE_H + + +#include "cp_config.h" +#include "cp_sample_manager.h" +class CPSample { + +public: + enum VibratoType { + VIBRATO_SINE, + VIBRATO_SAW, + VIBRATO_SQUARE, + VIBRATO_RANDOM + + }; + +private: + + enum { NAME_MAX_LEN=26 }; + + char name[NAME_MAX_LEN]; + + uint8_t default_volume; /* 0.. 64 */ + uint8_t global_volume; /* 0.. 64 */ + + bool pan_enabled; + uint8_t pan; /* 0.. 64 */ + + VibratoType vibrato_type; + uint8_t vibrato_speed; /* 0.. 64 */ + uint8_t vibrato_depth; /* 0.. 64 */ + uint8_t vibrato_rate; /* 0.. 64 */ + + CPSample_ID id; + + void copy_from(const CPSample &p_sample); +public: + + + void operator=(const CPSample &p_sample); + + const char * get_name() const; + void set_name(const char *p_name); + + void set_default_volume(uint8_t p_vol); + uint8_t get_default_volume() const; + + void set_global_volume(uint8_t p_vol); + uint8_t get_global_volume() const; + + void set_pan_enabled(bool p_vol); + bool is_pan_enabled() const; + + void set_pan(uint8_t p_pan); + uint8_t get_pan() const; + + void set_vibrato_type(VibratoType p_vibrato_type); + VibratoType get_vibrato_type() const; + + void set_vibrato_speed(uint8_t p_vibrato_speed) ; + uint8_t get_vibrato_speed() const; + + void set_vibrato_depth(uint8_t p_vibrato_depth); + uint8_t get_vibrato_depth() const; + + void set_vibrato_rate(uint8_t p_vibrato_rate); + uint8_t get_vibrato_rate() const; + + void set_sample_data(CPSample_ID); + CPSample_ID get_sample_data() const; + + void reset(); + + CPSample(const CPSample&p_from); + CPSample(); + ~CPSample(); + +}; + + + + +#endif diff --git a/modules/chibi/cp_sample_defs.h b/modules/chibi/cp_sample_defs.h new file mode 100644 index 000000000..5ae57aed8 --- /dev/null +++ b/modules/chibi/cp_sample_defs.h @@ -0,0 +1,97 @@ +/*************************************************************************/ +/* cp_sample_defs.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_SAMPLE_DEFS_H +#define CP_SAMPLE_DEFS_H + +#include "cp_config.h" + +enum CPSample_Loop_Type { + + CP_LOOP_NONE, + CP_LOOP_FORWARD, + CP_LOOP_BIDI +}; + +//#define INVALID_SAMPLE_ID -1 + +#define CP_MIXING_FRAC_BITS_MACRO 13 +#define CP_MIXING_FRAC_BITS_TEXT "13" +// 1<<9 - 1 +#define CP_MIXING_FRAC_BITS_MASK_TEXT "8191" + +enum CPMixConstants { + CP_MIXING_FRAC_BITS=CP_MIXING_FRAC_BITS_MACRO, + CP_MIXING_FRAC_LENGTH=(1<<CP_MIXING_FRAC_BITS), + CP_MIXING_FRAC_MASK=CP_MIXING_FRAC_LENGTH-1, + CP_MIXING_VOL_FRAC_BITS=8, + CP_MIXING_FREQ_FRAC_BITS=8 +}; + +enum CPFilterConstants { + CP_FILTER_SHIFT=16, + CP_FILTER_LENGTH=(1<<CP_FILTER_SHIFT) +}; + + +enum CPInterpolationType { + CP_INTERPOLATION_RAW, + CP_INTERPOLATION_LINEAR, + CP_INTERPOLATION_CUBIC +}; + +enum CPPanConstants { + + CP_PAN_BITS=8, // 0 .. 256 + CP_PAN_LEFT=0, + CP_PAN_RIGHT=((1<<CP_PAN_BITS)-1), // 255 + CP_PAN_CENTER=CP_PAN_RIGHT/2, // 128 + CP_PAN_SURROUND=512 +}; + +enum CPMixerVolConstants { + CP_VOL_MAX=512, + CP_VOL_RAMP_BITS=9, + CP_VOL_SHIFT=2 + + +}; + +enum CPStereoCannels { + CP_CHAN_LEFT, + CP_CHAN_RIGHT +}; + +#define CP_FIRST_SAMPLE_DECLICK_THRESHOLD 1000 +#define CP_FIRST_SAMPLE_RAMP_LEN 32 + +typedef signed char CPFrame8; +typedef signed short CPFrame16; + + +#endif diff --git a/modules/chibi/cp_sample_manager.cpp b/modules/chibi/cp_sample_manager.cpp new file mode 100644 index 000000000..2ad0a720b --- /dev/null +++ b/modules/chibi/cp_sample_manager.cpp @@ -0,0 +1,78 @@ +/*************************************************************************/ +/* cp_sample_manager.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_sample_manager.h" + + +CPSampleManager * CPSampleManager::singleton=NULL; + + +void CPSampleManager::copy_to(CPSample_ID p_from,CPSample_ID &p_to) { + + ERR_FAIL_COND(!check( p_from )); + + + if (p_to.is_null()) { + + p_to=create( is_16bits( p_from), is_stereo( p_from), get_size(p_from)); + } else { + + recreate( p_to, is_16bits( p_from), is_stereo( p_from), get_size(p_from)); + + } + + int len=get_size( p_from ); + int ch=is_stereo( p_from ) ? 2 : 1; + + for (int c=0;c<ch;c++) { + + for (int i=0;i<len;i++) { + + int16_t s=get_data( p_from, i, c ); + set_data( p_to, i, s, c ); + } + } + + set_loop_type( p_to, get_loop_type( p_from ) ); + set_loop_begin( p_to, get_loop_begin( p_from ) ); + set_loop_end( p_to, get_loop_end( p_from ) ); + set_c5_freq( p_to, get_c5_freq( p_from ) ); + + + +} + +CPSampleManager::CPSampleManager() { + + singleton=this; +} + +CPSampleManager *CPSampleManager::get_singleton() { + + return singleton; +} diff --git a/modules/chibi/cp_sample_manager.h b/modules/chibi/cp_sample_manager.h new file mode 100644 index 000000000..b6d47a301 --- /dev/null +++ b/modules/chibi/cp_sample_manager.h @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* cp_sample_manager.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CP_SAMPLE_MANAGER_H +#define CP_SAMPLE_MANAGER_H + +#include "cp_config.h" +#include "cp_sample_defs.h" + + + +/** +@author Juan Linietsky +*/ + + +/* abstract base CPSample_ID class */ + +struct CPSample_ID { + + void *_private; + + bool operator==(const CPSample_ID&p_other) const { return _private==p_other._private; } + bool operator!=(const CPSample_ID&p_other) const { return _private!=p_other._private; } + bool is_null() const { return _private==0; } + CPSample_ID(void *p_private=0) { _private=p_private; }; +}; + + +class CPSampleManager { + + static CPSampleManager * singleton; + +public: + + /* get the singleton instance */ + static CPSampleManager *get_singleton(); + + virtual void copy_to(CPSample_ID p_from,CPSample_ID &p_to); ///< if p_to is null, it gets created + + virtual CPSample_ID create(bool p_16bits,bool p_stereo,int32_t p_len)=0; + virtual void recreate(CPSample_ID p_id,bool p_16bits,bool p_stereo,int32_t p_len)=0; + virtual void destroy(CPSample_ID p_id)=0; + virtual bool check(CPSample_ID p_id)=0; // return false if invalid + + virtual void set_c5_freq(CPSample_ID p_id,int32_t p_freq)=0; + virtual void set_loop_begin(CPSample_ID p_id,int32_t p_begin)=0; + virtual void set_loop_end(CPSample_ID p_id,int32_t p_end)=0; + virtual void set_loop_type(CPSample_ID p_id,CPSample_Loop_Type p_type)=0; + virtual void set_chunk(CPSample_ID p_id,int32_t p_index,void *p_data,int p_data_len)=0; + + + virtual int32_t get_loop_begin(CPSample_ID p_id)=0; + virtual int32_t get_loop_end(CPSample_ID p_id)=0; + virtual CPSample_Loop_Type get_loop_type(CPSample_ID p_id)=0; + virtual int32_t get_c5_freq(CPSample_ID p_id)=0; + virtual int32_t get_size(CPSample_ID p_id)=0; + virtual bool is_16bits(CPSample_ID p_id)=0; + virtual bool is_stereo(CPSample_ID p_id)=0; + virtual bool lock_data(CPSample_ID p_id)=0; + virtual void *get_data(CPSample_ID p_id)=0; /* WARNING: Not all sample managers +may be able to implement this, it depends on the mixer in use! */ + virtual int16_t get_data(CPSample_ID p_id, int p_sample, int p_channel=0)=0; /// Does not need locking + virtual void set_data(CPSample_ID p_id, int p_sample, int16_t p_data,int p_channel=0)=0; /// Does not need locking + virtual void unlock_data(CPSample_ID p_id)=0; + + virtual void get_chunk(CPSample_ID p_id,int32_t p_index,void *p_data,int p_data_len)=0; + + CPSampleManager(); + virtual ~CPSampleManager(){} + +}; + +#endif diff --git a/modules/chibi/cp_song.cpp b/modules/chibi/cp_song.cpp new file mode 100644 index 000000000..197e44f69 --- /dev/null +++ b/modules/chibi/cp_song.cpp @@ -0,0 +1,957 @@ +/*************************************************************************/ +/* cp_song.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "cp_song.h" + +void CPSong::set_name(const char *p_name) { + + if (p_name==NULL) { + variables.name[0]=0; + return; + } + + + bool done=false; + for (int i=0;i<MAX_SONG_NAME;i++) { + + + variables.name[i]=done?0:p_name[i]; + if (!done && p_name[i]==0) + done=true; + } + + variables.name[MAX_SONG_NAME-1]=0; /* just in case */ +} + +const char * CPSong::get_name() { + + return variables.name; + +} + +void CPSong::set_message(const char *p_message) { + + if (p_message==NULL) { + variables.message[0]=0; + return; + } + + bool done=false; + for (int i=0;i<MAX_MESSAGE_LEN;i++) { + + + variables.message[i]=done?0:p_message[i]; + if (!done && p_message[i]==0) + done=true; + } + + variables.message[MAX_MESSAGE_LEN-1]=0; /* just in case */ +} + +const char * CPSong::get_message() { + + return variables.message; + +} + +void CPSong::set_row_highlight_minor(int p_hl_minor) { + + variables.row_highlight_minor=p_hl_minor; +} +int CPSong::get_row_highlight_minor() { + + return variables.row_highlight_minor; +} + +void CPSong::set_row_highlight_major(int p_hl_major) { + + variables.row_highlight_major=p_hl_major; + + +} /* 0 .. 256 */ +int CPSong::get_row_highlight_major() { + + return variables.row_highlight_major; + + +} /* 0 .. 256 */ + +void CPSong::set_mixing_volume(int p_mix_volume) { + + + variables.mixing_volume=p_mix_volume; +} /* 0 .. 128 */ +int CPSong::get_mixing_volume() { + + return variables.mixing_volume; + +} /* 0 .. 128 */ + +void CPSong::set_global_volume(int p_global_volume) { + + + initial_variables.global_volume=p_global_volume; + +} /* 0 .. 128 */ +int CPSong::get_global_volume() { + + return initial_variables.global_volume; + +} /* 0 .. 128 */ + +void CPSong::set_stereo_separation(int p_separation) { + + variables.stereo_separation=p_separation; + +} /* 0 .. 128 */ +int CPSong::get_stereo_separation() { + + return variables.stereo_separation; +} /* 0 .. 128 */ + +void CPSong::set_stereo(bool p_stereo) { + + variables.use_stereo=p_stereo; + +} +bool CPSong::is_stereo() { + + return variables.use_stereo; +} + +void CPSong::set_instruments(bool p_instruments) { + + variables.use_instruments=p_instruments; + + +} +bool CPSong::has_instruments() { + + + return variables.use_instruments; + +} + +void CPSong::set_linear_slides(bool p_linear_slides) { + + variables.use_linear_slides=p_linear_slides; + + +} +bool CPSong::has_linear_slides() { + + return variables.use_linear_slides; + + +} + +void CPSong::set_old_effects(bool p_old_effects) { + + variables.old_effects=p_old_effects; + + +} +bool CPSong::has_old_effects() { + + return variables.old_effects; +} + +void CPSong::set_compatible_gxx(bool p_compatible_gxx) { + + + variables.compatible_gxx=p_compatible_gxx; +} +bool CPSong::has_compatible_gxx() { + + return variables.compatible_gxx; + +} + +void CPSong::set_speed(int p_speed) { + + CP_ERR_COND(p_speed<MIN_SPEED); + CP_ERR_COND(p_speed>MAX_SPEED); + + initial_variables.speed=p_speed; + +} /* 1 .. 255 */ +int CPSong::get_speed() { + + return initial_variables.speed; + +} /* 1 .. 255 */ + +void CPSong::set_tempo(int p_tempo) { + + CP_ERR_COND( p_tempo<MIN_TEMPO ); + CP_ERR_COND( p_tempo>MAX_TEMPO ); + + initial_variables.tempo=p_tempo; + +} /* MIN_TEMPO .. MAX_TEMPO */ +int CPSong::get_tempo() { + + return initial_variables.tempo; + + +} /* MIN_TEMPO .. MAX_TEMPO */ + +void CPSong::set_channel_pan(int p_channel,int p_pan) { + + CP_FAIL_INDEX(p_channel,CPPattern::WIDTH); + CP_FAIL_INDEX(p_pan,CHANNEL_MAX_PAN+1); + + initial_variables.channel[p_channel].pan=p_pan; + +} /* 0 .. CHANNEL_MAX_PAN */ +int CPSong::get_channel_pan(int p_channel) { + + CP_FAIL_INDEX_V(p_channel,CPPattern::WIDTH,-1); + + return initial_variables.channel[p_channel].pan; +} + +void CPSong::set_channel_volume(int p_channel,int p_volume) { + + CP_FAIL_INDEX(p_channel,CPPattern::WIDTH); + CP_FAIL_INDEX(p_volume,CHANNEL_MAX_VOLUME+1); + + + initial_variables.channel[p_channel].volume=p_volume; + + +} /* 0 .. CHANNEL_MAX_VOLUME */ + + +int CPSong::get_channel_volume(int p_channel) { + + CP_FAIL_INDEX_V(p_channel,CPPattern::WIDTH,-1); + + return initial_variables.channel[p_channel].volume; + +} + +void CPSong::set_channel_chorus(int p_channel,int p_chorus) { + + CP_FAIL_INDEX(p_channel,CPPattern::WIDTH); + CP_FAIL_INDEX(p_chorus,CHANNEL_MAX_CHORUS+1); + + + initial_variables.channel[p_channel].chorus=p_chorus; + + +} /* 0 .. CHANNEL_MAX_CHORUS */ + + +int CPSong::get_channel_chorus(int p_channel) { + + CP_FAIL_INDEX_V(p_channel,CPPattern::WIDTH,-1); + + return initial_variables.channel[p_channel].chorus; + +} + +void CPSong::set_channel_reverb(int p_channel,int p_reverb) { + + CP_FAIL_INDEX(p_channel,CPPattern::WIDTH); + CP_FAIL_INDEX(p_reverb,CHANNEL_MAX_REVERB+1); + + + initial_variables.channel[p_channel].reverb=p_reverb; + + +} /* 0 .. CHANNEL_MAX_CHORUS */ + + +int CPSong::get_channel_reverb(int p_channel) { + + CP_FAIL_INDEX_V(p_channel,CPPattern::WIDTH,-1); + + return initial_variables.channel[p_channel].reverb; + +} + +void CPSong::set_channel_surround(int p_channel,bool p_surround) { + + CP_FAIL_INDEX(p_channel,CPPattern::WIDTH); + initial_variables.channel[p_channel].surround=p_surround; + +} +bool CPSong::is_channel_surround(int p_channel) { + + CP_FAIL_INDEX_V(p_channel,CPPattern::WIDTH,false); + + return initial_variables.channel[p_channel].surround; + + +} + +void CPSong::set_channel_mute(int p_channel,bool p_mute) { + + CP_FAIL_INDEX(p_channel,CPPattern::WIDTH); + + initial_variables.channel[p_channel].mute=p_mute; + +} +bool CPSong::is_channel_mute(int p_channel) { + + CP_FAIL_INDEX_V(p_channel,CPPattern::WIDTH,false); + + return initial_variables.channel[p_channel].mute; + +} + +/* arrays of stuff */ + +CPPattern* CPSong::get_pattern(int p_pattern) { + + CP_FAIL_INDEX_V(p_pattern,MAX_PATTERNS, NULL); + + return &pattern[p_pattern]; + +} +CPSample* CPSong::get_sample(int p_sample) { + + CP_FAIL_INDEX_V(p_sample,MAX_SAMPLES,NULL); + + return &sample[p_sample]; + + +} +CPInstrument* CPSong::get_instrument(int p_instrument) { + + + CP_FAIL_INDEX_V(p_instrument,MAX_INSTRUMENTS,NULL); + + return &instrument[p_instrument]; + +} + +int CPSong::get_order(int p_order) { + + CP_FAIL_INDEX_V(p_order,MAX_ORDERS,CP_ORDER_NONE); + + + return order[p_order]; + +} +void CPSong::set_order(int p_order,int p_pattern) { + + CP_FAIL_INDEX(p_order,MAX_ORDERS); + + order[p_order]=p_pattern; + +} + + +void CPSong::clear_instrument_with_samples(int p_instrument) { + + CPInstrument *ins = get_instrument( p_instrument ); + if (!ins) + return; + + for (int i=0;i<CPNote::NOTES;i++) { + + CPSample *s=get_sample( ins->get_sample_number( i ) ); + + if (!s) + continue; + + if (s->get_sample_data().is_null()) + continue; + + s->reset(); + } + ins->reset(); +} + +void CPSong::make_instrument_from_sample(int p_sample) { + + if (!has_instruments()) + return; + CP_ERR_COND(!get_sample( p_sample )); + + for (int i=0;i<MAX_INSTRUMENTS;i++) { + + + CPInstrument *ins=get_instrument(i); + + bool empty_slot=true; + for (int n=0;n<CPNote::NOTES;n++) { + + if (ins->get_sample_number(n)<MAX_SAMPLES) { + + empty_slot=false; + break; + } + } + + if (!empty_slot) + continue; + + for (int n=0;n<CPNote::NOTES;n++) { + + ins->set_sample_number(n,p_sample); + ins->set_note_number(n,n); + } + + ins->set_name( get_sample( p_sample )->get_name() ); + break; + } + +} + +void CPSong::make_instruments_from_samples() { + + for (int i=0;i<MAX_SAMPLES;i++) { + + CPInstrument *ins=get_instrument( i ); + + if (!ins) + continue; + + ins->reset(); + + CPSample *s=get_sample( i ); + + if (!s) + continue; + + ins->set_name( s->get_name() ); + + if (s->get_sample_data().is_null()) + continue; + + + + + for(int j=0;j<CPNote::NOTES;j++) + ins->set_sample_number( j, i ); + + + + } +} + +void CPSong::reset(bool p_clear_patterns,bool p_clear_samples,bool p_clear_instruments,bool p_clear_variables) { + + if (p_clear_variables) { + variables.name[0]=0; + variables.message[0]=0; + variables.row_highlight_major=16; + variables.row_highlight_minor=4; + variables.mixing_volume=48; + variables.old_effects=false; + if (p_clear_instruments) //should not be cleared, if not clearing instruments!! + variables.use_instruments=false; + variables.stereo_separation=128; + variables.use_linear_slides=true; + variables.use_stereo=true; + + initial_variables.global_volume=128; + initial_variables.speed=6; + initial_variables.tempo=125; + + for (int i=0;i<CPPattern::WIDTH;i++) { + + initial_variables.channel[i].pan=32; + initial_variables.channel[i].volume=CHANNEL_MAX_VOLUME; + initial_variables.channel[i].mute=false; + initial_variables.channel[i].surround=false; + initial_variables.channel[i].chorus=0; + initial_variables.channel[i].reverb=0; + + } + + effects.chorus.delay_ms=6; + effects.chorus.separation_ms=3; + effects.chorus.depth_ms10=6, + effects.chorus.speed_hz10=5; + effects.reverb_mode=REVERB_MODE_ROOM; + } + + if (p_clear_samples) { + for (int i=0;i<MAX_SAMPLES;i++) + get_sample(i)->reset(); + } + + if (p_clear_instruments) { + for (int i=0;i<MAX_INSTRUMENTS;i++) + get_instrument(i)->reset(); + } + + if (p_clear_patterns) { + for (int i=0;i<MAX_PATTERNS;i++) + get_pattern(i)->clear(); + + for (int i=0;i<MAX_ORDERS;i++) + set_order( i, CP_ORDER_NONE ); + } + + +} + + +CPSong::ReverbMode CPSong::get_reverb_mode() { + + return effects.reverb_mode; +} +void CPSong::set_reverb_mode(ReverbMode p_mode) { + + effects.reverb_mode=p_mode; +} + +void CPSong::set_chorus_delay_ms(int p_amount) { + + effects.chorus.delay_ms=p_amount; +} +void CPSong::set_chorus_separation_ms(int p_amount) { + + effects.chorus.separation_ms=p_amount; + +} +void CPSong::set_chorus_depth_ms10(int p_amount) { + + effects.chorus.depth_ms10=p_amount; + +} +void CPSong::set_chorus_speed_hz10(int p_amount) { + + effects.chorus.speed_hz10=p_amount; + +} + +int CPSong::get_chorus_delay_ms() { + + return effects.chorus.delay_ms; + +} +int CPSong::get_chorus_separation_ms() { + + return effects.chorus.separation_ms; +} +int CPSong::get_chorus_depth_ms10() { + + return effects.chorus.depth_ms10; + +} +int CPSong::get_chorus_speed_hz10() { + + return effects.chorus.speed_hz10; + +} + +void CPSong::cleanup_unused_patterns() { + + for (int i=0;i<MAX_PATTERNS;i++) { + + bool used=false; + if (get_pattern(i)->is_empty()) + continue; + + for (int j=0;j<MAX_ORDERS;j++) { + + if (get_order(j)==i) { + used=true; + + } + } + + if (!used) + get_pattern(i)->clear(); + } + +} +void CPSong::cleanup_unused_instruments(){ + + if (!has_instruments()) + return; + + bool instr_found[MAX_INSTRUMENTS]; + for (int i=0;i<MAX_INSTRUMENTS;i++) + instr_found[i]=false; + + for (int i=0;i<MAX_PATTERNS;i++) { + + if (get_pattern(i)->is_empty()) + continue; + + for (int row=0;row<get_pattern(i)->get_length();row++) { + + + for (int col=0;col<CPPattern::WIDTH;col++) { + + CPNote n; + n=get_pattern(i)->get_note( col,row ); + + if (n.instrument<MAX_INSTRUMENTS) + instr_found[n.instrument]=true; + + } + + } + + } + + for (int i=0;i<MAX_INSTRUMENTS;i++) + if (!instr_found[i]) + get_instrument(i)->reset(); + + +} +void CPSong::cleanup_unused_samples(){ + + if (!has_instruments()) + return; + + bool sample_found[MAX_SAMPLES]; + for (int i=0;i<MAX_INSTRUMENTS;i++) + sample_found[i]=false; + + for (int i=0;i<MAX_PATTERNS;i++) { + + if (get_pattern(i)->is_empty()) + continue; + + + for (int row=0;row<get_pattern(i)->get_length();row++) { + + + for (int col=0;col<CPPattern::WIDTH;col++) { + + CPNote n; + n=get_pattern(i)->get_note( col,row ); + + if (n.instrument>=MAX_SAMPLES) + continue; + + if (has_instruments()) { + + for (int nt=0;nt<CPNote::NOTES;nt++) { + + int smp=get_instrument(n.instrument)->get_sample_number(nt); + if (smp<MAX_SAMPLES) + sample_found[smp]=true; + } + + } else { + if (n.instrument<MAX_SAMPLES) + sample_found[n.instrument]=true; + } + + } + + } + + } + + for (int i=0;i<MAX_SAMPLES;i++) + if (!sample_found[i]) + get_sample(i)->reset(); + +} +void CPSong::cleanup_unused_orders(){ + + bool finito=false; + for (int j=0;j<MAX_ORDERS;j++) { + + + if (get_order(j)==CP_ORDER_NONE) + finito=true; + if (finito) + set_order(j,CP_ORDER_NONE); + + } + +} + +void CPSong::clear_all_default_pan() { + + for (int i=0;i<MAX_INSTRUMENTS;i++) + get_instrument(i)->set_pan_default_enabled( false ); //die! + + for (int i=0;i<MAX_SAMPLES;i++) + get_sample(i)->set_pan_enabled( false ); //die! + +} + + +void CPSong::clear_all_default_vol(){ + + for (int i=0;i<MAX_SAMPLES;i++) + get_sample(i)->set_default_volume( 64 ); //die! + for (int i=0;i<MAX_INSTRUMENTS;i++) + get_instrument(i)->set_volume_global_amount( CPInstrument::MAX_VOLUME ); + +} + + +int CPSong::get_order_in_use_count() { + + + int order_count = 0; + + for (int i=(MAX_ORDERS-1);i>=0;i--) { + + + if (get_order(i)!=CP_ORDER_NONE) { + order_count=i+1; + break; + } + } + + return order_count; +} +int CPSong::get_pattern_in_use_count() { + + + int pattern_count=0; + + for (int i=(CPSong::MAX_PATTERNS-1);i>=0;i--) { + + + if (!get_pattern(i)->is_empty()) { + pattern_count=i+1; + break; + } + } + + return pattern_count; +} + +int CPSong::get_instrument_in_use_count() { + + int instrument_count=0; + + for (int i=(CPSong::MAX_INSTRUMENTS-1);i>=0;i--) { + + CPInstrument *ins = get_instrument(i); + bool in_use=false; + + for (int s = 0 ; s < CPNote::NOTES ; s++ ) { + + int smp_idx = ins->get_sample_number(s); + if (smp_idx<0 || smp_idx>=CPSong::MAX_SAMPLES) + continue; + + if (!get_sample(smp_idx)->get_sample_data().is_null()) { + in_use=true; + break; + } + + } + + if (in_use) { + instrument_count=i+1; + break; + } + } + + return instrument_count; +} +#include <stdio.h> +int CPSong::get_channels_in_use() { + + int max=0; + + for (int p=0;p<CPSong::MAX_PATTERNS;p++) { + + CPPattern *pat = get_pattern(p); + if (pat->is_empty()) + continue; + + + for (int c=(CPPattern::WIDTH-1);c>=0;c--) { + + if (c<max) + break; + + bool has_note=false; + for (int r=0;r<pat->get_length();r++) { + + CPNote n = pat->get_note( c, r ); + if (!n.is_empty()) { + has_note=true; + break; + } + } + + if (has_note) { + + max=c+1; + } + } + } + + return max; +} + + +void CPSong::separate_in_one_sample_instruments(int p_instrument) { + + CP_ERR_COND( !variables.use_instruments ); + CP_FAIL_INDEX( p_instrument, MAX_INSTRUMENTS ); + + int remapped_count=0; + + signed char remap[MAX_SAMPLES]; + + for (int i=0;i<MAX_SAMPLES;i++) { + + remap[i]=-1; + } + + /* Find remaps */ + CPInstrument *ins=get_instrument(p_instrument); + for (int i=0;i<CPNote::NOTES;i++) { + + int sn = ins->get_sample_number(i); + + // check for unusable sample + if (sn<0 || sn>=MAX_SAMPLES || get_sample(sn)->get_sample_data().is_null()) + continue; + printf("sample %i\n",sn); + if ( remap[sn] !=-1 ) { + printf("already mapped to %i\n",remap[sn]); + continue; + } + + printf("isn't remapped\n"); + + // find remap + + for (int j=0;j<MAX_INSTRUMENTS;j++) { + + if (!get_instrument(j)->is_empty()) + continue; + + printf("map to %i\n",j); + + //copy + *get_instrument(j)=*ins; + + // assign samples + for (int k=0;k<CPNote::NOTES;k++) { + + get_instrument(j)->set_note_number(k,k); + get_instrument(j)->set_sample_number(k,sn); + } + remap[sn]=j; + remapped_count++; + break; + } + + CP_ERR_COND(remap[sn]==-1); // no more free instruments + } + + printf("remapped %i\n",remapped_count); + + if (remapped_count<2) { + //undo if only one is remapped + for (int i=0;i<MAX_SAMPLES;i++) { + + if (remap[i]!=-1) { + + get_instrument(remap[i])->reset(); + } + } + return; + } + + /* remap all song */ + + for (int p=0;p<CPSong::MAX_PATTERNS;p++) { + + CPPattern *pat = get_pattern(p); + if (pat->is_empty()) + continue; + + + for (int c=0;c<CPPattern::WIDTH;c++) { + + for (int r=0;r<pat->get_length();r++) { + + CPNote n = pat->get_note(c,r); + if (n.note<CPNote::NOTES && n.instrument==p_instrument) { + + int sn = ins->get_sample_number(n.note); + if (remap[sn]==-1) + pat->set_note(c,r,CPNote()); + else { + + n.instrument=remap[sn]; + pat->set_note(c,r,n); + } + } + } + } + } + + ins->reset(); + +} + + +CPSong::CPSong() { + + reset(); +} +CPSong::~CPSong() { + +} + + + + +int get_song_next_order_idx(CPSong *p_song, int p_order_idx) { + + int baseorder,order_counter; + + order_counter=-1; + + baseorder=p_order_idx; + + do { + + baseorder++; + if ( baseorder>(CPSong::MAX_ORDERS-1) ) baseorder=0; + order_counter++; + + } while ( (p_song->get_order(baseorder)>=(CPSong::MAX_PATTERNS) ) && (order_counter<CPSong::MAX_ORDERS) ); + + + if (order_counter==CPSong::MAX_ORDERS) { + + return -1; + + } else { + + return baseorder; + } + +} diff --git a/modules/chibi/cp_song.h b/modules/chibi/cp_song.h new file mode 100644 index 000000000..ba0fa3e80 --- /dev/null +++ b/modules/chibi/cp_song.h @@ -0,0 +1,261 @@ +/*************************************************************************/ +/* cp_song.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef CPSONG_H +#define CPSONG_H + + +/**CPSong Class + *@author Juan Linietsky + */ + +#include "cp_order.h" +#include "cp_pattern.h" +#include "cp_sample.h" +#include "cp_instrument.h" + +class CPSong { +public: + enum { + MAX_SONG_NAME=26, + MAX_ORDERS=200, + MAX_PATTERNS=200, + MAX_SAMPLES=99, + MAX_INSTRUMENTS=99, + + CHANNEL_MAX_PAN=64, + CHANNEL_MAX_VOLUME=64, + CHANNEL_MAX_CHORUS=64, + CHANNEL_MAX_REVERB=64, + + MIN_TEMPO=31, + MAX_TEMPO=255, + MIN_SPEED=1, + MAX_SPEED=255, + MAX_MESSAGE_LEN=8000, + + + + }; + + enum ReverbMode { + REVERB_MODE_ROOM, + REVERB_MODE_STUDIO_SMALL, + REVERB_MODE_STUDIO_MEDIUM, + REVERB_MODE_STUDIO_LARGE, + REVERB_MODE_HALL, + REVERB_MODE_SPACE_ECHO, + REVERB_MODE_ECHO, + REVERB_MODE_DELAY, + REVERB_MODE_HALF_ECHO + }; + +private: + CPOrder order[MAX_ORDERS]; + CPPattern pattern[MAX_PATTERNS]; + CPSample sample[MAX_SAMPLES]; + CPInstrument instrument[MAX_INSTRUMENTS]; + + + + struct Song_Variables { // variables that wont change in playback + + char name[MAX_SONG_NAME]; + char message[MAX_MESSAGE_LEN]; + /* string message; */ + + int row_highlight_minor; + int row_highlight_major; + + int mixing_volume; + int stereo_separation; + + bool use_stereo; + bool use_instruments; + bool use_linear_slides; + + bool old_effects; + bool compatible_gxx; + + } variables; + + struct Initial_Variables { // Initial values used for playback + + struct Channel_State { + + int pan,volume; // 0-- CHANNEL_MAX_PAN, CHANNEL_MAX_VOLUME + bool surround; + bool mute; + int chorus; //0 - 64 + int reverb; //0 - 64 + }; + + int global_volume; + int speed; + int tempo; + + Channel_State channel[CPPattern::WIDTH]; + } initial_variables; + + struct Effects { + + ReverbMode reverb_mode; + + struct Chorus { + + int delay_ms; + int separation_ms; + int depth_ms10; + int speed_hz10; + } chorus; + + } effects; + +public: + + /* Properties */ + + const char *get_name(); + void set_name(const char *p_name); + + const char *get_message(); + void set_message(const char *p_message); + + void set_row_highlight_minor(int p_hl_minor); /* 0 .. 256 */ + int get_row_highlight_minor(); /* 0 .. 256 */ + + void set_row_highlight_major(int p_hl_major); /* 0 .. 256 */ + int get_row_highlight_major(); /* 0 .. 256 */ + + void set_mixing_volume(int p_mix_volume); /* 0 .. 128 */ + int get_mixing_volume(); /* 0 .. 128 */ + + void set_global_volume(int p_global_volume); /* 0 .. 128 */ + int get_global_volume(); /* 0 .. 128 */ + + void set_stereo_separation(int p_separation); /* 0 .. 128 */ + int get_stereo_separation(); /* 0 .. 128 */ + + void set_stereo(bool p_stereo); + bool is_stereo(); + + void set_instruments(bool p_instruments); + bool has_instruments(); + + void set_linear_slides(bool p_linear_slides); + bool has_linear_slides(); + + void set_old_effects(bool p_old_effects); + bool has_old_effects(); + + void set_compatible_gxx(bool p_compatible_gxx); + bool has_compatible_gxx(); + + void set_speed(int p_speed); /* 1 .. 255 */ + int get_speed(); /* 1 .. 255 */ + + void set_tempo(int p_tempo); /* 31 .. 255 */ + int get_tempo(); /* 31 .. 255 */ + + void set_channel_pan(int p_channel,int p_pan); /* 0 .. 64 */ + int get_channel_pan(int p_channel); + + void set_channel_volume(int p_channel,int p_volume); /* 0 .. 64 */ + int get_channel_volume(int p_channel); + + void set_channel_surround(int p_channel,bool p_surround); + bool is_channel_surround(int p_channel); + + void set_channel_mute(int p_channel,bool p_mute); + bool is_channel_mute(int p_channel); + + void set_channel_chorus(int p_channel,int p_chorus); /* 0 .. 64 */ + int get_channel_chorus(int p_channel); + + void set_channel_reverb(int p_channel,int p_reverb); /* 0 .. 64 */ + int get_channel_reverb(int p_channel); + + /* arrays of stuff */ + + CPPattern* get_pattern(int p_pattern); + CPSample* get_sample(int p_sample); + CPInstrument* get_instrument(int p_instrument); + + int get_order(int p_position); + void set_order(int p_position,int p_order); + + + /* Effects */ + + ReverbMode get_reverb_mode(); + void set_reverb_mode(ReverbMode p_mode); + + void set_chorus_delay_ms(int p_amount); + void set_chorus_separation_ms(int p_amount); + void set_chorus_depth_ms10(int p_amount); + void set_chorus_speed_hz10(int p_amount); + + int get_chorus_delay_ms(); + int get_chorus_separation_ms(); + int get_chorus_depth_ms10(); + int get_chorus_speed_hz10(); + + /* utils */ + + void reset(bool p_clear_patterns=true,bool p_clear_samples=true,bool p_clear_instruments=true,bool p_clear_variables=true); + + void cleanup_unused_patterns(); + void cleanup_unused_instruments(); + void cleanup_unused_samples(); + void cleanup_unused_orders(); + void clear_all_default_pan(); + void clear_all_default_vol(); + + void clear_instrument_with_samples(int p_instrument); + + void make_instruments_from_samples(); + void make_instrument_from_sample(int p_sample); + + void separate_in_one_sample_instruments(int p_instrument); + + int get_order_in_use_count(); + int get_pattern_in_use_count(); + int get_instrument_in_use_count(); + int get_channels_in_use(); + + CPSong(); + ~CPSong(); + +}; + + +/* Some helper for something used a lot */ + +int get_song_next_order_idx(CPSong *p_song, int p_order_idx); + +#endif diff --git a/modules/chibi/cp_tables.cpp b/modules/chibi/cp_tables.cpp new file mode 100644 index 000000000..a7ed34ff3 --- /dev/null +++ b/modules/chibi/cp_tables.cpp @@ -0,0 +1,254 @@ +/*************************************************************************/ +/* cp_tables.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cp_tables.h" + +int32_t CPTables::linear_period_to_freq_tab[768]={ + + 535232,534749,534266,533784,533303,532822,532341,531861, + 531381,530902,530423,529944,529466,528988,528511,528034, + 527558,527082,526607,526131,525657,525183,524709,524236, + 523763,523290,522818,522346,521875,521404,520934,520464, + 519994,519525,519057,518588,518121,517653,517186,516720, + 516253,515788,515322,514858,514393,513929,513465,513002, + 512539,512077,511615,511154,510692,510232,509771,509312, + 508852,508393,507934,507476,507018,506561,506104,505647, + 505191,504735,504280,503825,503371,502917,502463,502010, + 501557,501104,500652,500201,499749,499298,498848,498398, + 497948,497499,497050,496602,496154,495706,495259,494812, + 494366,493920,493474,493029,492585,492140,491696,491253, + 490809,490367,489924,489482,489041,488600,488159,487718, + 487278,486839,486400,485961,485522,485084,484647,484210, + 483773,483336,482900,482465,482029,481595,481160,480726, + 480292,479859,479426,478994,478562,478130,477699,477268, + 476837,476407,475977,475548,475119,474690,474262,473834, + 473407,472979,472553,472126,471701,471275,470850,470425, + 470001,469577,469153,468730,468307,467884,467462,467041, + 466619,466198,465778,465358,464938,464518,464099,463681, + 463262,462844,462427,462010,461593,461177,460760,460345, + 459930,459515,459100,458686,458272,457859,457446,457033, + 456621,456209,455797,455386,454975,454565,454155,453745, + 453336,452927,452518,452110,451702,451294,450887,450481, + 450074,449668,449262,448857,448452,448048,447644,447240, + 446836,446433,446030,445628,445226,444824,444423,444022, + 443622,443221,442821,442422,442023,441624,441226,440828, + 440430,440033,439636,439239,438843,438447,438051,437656, + 437261,436867,436473,436079,435686,435293,434900,434508, + 434116,433724,433333,432942,432551,432161,431771,431382, + 430992,430604,430215,429827,429439,429052,428665,428278, + 427892,427506,427120,426735,426350,425965,425581,425197, + 424813,424430,424047,423665,423283,422901,422519,422138, + 421757,421377,420997,420617,420237,419858,419479,419101, + 418723,418345,417968,417591,417214,416838,416462,416086, + 415711,415336,414961,414586,414212,413839,413465,413092, + 412720,412347,411975,411604,411232,410862,410491,410121, + 409751,409381,409012,408643,408274,407906,407538,407170, + 406803,406436,406069,405703,405337,404971,404606,404241, + 403876,403512,403148,402784,402421,402058,401695,401333, + 400970,400609,400247,399886,399525,399165,398805,398445, + 398086,397727,397368,397009,396651,396293,395936,395579, + 395222,394865,394509,394153,393798,393442,393087,392733, + 392378,392024,391671,391317,390964,390612,390259,389907, + 389556,389204,388853,388502,388152,387802,387452,387102, + 386753,386404,386056,385707,385359,385012,384664,384317, + 383971,383624,383278,382932,382587,382242,381897,381552, + 381208,380864,380521,380177,379834,379492,379149,378807, + 378466,378124,377783,377442,377102,376762,376422,376082, + 375743,375404,375065,374727,374389,374051,373714,373377, + 373040,372703,372367,372031,371695,371360,371025,370690, + 370356,370022,369688,369355,369021,368688,368356,368023, + 367691,367360,367028,366697,366366,366036,365706,365376, + 365046,364717,364388,364059,363731,363403,363075,362747, + 362420,362093,361766,361440,361114,360788,360463,360137, + 359813,359488,359164,358840,358516,358193,357869,357547, + 357224,356902,356580,356258,355937,355616,355295,354974, + 354654,354334,354014,353695,353376,353057,352739,352420, + 352103,351785,351468,351150,350834,350517,350201,349885, + 349569,349254,348939,348624,348310,347995,347682,347368, + 347055,346741,346429,346116,345804,345492,345180,344869, + 344558,344247,343936,343626,343316,343006,342697,342388, + 342079,341770,341462,341154,340846,340539,340231,339924, + 339618,339311,339005,338700,338394,338089,337784,337479, + 337175,336870,336566,336263,335959,335656,335354,335051, + 334749,334447,334145,333844,333542,333242,332941,332641, + 332341,332041,331741,331442,331143,330844,330546,330247, + 329950,329652,329355,329057,328761,328464,328168,327872, + 327576,327280,326985,326690,326395,326101,325807,325513, + 325219,324926,324633,324340,324047,323755,323463,323171, + 322879,322588,322297,322006,321716,321426,321136,320846, + 320557,320267,319978,319690,319401,319113,318825,318538, + 318250,317963,317676,317390,317103,316817,316532,316246, + 315961,315676,315391,315106,314822,314538,314254,313971, + 313688,313405,313122,312839,312557,312275,311994,311712, + 311431,311150,310869,310589,310309,310029,309749,309470, + 309190,308911,308633,308354,308076,307798,307521,307243, + 306966,306689,306412,306136,305860,305584,305308,305033, + 304758,304483,304208,303934,303659,303385,303112,302838, + 302565,302292,302019,301747,301475,301203,300931,300660, + 300388,300117,299847,299576,299306,299036,298766,298497, + 298227,297958,297689,297421,297153,296884,296617,296349, + 296082,295815,295548,295281,295015,294749,294483,294217, + 293952,293686,293421,293157,292892,292628,292364,292100, + 291837,291574,291311,291048,290785,290523,290261,289999, + 289737,289476,289215,288954,288693,288433,288173,287913, + 287653,287393,287134,286875,286616,286358,286099,285841, + 285583,285326,285068,284811,284554,284298,284041,283785, + 283529,283273,283017,282762,282507,282252,281998,281743, + 281489,281235,280981,280728,280475,280222,279969,279716, + 279464,279212,278960,278708,278457,278206,277955,277704, + 277453,277203,276953,276703,276453,276204,275955,275706, + 275457,275209,274960,274712,274465,274217,273970,273722, + 273476,273229,272982,272736,272490,272244,271999,271753, + 271508,271263,271018,270774,270530,270286,270042,269798, + 269555,269312,269069,268826,268583,268341,268099,267857 +}; + +uint16_t CPTables::old_period_table[OCTAVE*2]={ + + 0x6b00, 0x6800, 0x6500, 0x6220, 0x5f50, 0x5c80, + 0x5a00, 0x5740, 0x54d0, 0x5260, 0x5010, 0x4dc0, + 0x4b90, 0x4960, 0x4750, 0x4540, 0x4350, 0x4160, + 0x3f90, 0x3dc0, 0x3c10, 0x3a40, 0x38b0, 0x3700 +}; + +#define LOGFAC 2*16 + +uint16_t CPTables::log_table[104]= { + LOGFAC*907,LOGFAC*900,LOGFAC*894,LOGFAC*887, + LOGFAC*881,LOGFAC*875,LOGFAC*868,LOGFAC*862, + LOGFAC*856,LOGFAC*850,LOGFAC*844,LOGFAC*838, + LOGFAC*832,LOGFAC*826,LOGFAC*820,LOGFAC*814, + LOGFAC*808,LOGFAC*802,LOGFAC*796,LOGFAC*791, + LOGFAC*785,LOGFAC*779,LOGFAC*774,LOGFAC*768, + LOGFAC*762,LOGFAC*757,LOGFAC*752,LOGFAC*746, + LOGFAC*741,LOGFAC*736,LOGFAC*730,LOGFAC*725, + LOGFAC*720,LOGFAC*715,LOGFAC*709,LOGFAC*704, + LOGFAC*699,LOGFAC*694,LOGFAC*689,LOGFAC*684, + LOGFAC*678,LOGFAC*675,LOGFAC*670,LOGFAC*665, + LOGFAC*660,LOGFAC*655,LOGFAC*651,LOGFAC*646, + LOGFAC*640,LOGFAC*636,LOGFAC*632,LOGFAC*628, + LOGFAC*623,LOGFAC*619,LOGFAC*614,LOGFAC*610, + LOGFAC*604,LOGFAC*601,LOGFAC*597,LOGFAC*592, + LOGFAC*588,LOGFAC*584,LOGFAC*580,LOGFAC*575, + LOGFAC*570,LOGFAC*567,LOGFAC*563,LOGFAC*559, + LOGFAC*555,LOGFAC*551,LOGFAC*547,LOGFAC*543, + LOGFAC*538,LOGFAC*535,LOGFAC*532,LOGFAC*528, + LOGFAC*524,LOGFAC*520,LOGFAC*516,LOGFAC*513, + LOGFAC*508,LOGFAC*505,LOGFAC*502,LOGFAC*498, + LOGFAC*494,LOGFAC*491,LOGFAC*487,LOGFAC*484, + LOGFAC*480,LOGFAC*477,LOGFAC*474,LOGFAC*470, + LOGFAC*467,LOGFAC*463,LOGFAC*460,LOGFAC*457, + LOGFAC*453,LOGFAC*450,LOGFAC*447,LOGFAC*443, + LOGFAC*440,LOGFAC*437,LOGFAC*434,LOGFAC*431 +}; + + + +int32_t CPTables::get_linear_period(uint16_t note,int32_t fine) { + + int32_t t; + + t=(24L*OCTAVE-(int32_t)note)*32L-(fine>>1); + return t; +} + +static int s3m_period_table[12]={1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907}; + + +int32_t CPTables::get_log_period(uint16_t note,int32_t p_c5freq) { + + return (8363L * 16 * s3m_period_table[note%12] >> (note/12)) / p_c5freq; + +} + +/* +int32_t CPTables::get_log_period(uint16_t note,int32_t p_c5freq) +{ + uint16_t n,o; + uint16_t p1,p2; + int32_t i; + + n=note%(2*OCTAVE); + o=note/(2*OCTAVE); + i=(n<<2); // n*8 + fine/16 + + if (i<0) + i=0; + + if (i>102) + i=102; + + + p1=log_table[i]; + p2=log_table[i+1]; + + + return (Interpolate(fine>>4,0,15,p1,p2)>>o); + +} */ + +int32_t CPTables::get_old_period(uint16_t note,int32_t speed) { + + uint16_t n,o,res; + +// if (!speed) { + + // return 4242; /* <- prevent divide overflow */ + // } + + n=note%(2*OCTAVE); + o=note/(2*OCTAVE); + + res=((8363L*(int32_t)old_period_table[n])>>o)/((old_period_table[17]>>1)+(speed<<2)); /*/(128-speed)*/; + + return res; +} + +int32_t CPTables::get_linear_frequency(int32_t period) { + + int32_t shift_value=(((int32_t)period/768)-2); + if (shift_value>0) { + + return linear_period_to_freq_tab[period%768]>>shift_value; + } else { + shift_value=0-shift_value; + return linear_period_to_freq_tab[period%768]<<shift_value; + } +} + +int32_t CPTables::get_old_frequency(int32_t period) { + + return (8363L*1712L)/(period?period:1); + +} + +CPTables::CPTables(){ +} +CPTables::~CPTables(){ +} diff --git a/modules/chibi/cp_tables.h b/modules/chibi/cp_tables.h new file mode 100644 index 000000000..4baa1c648 --- /dev/null +++ b/modules/chibi/cp_tables.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* cp_tables.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CPTABLES_H +#define CPTABLES_H + +#include "cp_config.h" + +/**conversion CPTables/functions + *@author Juan Linietsky + */ + +/****************************** + CPTables.h + -------- + +CPTables methods for miscelaneous +conversion utilities +********************************/ + +class CPTables { +public: + + enum { OCTAVE=12 }; + + static uint16_t old_period_table[OCTAVE*2]; + static uint16_t log_table[104]; + static int32_t linear_period_to_freq_tab[768]; + + static int32_t get_old_period(uint16_t note,int32_t speed); + static int32_t get_amiga_period(uint16_t note,int32_t fine); + static int32_t get_linear_period(uint16_t note,int32_t fine); + static int32_t get_linear_frequency(int32_t period); + static int32_t get_old_frequency(int32_t period); + static int32_t get_log_period(uint16_t note,int32_t p_c5freq); + + CPTables(); + ~CPTables(); +}; + +#endif diff --git a/modules/chibi/event_stream_chibi.cpp b/modules/chibi/event_stream_chibi.cpp new file mode 100644 index 000000000..ab3408052 --- /dev/null +++ b/modules/chibi/event_stream_chibi.cpp @@ -0,0 +1,872 @@ +/*************************************************************************/ +/* event_stream_chibi.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "event_stream_chibi.h" +#include "cp_loader_it.h" +#include "cp_loader_xm.h" +#include "cp_loader_s3m.h" +#include "cp_loader_mod.h" + +static CPSampleManagerImpl *sample_manager; +static ResourceFormatLoaderChibi *resource_loader; + +CPSample_ID CPSampleManagerImpl::create(bool p_16bits,bool p_stereo,int32_t p_len) { + + AudioServer::SampleFormat sf=p_16bits?AudioServer::SAMPLE_FORMAT_PCM16:AudioServer::SAMPLE_FORMAT_PCM8; + + SampleData *sd = memnew( SampleData ); + sd->rid = AudioServer::get_singleton()->sample_create(sf,p_stereo,p_len); + sd->stereo=p_stereo; + sd->len=p_len; + sd->is16=p_16bits; + sd->mixfreq=44100; + sd->loop_begin=0; + sd->loop_end=0; + sd->loop_type=CP_LOOP_NONE; + sd->locks=0; +#ifdef DEBUG_ENABLED + valid.insert(sd); +#endif + CPSample_ID sid; + sid._private=sd; + return sid; +} + +void CPSampleManagerImpl::recreate(CPSample_ID p_id,bool p_16bits,bool p_stereo,int32_t p_len){ + + AudioServer::SampleFormat sf=p_16bits?AudioServer::SAMPLE_FORMAT_PCM16:AudioServer::SAMPLE_FORMAT_PCM8; + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + AudioServer::get_singleton()->free(sd->rid); + sd->rid = AudioServer::get_singleton()->sample_create(sf,p_stereo,p_len); + sd->stereo=p_stereo; + sd->len=p_len; + sd->is16=p_16bits; + sd->mixfreq=44100; + sd->loop_begin=0; + sd->loop_end=0; + sd->loop_type=CP_LOOP_NONE; +} +void CPSampleManagerImpl::destroy(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); + valid.erase(sd); +#endif + AudioServer::get_singleton()->free(sd->rid); + + memdelete(sd); +} +bool CPSampleManagerImpl::check(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + return valid.has(sd); +#else + return _getsd(p_id)!=NULL; +#endif +} + +void CPSampleManagerImpl::set_c5_freq(CPSample_ID p_id,int32_t p_freq){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + sd->mixfreq=p_freq; + AudioServer::get_singleton()->sample_set_mix_rate(sd->rid,p_freq); + +} +void CPSampleManagerImpl::set_loop_begin(CPSample_ID p_id,int32_t p_begin){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + sd->loop_begin=p_begin; + AudioServer::get_singleton()->sample_set_loop_begin(sd->rid,p_begin); + +} +void CPSampleManagerImpl::set_loop_end(CPSample_ID p_id,int32_t p_end){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + sd->loop_end=p_end; + AudioServer::get_singleton()->sample_set_loop_end(sd->rid,p_end); + +} +void CPSampleManagerImpl::set_loop_type(CPSample_ID p_id,CPSample_Loop_Type p_type){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + + sd->loop_type=p_type; + AudioServer::get_singleton()->sample_set_loop_format(sd->rid,AudioServer::SampleLoopFormat(p_type)); + + +} +void CPSampleManagerImpl::set_chunk(CPSample_ID p_id,int32_t p_index,void *p_data,int p_data_len){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + + ERR_FAIL(); +} + + +int32_t CPSampleManagerImpl::get_loop_begin(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + return sd->loop_begin; + +} +int32_t CPSampleManagerImpl::get_loop_end(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + return sd->loop_end; +} +CPSample_Loop_Type CPSampleManagerImpl::get_loop_type(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),CP_LOOP_NONE); +#endif + + return sd->loop_type; +} +int32_t CPSampleManagerImpl::get_c5_freq(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + return sd->mixfreq; +} +int32_t CPSampleManagerImpl::get_size(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + return sd->len; + +} +bool CPSampleManagerImpl::is_16bits(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),false); +#endif + + return sd->is16; + +} +bool CPSampleManagerImpl::is_stereo(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),false); +#endif + return sd->stereo; + + +} +bool CPSampleManagerImpl::lock_data(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + sd->locks++; + if (sd->locks==1) { + sd->lock=AudioServer::get_singleton()->sample_get_data(sd->rid); + sd->w=sd->lock.write(); + } + + return true; +} +void *CPSampleManagerImpl::get_data(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + ERR_FAIL_COND_V(sd->locks==0,0); + return sd->w.ptr(); +} + +int16_t CPSampleManagerImpl::get_data(CPSample_ID p_id, int p_sample, int p_channel){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V(!valid.has(sd),0); +#endif + + ERR_FAIL_V(0); + lock_data(p_id); + + int sofs = sd->stereo ? 2:1; + uint16_t v=0; + if (sd->is16) { + int16_t *p=(int16_t*)sd->w.ptr(); + v=p[p_sample*sofs+p_channel]; + } else { + int8_t *p=(int8_t*)sd->w.ptr(); + v=p[p_sample*sofs+p_channel]; + } + + unlock_data(p_id); + + return v; +} +void CPSampleManagerImpl::set_data(CPSample_ID p_id, int p_sample, int16_t p_data,int p_channel){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + + ERR_FAIL(); + lock_data(p_id); + + int sofs = sd->stereo ? 2:1; + if (sd->is16) { + int16_t *p=(int16_t*)sd->w.ptr(); + p[p_sample*sofs+p_channel]=p_data; + } else { + int8_t *p=(int8_t*)sd->w.ptr(); + p[p_sample*sofs+p_channel]=p_data; + } + + unlock_data(p_id); + +} +void CPSampleManagerImpl::unlock_data(CPSample_ID p_id){ + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + + ERR_FAIL_COND(sd->locks==0); + + sd->locks--; + if (sd->locks==0) { + sd->w=PoolVector<uint8_t>::Write(); + AudioServer::get_singleton()->sample_set_data(sd->rid,sd->lock); + sd->lock=PoolVector<uint8_t>(); + } +} + +void CPSampleManagerImpl::get_chunk(CPSample_ID p_id,int32_t p_index,void *p_data,int p_data_len) { + + SampleData *sd=_getsd(p_id); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(!valid.has(sd)); +#endif + + ERR_FAIL(); +} + + +/** MIXER **/ + +void CPMixerImpl::set_callback_interval(int p_interval_us) { + + callback_interval=p_interval_us; +} + +void CPMixerImpl::set_callback(void (*p_callback)(void*),void *p_userdata) { + + callback=p_callback; + userdata=p_userdata; +} + +void CPMixerImpl::setup_voice(int p_voice_index,CPSample_ID p_sample_id,int32_t p_start_index) { + + Voice &v=voices[p_voice_index]; + if (v.channel!=AudioMixer::INVALID_CHANNEL) { + mixer->channel_free(v.channel); + } + v.channel=mixer->channel_alloc(sample_manager->get_rid(p_sample_id)); + v.freq_mult = sample_manager->get_c5_freq(p_sample_id)/261.6255653006; + v.sample = p_sample_id; +} + +void CPMixerImpl::stop_voice(int p_voice_index) { + + Voice &v=voices[p_voice_index]; + if (v.channel==AudioMixer::INVALID_CHANNEL) + return; + + mixer->channel_free(v.channel); + v.channel=AudioMixer::INVALID_CHANNEL; + +} + +void CPMixerImpl::set_voice_frequency(int p_voice_index,int32_t p_freq) { + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + float f = p_freq / 256.0; + f*=pitch_scale; + mixer->channel_set_mix_rate(v.channel,f * v.freq_mult ); +} + +void CPMixerImpl::set_voice_panning(int p_voice_index,int p_pan) { + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + if (p_pan==CP_PAN_SURROUND) + p_pan=CP_PAN_CENTER; + float p = p_pan / 256.0; + mixer->channel_set_pan(v.channel,p); + +} + +void CPMixerImpl::set_voice_volume(int p_voice_index,int p_vol) { + + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + float vol = p_vol/512.0; + vol*=voice_scale; + mixer->channel_set_volume(v.channel,vol); + mixer->channel_set_reverb(v.channel,reverb_type,vol*v.reverb); +} + +void CPMixerImpl::set_voice_filter(int p_voice_index,bool p_enabled,uint8_t p_cutoff, uint8_t p_resonance ){ + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + +} + +void CPMixerImpl::set_voice_reverb_send(int p_voice_index,int p_reverb){ + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + v.reverb=p_reverb/255.0; + //mixer->channel_set_reverb(v.channel,reverb_type,p_reverb/255.0); + +} + +void CPMixerImpl::set_voice_chorus_send(int p_voice_index,int p_chorus){ + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + mixer->channel_set_chorus(v.channel,p_chorus/255.0); + +} + + +void CPMixerImpl::set_reverb_mode(ReverbMode p_mode){ + + //Voice &v=voices[p_voice_index]; + //ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + switch(p_mode) { + case CPMixer::REVERB_MODE_STUDIO_SMALL: reverb_type=AudioMixer::REVERB_SMALL; break; + case CPMixer::REVERB_MODE_STUDIO_MEDIUM: reverb_type=AudioMixer::REVERB_MEDIUM; break; + case CPMixer::REVERB_MODE_STUDIO_LARGE: reverb_type=AudioMixer::REVERB_LARGE; break; + case CPMixer::REVERB_MODE_HALL: reverb_type=AudioMixer::REVERB_HALL; break; + default: reverb_type=AudioMixer::REVERB_SMALL; break; + } + +} + +void CPMixerImpl::set_chorus_params(unsigned int p_delay_ms,unsigned int p_separation_ms,unsigned int p_depth_ms10,unsigned int p_speed_hz10){ + + //Voice &v=voices[p_voice_index]; + //ERR_FAIL_COND(v.channel==AudioMixer::INVALID_CHANNEL); + +} + + + +/* Info retrieving */ + +int32_t CPMixerImpl::get_voice_sample_pos_index(int p_voice_index) { + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND_V(v.channel==AudioMixer::INVALID_CHANNEL,0); + return 0; + +} + +int CPMixerImpl::get_voice_panning(int p_voice_index) { + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND_V(!is_voice_active(p_voice_index),0); + return mixer->channel_get_pan(v.channel)*CP_PAN_RIGHT; + +} + +int CPMixerImpl::get_voice_volume(int p_voice_index) { + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND_V(!is_voice_active(p_voice_index),0); + return mixer->channel_get_volume(v.channel); + + +} + +CPSample_ID CPMixerImpl::get_voice_sample_id(int p_voice_index) { + + Voice &v=voices[p_voice_index]; + ERR_FAIL_COND_V(v.channel==AudioMixer::INVALID_CHANNEL,CPSample_ID()); + return v.sample; + + +} + +bool CPMixerImpl::is_voice_active(int p_voice_index){ + + Voice &v=voices[p_voice_index]; + if (v.channel==AudioMixer::INVALID_CHANNEL) + return false; + if (!mixer->channel_is_valid(v.channel)) + v.channel=AudioMixer::INVALID_CHANNEL; + + return v.channel!=AudioMixer::INVALID_CHANNEL; +} + +void CPMixerImpl::process_usecs(int p_usec,float p_volume,float p_pitch_scale,float p_tempo_scale) { + + ERR_FAIL_COND(callback_interval==0); + //update this somewhere + pitch_scale=p_pitch_scale; + tempo_scale=p_tempo_scale; + voice_scale = AudioServer::get_singleton()->get_event_voice_global_volume_scale()*p_volume; + while(p_usec) { + + if (p_usec>=callback_timeout) { + + p_usec-=callback_timeout; + callback_timeout=0; + if (callback) { + callback(userdata); + } + callback_timeout=callback_interval*(1.0/p_tempo_scale); + + } else { + + callback_timeout-=p_usec; + p_usec=0; + } + } +} + + +CPMixerImpl::CPMixerImpl(AudioMixer *p_mixer) { + + callback_interval=1; + callback_timeout=0; + userdata=0; + callback=0; + tempo_scale=1.0; + pitch_scale=1.0; + mixer=p_mixer; + voice_scale = AudioServer::get_singleton()->get_event_voice_global_volume_scale(); + reverb_type = AudioMixer::REVERB_SMALL; + +} + +/** FILE ACCESS WRAPPER **/ + + +CPFileAccessWrapperImpl::Error CPFileAccessWrapperImpl::open(const char *p_filename, int p_mode_flags) { + + ERR_FAIL_COND_V(p_mode_flags&WRITE,ERROR_WRITING_FILE); + close(); + f = FileAccess::open(String::utf8(p_filename),p_mode_flags); + if (!f) + return ERROR_FILE_NOT_FOUND; + return OK; +} + +void CPFileAccessWrapperImpl::close(){ + + if (f) + memdelete(f); + f=NULL; + + +} + +void CPFileAccessWrapperImpl::seek(uint32_t p_position){ + + f->seek(p_position); +} +void CPFileAccessWrapperImpl::seek_end(){ + + f->seek_end(); +} +uint32_t CPFileAccessWrapperImpl::get_pos(){ + + return f->get_pos(); +} + +bool CPFileAccessWrapperImpl::eof_reached(){ + + return f->eof_reached(); +} + +uint8_t CPFileAccessWrapperImpl::get_byte(){ + + return f->get_8(); +} +void CPFileAccessWrapperImpl::get_byte_array(uint8_t *p_dest,int p_elements){ + + f->get_buffer(p_dest,p_elements); +} +void CPFileAccessWrapperImpl::get_word_array(uint16_t *p_dest,int p_elements){ + + for(int i=0;i<p_elements;i++) { + p_dest[i]=f->get_16(); + } + +} + +uint16_t CPFileAccessWrapperImpl::get_word(){ + + return f->get_16(); +} +uint32_t CPFileAccessWrapperImpl::get_dword(){ + + return f->get_32(); +} + +void CPFileAccessWrapperImpl::set_endian_conversion(bool p_swap){ + + f->set_endian_swap(p_swap); +} +bool CPFileAccessWrapperImpl::is_open(){ + + return f!=NULL; +} + +CPFileAccessWrapperImpl::Error CPFileAccessWrapperImpl::get_error(){ + + return (f->get_error()!=::OK)?ERROR_READING_FILE:OK; +} + +void CPFileAccessWrapperImpl::store_byte(uint8_t p_dest){ + +} +void CPFileAccessWrapperImpl::store_byte_array(const uint8_t *p_dest,int p_elements){ + +} + +void CPFileAccessWrapperImpl::store_word(uint16_t p_dest){ + +} +void CPFileAccessWrapperImpl::store_dword(uint32_t p_dest){ + +} + +//////////////////////////////////////////////// + + +Error EventStreamPlaybackChibi::_play() { + + last_order=0; + loops=0; + player->play_start_song(); + total_usec=0; + + return OK; +} + +bool EventStreamPlaybackChibi::_update(AudioMixer* p_mixer, uint64_t p_usec){ + + total_usec+=p_usec; + mixer.process_usecs(p_usec,volume,pitch_scale,tempo_scale); + int order=player->get_current_order(); + if (order<last_order) { + if (!loop) { + stop(); + } else { + loops++; + } + } + last_order=order; + return false; +} +void EventStreamPlaybackChibi::_stop(){ + + player->play_stop(); +} + +void EventStreamPlaybackChibi::set_paused(bool p_paused){ + +} +bool EventStreamPlaybackChibi::is_paused() const{ + + return false; +} +void EventStreamPlaybackChibi::set_loop(bool p_loop){ + + loop=p_loop; + +} +bool EventStreamPlaybackChibi::is_loop_enabled() const{ + + return loop; +} + +int EventStreamPlaybackChibi::get_loop_count() const{ + + //return player->is + return loops; +} + +float EventStreamPlaybackChibi::get_pos() const{ + + return double(total_usec)/1000000.0; +} +void EventStreamPlaybackChibi::seek_pos(float p_time){ + + WARN_PRINT("seek_pos unimplemented."); +} + +void EventStreamPlaybackChibi::set_volume(float p_volume) { + + volume=p_volume; +} + +float EventStreamPlaybackChibi::get_volume() const{ + + return volume; +} + +void EventStreamPlaybackChibi::set_pitch_scale(float p_pitch_scale) { + + pitch_scale=p_pitch_scale; +} + +float EventStreamPlaybackChibi::get_pitch_scale() const{ + + return pitch_scale; +} + +void EventStreamPlaybackChibi::set_tempo_scale(float p_tempo_scale) { + + tempo_scale=p_tempo_scale; +} + +float EventStreamPlaybackChibi::get_tempo_scale() const{ + + return tempo_scale; +} + + +void EventStreamPlaybackChibi::set_channel_volume(int p_channel,float p_volume) { + + + if (p_channel>=64) + return; + player->set_channel_global_volume(p_channel,p_volume*256); +} + + + +float EventStreamPlaybackChibi::get_channel_volume(int p_channel) const{ + + return player->get_channel_global_volume(p_channel)/256.0; + +} + +float EventStreamPlaybackChibi::get_last_note_time(int p_channel) const { + + + double v = (player->get_channel_last_note_time_usec(p_channel))/1000000.0; + if (v<0) + v=-1; + return v; +} + +EventStreamPlaybackChibi::EventStreamPlaybackChibi(Ref<EventStreamChibi> p_stream) : mixer(_get_mixer()) { + + stream=p_stream; + player = memnew( CPPlayer(&mixer,&p_stream->song) ); + loop=false; + last_order=0; + loops=0; + volume=1.0; + pitch_scale=1.0; + tempo_scale=1.0; +} +EventStreamPlaybackChibi::~EventStreamPlaybackChibi(){ + + player->play_stop(); + memdelete(player); +} + +//////////////////////////////////////////////////// + +Ref<EventStreamPlayback> EventStreamChibi::instance_playback() { + + return Ref<EventStreamPlayback>( memnew(EventStreamPlaybackChibi(Ref<EventStreamChibi>(this))) ); +} + +String EventStreamChibi::get_stream_name() const{ + + return song.get_name(); + +} + + + +float EventStreamChibi::get_length() const{ + + return 1; +} + + +EventStreamChibi::EventStreamChibi() { + + +} + + + +////////////////////////////////////////////////////////////////// + + + + +RES ResourceFormatLoaderChibi::load(const String &p_path, const String& p_original_path, Error *r_error) { + + if (r_error) + *r_error=ERR_FILE_CANT_OPEN; + String el = p_path.get_extension().to_lower(); + + CPFileAccessWrapperImpl f; + + if (el=="it") { + + Ref<EventStreamChibi> esc( memnew( EventStreamChibi ) ); + CPLoader_IT loader(&f); + CPLoader::Error err = loader.load_song(p_path.utf8().get_data(),&esc->song,false); + ERR_FAIL_COND_V(err!=CPLoader::FILE_OK,RES()); + if (r_error) + *r_error=OK; + + return esc; + + } else if (el=="xm") { + + Ref<EventStreamChibi> esc( memnew( EventStreamChibi ) ); + CPLoader_XM loader(&f); + CPLoader::Error err=loader.load_song(p_path.utf8().get_data(),&esc->song,false); + ERR_FAIL_COND_V(err!=CPLoader::FILE_OK,RES()); + if (r_error) + *r_error=OK; + return esc; + + } else if (el=="s3m") { + + Ref<EventStreamChibi> esc( memnew( EventStreamChibi ) ); + CPLoader_S3M loader(&f); + CPLoader::Error err=loader.load_song(p_path.utf8().get_data(),&esc->song,false); + ERR_FAIL_COND_V(err!=CPLoader::FILE_OK,RES()); + if (r_error) + *r_error=OK; + + return esc; + + } else if (el=="mod") { + + Ref<EventStreamChibi> esc( memnew( EventStreamChibi ) ); + CPLoader_MOD loader(&f); + CPLoader::Error err=loader.load_song(p_path.utf8().get_data(),&esc->song,false); + ERR_FAIL_COND_V(err!=CPLoader::FILE_OK,RES()); + if (r_error) + *r_error=OK; + return esc; + } + + return RES(); + +} + +void ResourceFormatLoaderChibi::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("it"); + p_extensions->push_back("xm"); + p_extensions->push_back("s3m"); + p_extensions->push_back("mod"); +} +bool ResourceFormatLoaderChibi::handles_type(const String& p_type) const { + + return (p_type=="EventStreamChibi" || p_type=="EventStream"); +} + +String ResourceFormatLoaderChibi::get_resource_type(const String &p_path) const { + String el = p_path.get_extension().to_lower(); + if (el=="it" || el=="s3m" || el=="xm" || el=="mod") + return "EventStreamChibi"; + return ""; +} + +///////////////////////////////////////////////////////////////// +void initialize_chibi() { + + sample_manager = memnew( CPSampleManagerImpl ); + resource_loader = memnew( ResourceFormatLoaderChibi ); + ClassDB::register_class<EventStreamChibi>(); + ResourceLoader::add_resource_format_loader( resource_loader ); +} + +void finalize_chibi() { + + memdelete( sample_manager ); + memdelete( resource_loader ); +} + diff --git a/modules/chibi/event_stream_chibi.h b/modules/chibi/event_stream_chibi.h new file mode 100644 index 000000000..0244ee0a9 --- /dev/null +++ b/modules/chibi/event_stream_chibi.h @@ -0,0 +1,314 @@ +/*************************************************************************/ +/* event_stream_chibi.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EVENT_STREAM_CHIBI_H +#define EVENT_STREAM_CHIBI_H + +#include "scene/resources/event_stream.h" +#include "cp_sample_manager.h" +#include "cp_mixer.h" +#include "cp_song.h" +#include "cp_file_access_wrapper.h" +#include "cp_player_data.h" +#include "resource.h" +#include "servers/audio_server.h" +#include "os/file_access.h" +#include "io/resource_loader.h" + +/** SAMPLE MANAGER **/ + +class CPSampleManagerImpl : public CPSampleManager { + + struct SampleData { + + RID rid; + bool stereo; + bool is16; + int len; + int mixfreq; + int loop_begin; + int loop_end; + int locks; + PoolVector<uint8_t> lock; + PoolVector<uint8_t>::Write w; + CPSample_Loop_Type loop_type; + }; + + + _FORCE_INLINE_ SampleData* _getsd(CPSample_ID p_id) { + + return ((SampleData*)p_id._private); + } + Set<SampleData*> valid; + +public: + + _FORCE_INLINE_ RID get_rid(CPSample_ID p_id) { return _getsd(p_id)->rid; } + virtual CPSample_ID create(bool p_16bits,bool p_stereo,int32_t p_len); + virtual void recreate(CPSample_ID p_id,bool p_16bits,bool p_stereo,int32_t p_len); + virtual void destroy(CPSample_ID p_id); + virtual bool check(CPSample_ID p_id); // return false if invalid + + virtual void set_c5_freq(CPSample_ID p_id,int32_t p_freq); + virtual void set_loop_begin(CPSample_ID p_id,int32_t p_begin); + virtual void set_loop_end(CPSample_ID p_id,int32_t p_end); + virtual void set_loop_type(CPSample_ID p_id,CPSample_Loop_Type p_type); + virtual void set_chunk(CPSample_ID p_id,int32_t p_index,void *p_data,int p_data_len); + + + virtual int32_t get_loop_begin(CPSample_ID p_id); + virtual int32_t get_loop_end(CPSample_ID p_id); + virtual CPSample_Loop_Type get_loop_type(CPSample_ID p_id); + virtual int32_t get_c5_freq(CPSample_ID p_id); + virtual int32_t get_size(CPSample_ID p_id); + virtual bool is_16bits(CPSample_ID p_id); + virtual bool is_stereo(CPSample_ID p_id); + virtual bool lock_data(CPSample_ID p_id); + virtual void *get_data(CPSample_ID p_id); /* WARNING: Not all sample managers +may be able to implement this, it depends on the mixer in use! */ + virtual int16_t get_data(CPSample_ID p_id, int p_sample, int p_channel=0); /// Does not need locking + virtual void set_data(CPSample_ID p_id, int p_sample, int16_t p_data,int p_channel=0); /// Does not need locking + virtual void unlock_data(CPSample_ID p_id); + + virtual void get_chunk(CPSample_ID p_id,int32_t p_index,void *p_data,int p_data_len); + +}; + + +/** MIXER **/ + +class CPMixerImpl : public CPMixer { + + enum { + MAX_VOICES=64 + }; + + struct Voice { + + AudioMixer::ChannelID channel; + CPSample_ID sample; + float freq_mult; + float reverb; + Voice() { reverb=0.0; } + }; + + Voice voices[MAX_VOICES]; + + + int callback_interval; + int callback_timeout; + void (*callback)(void*); + void *userdata; + float voice_scale; + float tempo_scale; + float pitch_scale; + AudioMixer::ReverbRoomType reverb_type; + AudioMixer *mixer; +public: + + void process_usecs(int p_usec,float p_volume,float p_pitch_scale,float p_tempo_scale); + + /* Callback */ + + virtual void set_callback_interval(int p_interval_us); //in usecs, for tracker it's 2500000/tempo + virtual void set_callback(void (*p_callback)(void*),void *p_userdata); + + /* Voice Control */ + + virtual void setup_voice(int p_voice_index,CPSample_ID p_sample_id,int32_t p_start_index) ; + virtual void stop_voice(int p_voice_index) ; + virtual void set_voice_frequency(int p_voice_index,int32_t p_freq) ; //in freq*FREQUENCY_BITS + virtual void set_voice_panning(int p_voice_index,int p_pan) ; + virtual void set_voice_volume(int p_voice_index,int p_vol) ; + virtual void set_voice_filter(int p_filter,bool p_enabled,uint8_t p_cutoff, uint8_t p_resonance ); + virtual void set_voice_reverb_send(int p_voice_index,int p_reverb); + virtual void set_voice_chorus_send(int p_voice_index,int p_chorus); /* 0 - 255 */ + + virtual void set_reverb_mode(ReverbMode p_mode); + virtual void set_chorus_params(unsigned int p_delay_ms,unsigned int p_separation_ms,unsigned int p_depth_ms10,unsigned int p_speed_hz10); + + + /* Info retrieving */ + + virtual int32_t get_voice_sample_pos_index(int p_voice_index) ; + virtual int get_voice_panning(int p_voice_index) ; + virtual int get_voice_volume(int p_voice_index) ; + virtual CPSample_ID get_voice_sample_id(int p_voice_index) ; + virtual bool is_voice_active(int p_voice_index); + virtual int get_active_voice_count() { return 0; } + virtual int get_total_voice_count() { return MAX_VOICES; } + + + virtual uint32_t get_mix_frequency() { return 0; } + + /* Methods below only work with software mixers, meant for software-based sound drivers, hardware mixers ignore them */ + virtual int32_t process(int32_t p_frames) { return 0; } + virtual int32_t *get_mixdown_buffer_ptr() { return NULL; } + virtual void set_mix_frequency(int32_t p_mix_frequency) {}; + + CPMixerImpl(AudioMixer *p_mixer=NULL); + virtual ~CPMixerImpl() {} +}; + +/** FILE ACCESS **/ + +class CPFileAccessWrapperImpl : public CPFileAccessWrapper { + + FileAccess *f; +public: + + + virtual Error open(const char *p_filename, int p_mode_flags); + virtual void close(); + + virtual void seek(uint32_t p_position); + virtual void seek_end(); + virtual uint32_t get_pos(); + + virtual bool eof_reached(); + + virtual uint8_t get_byte(); + virtual void get_byte_array(uint8_t *p_dest,int p_elements); + virtual void get_word_array(uint16_t *p_dest,int p_elements); + + virtual uint16_t get_word(); + virtual uint32_t get_dword(); + + virtual void set_endian_conversion(bool p_swap); + virtual bool is_open(); + + virtual Error get_error(); + + virtual void store_byte(uint8_t p_dest); + virtual void store_byte_array(const uint8_t *p_dest,int p_elements); + + virtual void store_word(uint16_t p_dest); + virtual void store_dword(uint32_t p_dest); + + CPFileAccessWrapperImpl() { f=NULL; } + virtual ~CPFileAccessWrapperImpl(){ if (f) memdelete(f); } + +}; + + + +///////////////////// + +class EventStreamChibi; + +class EventStreamPlaybackChibi : public EventStreamPlayback { + + GDCLASS(EventStreamPlaybackChibi,EventStreamPlayback); + + CPMixerImpl mixer; + uint64_t total_usec; + Ref<EventStreamChibi> stream; + mutable CPPlayer *player; + bool loop; + int last_order; + int loops; + virtual Error _play(); + virtual bool _update(AudioMixer* p_mixer, uint64_t p_usec); + virtual void _stop(); + float volume; + float tempo_scale; + float pitch_scale; + + +public: + + + virtual void set_paused(bool p_paused); + virtual bool is_paused() const; + + virtual void set_loop(bool p_loop); + virtual bool is_loop_enabled() const; + + virtual int get_loop_count() const; + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + virtual void set_volume(float p_vol); + virtual float get_volume() const; + + virtual void set_pitch_scale(float p_pitch_scale); + virtual float get_pitch_scale() const; + + virtual void set_tempo_scale(float p_tempo_scale); + virtual float get_tempo_scale() const; + + virtual void set_channel_volume(int p_channel,float p_volume); + virtual float get_channel_volume(int p_channel) const; + + virtual float get_last_note_time(int p_channel) const; + + EventStreamPlaybackChibi(Ref<EventStreamChibi> p_stream=Ref<EventStreamChibi>()); + ~EventStreamPlaybackChibi(); +}; + + +class EventStreamChibi : public EventStream { + + GDCLASS(EventStreamChibi,EventStream); + +friend class ResourceFormatLoaderChibi; +friend class EventStreamPlaybackChibi; + //I think i didn't know what const was when i wrote this more than a decade ago + //so it goes mutable :( + mutable CPSong song; + + +public: + + virtual Ref<EventStreamPlayback> instance_playback(); + + virtual String get_stream_name() const; + + virtual float get_length() const; + + virtual int get_channel_count() const { return 64; } //tracker limit + + EventStreamChibi(); +}; + + +class ResourceFormatLoaderChibi : public ResourceFormatLoader { + +public: + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + +}; + +void initialize_chibi(); +void finalize_chibi(); + +#endif // EVENT_STREAM_CHIBI_H diff --git a/modules/chibi/register_types.cpp b/modules/chibi/register_types.cpp new file mode 100644 index 000000000..1a0c80881 --- /dev/null +++ b/modules/chibi/register_types.cpp @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "event_stream_chibi.h" + +void register_chibi_types() { + + initialize_chibi(); +} + +void unregister_chibi_types() { + + finalize_chibi(); +} diff --git a/modules/chibi/register_types.h b/modules/chibi/register_types.h new file mode 100644 index 000000000..08856c074 --- /dev/null +++ b/modules/chibi/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_chibi_types(); +void unregister_chibi_types(); diff --git a/modules/cscript/SCsub b/modules/cscript/SCsub new file mode 100644 index 000000000..088240676 --- /dev/null +++ b/modules/cscript/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.modules_sources, "*.cpp") + +Export('env') diff --git a/modules/cscript/config.py b/modules/cscript/config.py new file mode 100644 index 000000000..5698a3729 --- /dev/null +++ b/modules/cscript/config.py @@ -0,0 +1,8 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/cscript/godot_c.cpp b/modules/cscript/godot_c.cpp new file mode 100644 index 000000000..d5c1b53df --- /dev/null +++ b/modules/cscript/godot_c.cpp @@ -0,0 +1,2 @@ +#include "godot_c.h" + diff --git a/modules/cscript/godot_c.h b/modules/cscript/godot_c.h new file mode 100644 index 000000000..3bf86d1ae --- /dev/null +++ b/modules/cscript/godot_c.h @@ -0,0 +1,568 @@ +#ifndef GODOT_C_H +#define GODOT_C_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(GDAPI_BUILT_IN) || !defined(WINDOWS_ENABLED) +#define GDAPI +#elif defined(GDAPI_EXPORTS) +#define GDAPI __declspec(dllexport) +#else +#define GDAPI __declspec(dllimport) +#endif + + +#define GODOT_API_VERSION 1 + + +typedef int godot_bool; + +#define GODOT_FALSE 0 +#define GODOT_TRUE 1 + +////// Image + + +#define GODOT_IMAGE_FORMAT_GRAYSCALE 0 +#define GODOT_IMAGE_FORMAT_INTENSITY 1 +#define GODOT_IMAGE_FORMAT_GRAYSCALE_ALPHA 2 +#define GODOT_IMAGE_FORMAT_RGB 3 +#define GODOT_IMAGE_FORMAT_RGBA 4 +#define GODOT_IMAGE_FORMAT_INDEXED 5 +#define GODOT_IMAGE_FORMAT_INDEXED_ALPHA 6 +#define GODOT_IMAGE_FORMAT_YUV_422 7 +#define GODOT_IMAGE_FORMAT_YUV_444 8 +#define GODOT_IMAGE_FORMAT_BC1 9 +#define GODOT_IMAGE_FORMAT_BC2 10 +#define GODOT_IMAGE_FORMAT_BC3 11 +#define GODOT_IMAGE_FORMAT_BC4 12 +#define GODOT_IMAGE_FORMAT_BC5 13 +#define GODOT_IMAGE_FORMAT_PVRTC2 14 +#define GODOT_IMAGE_FORMAT_PVRTC2_ALPHA 15 +#define GODOT_IMAGE_FORMAT_PVRTC4 16 +#define GODOT_IMAGE_FORMAT_PVRTC4_ALPHA 17 +#define GODOT_IMAGE_FORMAT_ETC 18 +#define GODOT_IMAGE_FORMAT_ATC 19 +#define GODOT_IMAGE_FORMAT_ATC_ALPHA_EXPLICIT 20 +#define GODOT_IMAGE_FORMAT_ATC_ALPHA_INTERPOLATED 21 + +typedef void* godot_image; + +godot_image GDAPI godot_image_create_empty(); +godot_image GDAPI godot_image_create(int p_width,int p_height,int p_format,int p_use_mipmaps); +godot_image GDAPI godot_image_create_with_data(int p_width,int p_height,int p_format,int p_use_mipmaps,unsigned char* p_buffer); +int GDAPI godot_image_get_width(godot_image p_image); +int GDAPI godot_image_get_height(godot_image p_image); +int GDAPI godot_image_get_format(godot_image p_image); +int GDAPI godot_image_get_mipmap_count(godot_image p_image); +godot_image GDAPI godot_image_copy(godot_image p_image); +void GDAPI godot_image_free(godot_image p_image); + + +////// RID + +typedef void* godot_rid; + +godot_rid GDAPI godot_rid_create(); +godot_rid GDAPI godot_rid_copy(godot_rid p_rid); +void GDAPI godot_rid_free(godot_rid p_rid); + +////// Variant (forward declared) + +typedef void* godot_variant; + + +////// Dictionary + +typedef void* godot_dictionary; + +godot_dictionary GDAPI godot_dictionary_create(); +void GDAPI godot_dictionary_has(godot_dictionary p_dictionary,godot_variant p_key); +godot_variant GDAPI godot_dictionary_get(godot_dictionary p_dictionary,godot_variant p_key); +void GDAPI godot_dictionary_insert(godot_dictionary p_dictionary,godot_variant p_key,godot_variant p_value); +void GDAPI godot_dictionary_remove(godot_dictionary p_dictionary,godot_variant p_key); +void GDAPI godot_dictionary_clear(godot_dictionary p_dictionary); +int GDAPI godot_dictionary_get_size(godot_dictionary p_dictionary); +void GDAPI godot_dictionary_get_keys(godot_dictionary p_dictionary,godot_variant* p_keys); +godot_dictionary GDAPI godot_dictionary_copy(godot_dictionary p_dictionary); +void GDAPI godot_dictionary_free(godot_dictionary p_dictionary); + +////// Array + +typedef void* godot_array; + +godot_array GDAPI godot_array_create(); +godot_variant GDAPI godot_array_get(godot_array p_array,int p_index); +void GDAPI godot_array_set(godot_array p_array,int p_index,godot_variant p_value); +void GDAPI godot_array_resize(godot_array p_array,int p_size); +void GDAPI godot_array_insert(godot_array p_array,int p_position,godot_variant p_value); +void GDAPI godot_array_remove(godot_array p_array,int p_position); +void GDAPI godot_array_clear(godot_array p_array); +int GDAPI godot_array_get_size(godot_array p_array); +int GDAPI godot_array_find(godot_array p_array,godot_variant p_value,int p_from_pos=-1); +godot_array GDAPI godot_array_copy(godot_array p_array); +void GDAPI godot_array_free(godot_array p_array); + +////// InputEvent + +#define INPUT_EVENT_BUTTON_LEFT 1 +#define INPUT_EVENT_BUTTON_RIGHT 2 +#define INPUT_EVENT_BUTTON_MIDDLE 3 +#define INPUT_EVENT_BUTTON_WHEEL_UP 4 +#define INPUT_EVENT_BUTTON_WHEEL_DOWN 5 +#define INPUT_EVENT_BUTTON_WHEEL_LEFT 6 +#define INPUT_EVENT_BUTTON_WHEEL_RIGHT 7 +#define INPUT_EVENT_BUTTON_MASK_LEFT (1<<(INPUT_EVENT_BUTTON_LEFT-1)) +#define INPUT_EVENT_BUTTON_MASK_RIGHT (1<<(INPUT_EVENT_BUTTON_RIGHT-1)) +#define INPUT_EVENT_BUTTON_MASK_MIDDLE (1<<(INPUT_EVENT_BUTTON_MIDDLE-1)) + +#define INPUT_EVENT_TYPE_NONE 0 +#define INPUT_EVENT_TYPE_KEY 1 +#define INPUT_EVENT_TYPE_MOUSE_MOTION 2 +#define INPUT_EVENT_TYPE_MOUSE_BUTTON 3 +#define INPUT_EVENT_TYPE_JOYPAD_MOTION 4 +#define INPUT_EVENT_TYPE_JOYPAD_BUTTON 5 +#define INPUT_EVENT_TYPE_SCREEN_TOUCH 6 +#define INPUT_EVENT_TYPE_SCREEN_DRAG 7 +#define INPUT_EVENT_TYPE_ACTION 8 + +typedef void* godot_input_event; + + +godot_input_event GDAPI godot_input_event_create(); +godot_input_event GDAPI godot_input_event_copy(godot_input_event p_input_event); +void GDAPI godot_input_event_free(godot_input_event p_input_event); + +int GDAPI godot_input_event_get_type(godot_input_event p_event); +int GDAPI godot_input_event_get_device(godot_input_event p_event); + +godot_bool GDAPI godot_input_event_mod_has_alt(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_ctrl(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_command(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_shift(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mod_has_meta(godot_input_event p_event); + +int GDAPI godot_input_event_key_get_scancode(godot_input_event p_event); +int GDAPI godot_input_event_key_get_unicode(godot_input_event p_event); +godot_bool GDAPI godot_input_event_key_is_pressed(godot_input_event p_event); +godot_bool GDAPI godot_input_event_key_is_echo(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_get_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_y(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_global_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_global_y(godot_input_event p_event); +int GDAPI godot_input_event_mouse_get_button_mask(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_button_get_button_index(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mouse_button_is_pressed(godot_input_event p_event); +godot_bool GDAPI godot_input_event_mouse_button_is_doubleclick(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_motion_get_relative_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_motion_get_relative_y(godot_input_event p_event); + +int GDAPI godot_input_event_mouse_motion_get_speed_x(godot_input_event p_event); +int GDAPI godot_input_event_mouse_motion_get_speed_y(godot_input_event p_event); + +int GDAPI godot_input_event_joypad_motion_get_axis(godot_input_event p_event); +float GDAPI godot_input_event_joypad_motion_get_axis_value(godot_input_event p_event); + +int GDAPI godot_input_event_joypad_button_get_button_index(godot_input_event p_event); +godot_bool GDAPI godot_input_event_joypad_button_is_pressed(godot_input_event p_event); +float GDAPI godot_input_event_joypad_button_get_pressure(godot_input_event p_event); + + +int GDAPI godot_input_event_screen_touch_get_index(godot_input_event p_event); +int GDAPI godot_input_event_screen_touch_get_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_touch_get_y(godot_input_event p_event); +int GDAPI godot_input_event_screen_touch_is_pressed(godot_input_event p_event); + +int GDAPI godot_input_event_screen_drag_get_index(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_y(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_relative_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_relative_y(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_speed_x(godot_input_event p_event); +int GDAPI godot_input_event_screen_drag_get_speed_y(godot_input_event p_event); + +int GDAPI godot_input_event_is_action(godot_input_event p_event,char *p_action); +int GDAPI godot_input_event_is_action_pressed(godot_input_event p_event,char *p_action); + +////// ByteArray + +typedef void* godot_byte_array; + +godot_byte_array GDAPI godot_byte_array_create(); +godot_byte_array GDAPI godot_byte_array_copy(godot_byte_array p_byte_array); +void GDAPI godot_byte_array_free(godot_byte_array p_byte_array); + +int GDAPI godot_byte_array_get_size(godot_byte_array p_byte_array); +unsigned char GDAPI godot_byte_array_get(godot_byte_array p_byte_array,int p_index); +void GDAPI godot_byte_array_set(godot_byte_array p_byte_array,int p_index,unsigned char p_value); +void GDAPI godot_byte_array_remove(godot_byte_array p_byte_array,int p_index); +void GDAPI godot_byte_array_clear(godot_byte_array p_byte_array); + +typedef void* godot_byte_array_lock; + +godot_byte_array_lock GDAPI godot_byte_array_get_lock(godot_byte_array p_byte_array); +unsigned char GDAPI *godot_byte_array_lock_get_pointer(godot_byte_array_lock p_byte_array_lock); +void GDAPI godot_byte_array_lock_free(godot_byte_array_lock p_byte_array_lock); + + +godot_image GDAPI godot_image_create_with_array(int p_width,int p_height,int p_format,int p_use_mipmaps,godot_array p_array); +godot_byte_array GDAPI godot_image_get_data(godot_image p_image); + + +////// IntArray + +typedef void* godot_int_array; + +godot_int_array GDAPI godot_int_array_create(); +godot_int_array GDAPI godot_int_array_copy(godot_int_array p_int_array); +void GDAPI godot_int_array_free(godot_int_array p_int_array); + +int GDAPI godot_int_array_get_size(godot_int_array p_int_array); +int GDAPI godot_int_array_get(godot_int_array p_int_array,int p_index); +void GDAPI godot_int_array_set(godot_int_array p_int_array,int p_index,int p_value); +void GDAPI godot_int_array_remove(godot_int_array p_int_array,int p_index); +void GDAPI godot_int_array_clear(godot_int_array p_int_array); + +typedef void* godot_int_array_lock; + +godot_int_array_lock GDAPI godot_int_array_get_lock(godot_int_array p_int_array); +int GDAPI *godot_int_array_lock_get_pointer(godot_int_array_lock p_int_array_lock); +void GDAPI godot_int_array_lock_free(godot_int_array_lock p_int_array_lock); + +////// RealArray + +typedef void* godot_real_array; + + +godot_real_array GDAPI godot_real_array_create(); +godot_real_array GDAPI godot_real_array_copy(godot_real_array p_real_array); +void GDAPI godot_real_array_free(godot_real_array p_real_array); + +int GDAPI godot_real_array_get_size(godot_real_array p_real_array); +float GDAPI godot_real_array_get(godot_real_array p_real_array,int p_index); +void GDAPI godot_real_array_set(godot_real_array p_real_array,int p_index,float p_value); +void GDAPI godot_real_array_remove(godot_real_array p_real_array,int p_index); +void GDAPI godot_real_array_clear(godot_real_array p_real_array); + +typedef void* godot_real_array_lock; + +godot_real_array_lock GDAPI godot_real_array_get_lock(godot_real_array p_real_array); +float GDAPI *godot_real_array_lock_get_pointer(godot_real_array_lock p_real_array_lock); +void GDAPI godot_real_array_lock_free(godot_real_array_lock p_real_array_lock); + + +////// StringArray + +typedef void* godot_string_array; + + +godot_string_array GDAPI godot_string_array_create(); +godot_string_array GDAPI godot_string_array_copy(godot_string_array p_string_array); +void GDAPI godot_string_array_free(godot_string_array p_string_array); + +int GDAPI godot_string_array_get_size(godot_string_array p_string_array); +int GDAPI godot_string_array_get(godot_string_array p_string_array,int p_index,unsigned char* p_string,int p_max_len); +void GDAPI godot_string_array_set(godot_string_array p_string_array,int p_index,unsigned char *p_string); +void GDAPI godot_string_array_remove(godot_string_array p_string_array,int p_index); +void GDAPI godot_string_array_clear(godot_string_array p_string_array); + +////// Vector2Array + +typedef void* godot_vector2_array; + +godot_vector2_array GDAPI godot_vector2_array_create(); +godot_vector2_array GDAPI godot_vector2_array_copy(godot_vector2_array p_vector2_array); +void GDAPI godot_vector2_array_free(godot_vector2_array p_vector2_array); + +int GDAPI godot_vector2_array_get_size(godot_vector2_array p_vector2_array); +int GDAPI godot_vector2_array_get_stride(godot_vector2_array p_vector2_array); +void GDAPI godot_vector2_array_get(godot_vector2_array p_vector2_array,int p_index,float* p_vector2); +void GDAPI godot_vector2_array_set(godot_vector2_array p_vector2_array,int p_index,float *p_vector2); +void GDAPI godot_vector2_array_remove(godot_vector2_array p_vector2_array,int p_index); +void GDAPI godot_vector2_array_clear(godot_vector2_array p_vector2_array); + + +typedef void* godot_vector2_array_lock; + +godot_vector2_array_lock GDAPI godot_vector2_array_get_lock(godot_vector2_array p_vector2_array); +float GDAPI *godot_vector2_array_lock_get_pointer(godot_vector2_array_lock p_vector2_array_lock); +void GDAPI godot_vector2_array_lock_free(godot_vector2_array_lock p_vector2_array_lock); + +////// Vector3Array + +typedef void* godot_vector3_array; + +godot_vector3_array GDAPI godot_vector3_array_create(); +godot_vector3_array GDAPI godot_vector3_array_copy(godot_vector3_array p_vector3_array); +void GDAPI godot_vector3_array_free(godot_vector3_array p_vector3_array); + +int GDAPI godot_vector3_array_get_size(godot_vector3_array p_vector3_array); +int GDAPI godot_vector3_array_get_stride(godot_vector3_array p_vector3_array); +void GDAPI godot_vector3_array_get(godot_vector3_array p_vector3_array,int p_index,float* p_vector3); +void GDAPI godot_vector3_array_set(godot_vector3_array p_vector3_array,int p_index,float *p_vector3); +void GDAPI godot_vector3_array_remove(godot_vector3_array p_vector3_array,int p_index); +void GDAPI godot_vector3_array_clear(godot_vector3_array p_vector3_array); + + +typedef void* godot_vector3_array_lock; + +godot_vector3_array_lock GDAPI godot_vector3_array_get_lock(godot_vector3_array p_vector3_array); +float GDAPI *godot_vector3_array_lock_get_pointer(godot_vector3_array_lock p_vector3_array_lock); +void GDAPI godot_vector3_array_lock_free(godot_vector3_array_lock p_vector3_array_lock); + +////// ColorArray + +typedef void* godot_color_array; + +godot_color_array GDAPI godot_color_array_create(); +godot_color_array GDAPI godot_color_array_copy(godot_color_array p_color_array); +void GDAPI godot_color_array_free(godot_color_array p_color_array); + +int GDAPI godot_color_array_get_size(godot_color_array p_color_array); +int GDAPI godot_color_array_get_stride(godot_color_array p_color_array); +void GDAPI godot_color_array_get(godot_color_array p_color_array,int p_index,float* p_color); +void GDAPI godot_color_array_set(godot_color_array p_color_array,int p_index,float *p_color); +void GDAPI godot_color_array_remove(godot_color_array p_color_array,int p_index); +void GDAPI godot_color_array_clear(godot_color_array p_color_array); + + +typedef void* godot_color_array_lock; + +godot_color_array_lock GDAPI godot_color_array_get_lock(godot_color_array p_color_array); +float GDAPI *godot_color_array_lock_get_pointer(godot_color_array_lock p_color_array_lock); +void GDAPI godot_color_array_lock_free(godot_color_array_lock p_color_array_lock); + + +////// Instance (forward declared) + +typedef void *godot_instance; + +////// Variant + +#define GODOT_VARIANT_NIL 0 +#define GODOT_VARIANT_BOOL 1 +#define GODOT_VARIANT_INT 2 +#define GODOT_VARIANT_REAL 3 +#define GODOT_VARIANT_STRING 4 +#define GODOT_VARIANT_VECTOR2 5 +#define GODOT_VARIANT_RECT2 6 +#define GODOT_VARIANT_VECTOR3 7 +#define GODOT_VARIANT_MATRIX32 8 +#define GODOT_VARIANT_PLANE 9 +#define GODOT_VARIANT_QUAT 10 +#define GODOT_VARIANT_AABB 11 +#define GODOT_VARIANT_MATRIX3 12 +#define GODOT_VARIANT_TRANSFORM 13 +#define GODOT_VARIANT_COLOR 14 +#define GODOT_VARIANT_IMAGE 15 +#define GODOT_VARIANT_NODE_PATH 16 +#define GODOT_VARIANT_RID 17 +#define GODOT_VARIANT_OBJECT 18 +#define GODOT_VARIANT_INPUT_EVENT 19 +#define GODOT_VARIANT_DICTIONARY 20 +#define GODOT_VARIANT_ARRAY 21 +#define GODOT_VARIANT_BYTE_ARRAY 22 +#define GODOT_VARIANT_INT_ARRAY 23 +#define GODOT_VARIANT_REAL_ARRAY 24 +#define GODOT_VARIANT_STRING_ARRAY 25 +#define GODOT_VARIANT_VECTOR2_ARRAY 26 +#define GODOT_VARIANT_VECTOR3_ARRAY 27 +#define GODOT_VARIANT_COLOR_ARRAY 28 +#define GODOT_VARIANT_MAX 29 + +godot_variant *godot_variant_new(); + +int GDAPI godot_variant_get_type(godot_variant p_variant); + +void GDAPI godot_variant_set_null(godot_variant p_variant); +void GDAPI godot_variant_set_bool(godot_variant p_variant,godot_bool p_bool); +void GDAPI godot_variant_set_int(godot_variant p_variant,int p_int); +void GDAPI godot_variant_set_float(godot_variant p_variant,int p_float); +void GDAPI godot_variant_set_string(godot_variant p_variant,char *p_string); +void GDAPI godot_variant_set_vector2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_rect2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_vector3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_matrix32(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_plane(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_aabb(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_matrix3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_transform(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_color(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_set_image(godot_variant p_variant,godot_image *p_image); +void GDAPI godot_variant_set_node_path(godot_variant p_variant,char *p_path); +void GDAPI godot_variant_set_rid(godot_variant p_variant,char *p_path); +void GDAPI godot_variant_set_instance(godot_variant p_variant,godot_instance p_instance); +void GDAPI godot_variant_set_input_event(godot_variant p_variant,godot_input_event p_instance); +void GDAPI godot_variant_set_dictionary(godot_variant p_variant,godot_dictionary p_dictionary); +void GDAPI godot_variant_set_array(godot_variant p_variant,godot_array p_array); +void GDAPI godot_variant_set_byte_array(godot_variant p_variant,godot_byte_array p_array); +void GDAPI godot_variant_set_int_array(godot_variant p_variant,godot_byte_array p_array); +void GDAPI godot_variant_set_string_array(godot_variant p_variant,godot_string_array p_array); +void GDAPI godot_variant_set_vector2_array(godot_variant p_variant,godot_vector2_array p_array); +void GDAPI godot_variant_set_vector3_array(godot_variant p_variant,godot_vector3_array p_array); +void GDAPI godot_variant_set_color_array(godot_variant p_variant,godot_color_array p_array); + +godot_bool GDAPI godot_variant_get_bool(godot_variant p_variant); +int GDAPI godot_variant_get_int(godot_variant p_variant); +float GDAPI godot_variant_get_float(godot_variant p_variant); +int GDAPI godot_variant_get_string(godot_variant p_variant,char *p_string,int p_bufsize); +void GDAPI godot_variant_get_vector2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_rect2(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_vector3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_matrix32(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_plane(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_aabb(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_matrix3(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_transform(godot_variant p_variant,float *p_elems); +void GDAPI godot_variant_get_color(godot_variant p_variant,float *p_elems); +godot_image GDAPI *godot_variant_get_image(godot_variant p_variant); +int GDAPI godot_variant_get_node_path(godot_variant p_variant,char *p_path, int p_bufsize); +godot_rid GDAPI godot_variant_get_rid(godot_variant p_variant); +godot_instance GDAPI godot_variant_get_instance(godot_variant p_variant); +void GDAPI godot_variant_get_input_event(godot_variant p_variant,godot_input_event); +void GDAPI godot_variant_get_dictionary(godot_variant p_variant,godot_dictionary); +godot_array GDAPI godot_variant_get_array(godot_variant p_variant); +godot_byte_array GDAPI godot_variant_get_byte_array(godot_variant p_variant); +godot_byte_array GDAPI godot_variant_get_int_array(godot_variant p_variant); +godot_string_array GDAPI godot_variant_get_string_array(godot_variant p_variant); +godot_vector2_array GDAPI godot_variant_get_vector2_array(godot_variant p_variant); +godot_vector3_array GDAPI godot_variant_get_vector3_array(godot_variant p_variant); +godot_color_array GDAPI godot_variant_get_color_array(godot_variant p_variant); + + +void GDAPI godot_variant_delete(godot_variant p_variant); + +////// Class +/// + +char GDAPI **godot_class_get_list(); //get list of classes in array to array of strings, must be freed, use godot_list_free() + +int GDAPI godot_class_get_base(char* p_class,char *p_base,int p_max_len); +int GDAPI godot_class_get_name(char* p_class,char *p_base,int p_max_len); + +char GDAPI **godot_class_get_method_list(char* p_class); //free with godot_list_free() +int GDAPI godot_class_method_get_argument_count(char* p_class,char *p_method); +int GDAPI godot_class_method_get_argument_type(char* p_class,char *p_method,int p_argument); +godot_variant GDAPI godot_class_method_get_argument_default_value(char* p_class,char *p_method,int p_argument); + +char GDAPI **godot_class_get_constant_list(char* p_class); //free with godot_list_free() +int GDAPI godot_class_constant_get_value(char* p_class,char *p_constant); + + +////// Instance + +typedef int godot_call_error; + +#define GODOT_CALL_OK +#define GODOT_CALL_ERROR_WRONG_ARGUMENTS +#define GODOT_CALL_ERROR_INVALID_INSTANCE + +godot_instance GDAPI godot_instance_new(char* p_class); +int GDAPI godot_instance_get_class(godot_instance p_instance,char* p_class,int p_max_len); + +typedef struct { + char *name; + int hint; + char *hint_string; + int usage; +} godot_property_info; + +godot_call_error GDAPI godot_instance_call(godot_instance p_instance, char* p_method, ...); +godot_call_error GDAPI godot_instance_call_ret(godot_instance p_instance, godot_variant r_return, char* p_method, ...); +godot_bool GDAPI godot_instance_set(godot_instance p_instance, char* p_prop,godot_variant p_value); +godot_variant GDAPI godot_instance_get(godot_instance p_instance, char* p_prop); + + +#define GODOT_PROPERTY_HINT_NONE 0 ///< no hint provided. +#define GODOT_PROPERTY_HINT_RANGE 1///< hint_text = "min,max,step,slider; //slider is optional" +#define GODOT_PROPERTY_HINT_EXP_RANGE 2///< hint_text = "min,max,step", exponential edit +#define GODOT_PROPERTY_HINT_ENUM 3///< hint_text= "val1,val2,val3,etc" +#define GODOT_PROPERTY_HINT_EXP_EASING 4/// exponential easing funciton (Math::ease) +#define GODOT_PROPERTY_HINT_LENGTH 5///< hint_text= "length" (as integer) +#define GODOT_PROPERTY_HINT_SPRITE_FRAME 6 +#define GODOT_PROPERTY_HINT_KEY_ACCEL 7///< hint_text= "length" (as integer) +#define GODOT_PROPERTY_HINT_FLAGS 8///< hint_text= "flag1,flag2,etc" (as bit flags) +#define GODOT_PROPERTY_HINT_ALL_FLAGS 9 +#define GODOT_PROPERTY_HINT_FILE 10 ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," +#define GODOT_PROPERTY_HINT_DIR 11 ///< a directort path must be passed +#define GODOT_PROPERTY_HINT_GLOBAL_FILE 12///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," +#define GODOT_PROPERTY_HINT_GLOBAL_DIR 13 ///< a directort path must be passed +#define GODOT_PROPERTY_HINT_RESOURCE_TYPE 14///< a resource object type +#define GODOT_PROPERTY_HINT_MULTILINE_TEXT 15///< used for string properties that can contain multiple lines +#define GODOT_PROPERTY_HINT_COLOR_NO_ALPHA 16///< used for ignoring alpha component when editing a color +#define GODOT_PROPERTY_HINT_IMAGE_COMPRESS_LOSSY 17 +#define GODOT_PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS 18 +#define GODOT_PROPERTY_HINT_OBJECT_ID 19 + + +#define GODOT_PROPERTY_USAGE_STORAGE 1 +#define GODOT_PROPERTY_USAGE_EDITOR 2 +#define GODOT_PROPERTY_USAGE_NETWORK 4 +#define GODOT_PROPERTY_USAGE_EDITOR_HELPER 8 +#define GODOT_PROPERTY_USAGE_CHECKABLE 16 //used for editing global variables +#define GODOT_PROPERTY_USAGE_CHECKED 32 //used for editing global variables +#define GODOT_PROPERTY_USAGE_INTERNATIONALIZED 64 //hint for internationalized strings +#define GODOT_PROPERTY_USAGE_BUNDLE 128 //used for optimized bundles +#define GODOT_PROPERTY_USAGE_CATEGORY 256 +#define GODOT_PROPERTY_USAGE_STORE_IF_NONZERO 512 //only store if nonzero +#define GODOT_PROPERTY_USAGE_STORE_IF_NONONE 1024 //only store if false +#define GODOT_PROPERTY_USAGE_NO_INSTANCE_STATE 2048 +#define GODOT_PROPERTY_USAGE_RESTART_IF_CHANGED 4096 +#define GODOT_PROPERTY_USAGE_SCRIPT_VARIABLE 8192 +#define GODOT_PROPERTY_USAGE_STORE_IF_NULL 16384 +#define GODOT_PROPERTY_USAGE_ANIMATE_AS_TRIGGER 32768 + +#define GODOT_PROPERTY_USAGE_DEFAULT GODOT_PROPERTY_USAGE_STORAGE|GODOT_PROPERTY_USAGE_EDITOR|GODOT_PROPERTY_USAGE_NETWORK +#define GODOT_PROPERTY_USAGE_DEFAULT_INTL GODOT_PROPERTY_USAGE_STORAGE|GODOT_PROPERTY_USAGE_EDITOR|GODOT_PROPERTY_USAGE_NETWORK|GODOT_PROPERTY_USAGE_INTERNATIONALIZED +#define GODOT_PROPERTY_USAGE_NOEDITOR GODOT_PROPERTY_USAGE_STORAGE|GODOT_PROPERTY_USAGE_NETWORK + + +godot_property_info GDAPI **godot_instance_get_property_list(godot_instance p_instance); +void GDAPI godot_instance_free_property_list(godot_instance p_instance,godot_property_info** p_list); + + + +void GDAPI godot_list_free(char **p_name); //helper to free all the class list + + +////// Script API + +typedef void* (godot_script_instance_func)(godot_instance); //passed an instance, return a pointer to your userdata +typedef void (godot_script_free_func)(godot_instance,void*); //passed an instance, please free your userdata + +void GDAPI godot_script_register(char* p_base,char* p_name,godot_script_instance_func p_instance_func,godot_script_free_func p_free_func); +void GDAPI godot_script_unregister(char* p_name); + +typedef GDAPI godot_variant (godot_script_func)(godot_instance,void*,godot_variant*,int); //instance,userdata,arguments,argument count. Return something or NULL. Arguments must not be freed. + + +void GDAPI godot_script_add_function(char* p_name,char* p_function_name,godot_script_func p_func); +void GDAPI godot_script_add_validated_function(char* p_name,char* p_function_name,godot_script_func p_func,int* p_arg_types,int p_arg_count,godot_variant* p_default_args,int p_default_arg_count); + +typedef void (godot_set_property_func)(godot_instance,void*,godot_variant); //instance,userdata,value. Value must not be freed. +typedef godot_variant (godot_get_property_func)(godot_instance,void*); //instance,userdata. Return a value or NULL. + +void GDAPI godot_script_add_property(char* p_name,char* p_path,godot_set_property_func p_set_func,godot_get_property_func p_get_func); +void GDAPI godot_script_add_listed_property(char* p_name,char* p_path,godot_set_property_func p_set_func,godot_get_property_func p_get_func,int p_type,int p_hint,char* p_hint_string,int p_usage); + + +////// System Functions + +//using these will help Godot track how much memory is in use in debug mode +void GDAPI *godot_alloc(int p_bytes); +void GDAPI *godot_realloc(void* p_ptr,int p_bytes); +void GDAPI godot_free(void* p_ptr); + + +#ifdef __cplusplus +} +#endif + + +#endif // GODOT_C_H diff --git a/modules/gdscript/gd_pretty_print.h b/modules/cscript/register_types.cpp index 0106d873d..d2101bbd4 100644 --- a/modules/gdscript/gd_pretty_print.h +++ b/modules/cscript/register_types.cpp @@ -1,11 +1,11 @@ /*************************************************************************/ -/* gd_pretty_print.h */ +/* register_types.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -26,15 +26,12 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GD_PRETTY_PRINT_H -#define GD_PRETTY_PRINT_H +#include "register_types.h" +void register_cscript_types() { +} +void unregister_cscript_types() { -class GDPrettyPrint { -public: - GDPrettyPrint(); -}; - -#endif // GD_PRETTY_PRINT_H +} diff --git a/modules/cscript/register_types.h b/modules/cscript/register_types.h new file mode 100644 index 000000000..6614ee3a1 --- /dev/null +++ b/modules/cscript/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_cscript_types(); +void unregister_cscript_types(); diff --git a/modules/dds/SCsub b/modules/dds/SCsub new file mode 100644 index 000000000..3d92ff02d --- /dev/null +++ b/modules/dds/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_dds = env_modules.Clone() + +env_dds.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/dds/config.py b/modules/dds/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/dds/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/dds/register_types.cpp b/modules/dds/register_types.cpp new file mode 100644 index 000000000..917305f54 --- /dev/null +++ b/modules/dds/register_types.cpp @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "texture_loader_dds.h" + +static ResourceFormatDDS *resource_loader_dds = NULL; + +void register_dds_types() { + + resource_loader_dds = memnew( ResourceFormatDDS ); + ResourceLoader::add_resource_format_loader(resource_loader_dds); +} + +void unregister_dds_types() { + + memdelete(resource_loader_dds); +} diff --git a/modules/ik/register_types.h b/modules/dds/register_types.h index 828917ade..69f47006e 100644 --- a/modules/ik/register_types.h +++ b/modules/dds/register_types.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -26,5 +26,5 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -void register_ik_types(); -void unregister_ik_types(); +void register_dds_types(); +void unregister_dds_types(); diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp new file mode 100644 index 000000000..5295183c3 --- /dev/null +++ b/modules/dds/texture_loader_dds.cpp @@ -0,0 +1,484 @@ +/*************************************************************************/ +/* texture_loader_dds.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "texture_loader_dds.h" +#include "os/file_access.h" + + +enum { + DDS_MAGIC=0x20534444, + DDSD_CAPS=0x00000001, + DDSD_PIXELFORMAT=0x00001000, + DDSD_PITCH=0x00000008, + DDSD_LINEARSIZE=0x00080000, + DDSD_MIPMAPCOUNT=0x00020000, + DDPF_FOURCC=0x00000004, + DDPF_ALPHAPIXELS=0x00000001, + DDPF_INDEXED=0x00000020, + DDPF_RGB=0x00000040, +}; + +enum DDSFormat { + + DDS_DXT1, + DDS_DXT3, + DDS_DXT5, + DDS_ATI1, + DDS_ATI2, + DDS_BGRA8, + DDS_BGR8, + DDS_RGBA8, //flipped in dds + DDS_RGB8, //flipped in dds + DDS_BGR5A1, + DDS_BGR565, + DDS_BGR10A2, + DDS_INDEXED, + DDS_LUMINANCE, + DDS_LUMINANCE_ALPHA, + DDS_MAX +}; + +struct DDSFormatInfo { + const char *name; + bool compressed; + bool palette; + uint32_t divisor; + uint32_t block_size; + Image::Format format; +}; + + +static const DDSFormatInfo dds_format_info[DDS_MAX]={ + {"DXT1",true,false,4,8,Image::FORMAT_DXT1}, + {"DXT3",true,false,4,16,Image::FORMAT_DXT3}, + {"DXT5",true,false,4,16,Image::FORMAT_DXT5}, + {"ATI1",true,false,4,8,Image::FORMAT_ATI1}, + {"ATI2",true,false,4,16,Image::FORMAT_ATI2}, + {"BGRA8",false,false,1,4,Image::FORMAT_RGBA8}, + {"BGR8",false,false,1,3,Image::FORMAT_RGB8}, + {"RGBA8",false,false,1,4,Image::FORMAT_RGBA8}, + {"RGB8",false,false,1,3,Image::FORMAT_RGB8}, + {"BGR5A1",false,false,1,2,Image::FORMAT_RGBA8}, + {"BGR565",false,false,1,2,Image::FORMAT_RGB8}, + {"BGR10A2",false,false,1,4,Image::FORMAT_RGBA8}, + {"GRAYSCALE",false,false,1,1,Image::FORMAT_L8}, + {"GRAYSCALE_ALPHA",false,false,1,2,Image::FORMAT_LA8} +}; + + +RES ResourceFormatDDS::load(const String &p_path, const String& p_original_path, Error *r_error) { + + if (r_error) + *r_error=ERR_CANT_OPEN; + + Error err; + FileAccess *f = FileAccess::open(p_path,FileAccess::READ,&err); + if (!f) + return RES(); + + FileAccessRef fref(f); + if (r_error) + *r_error=ERR_FILE_CORRUPT; + + ERR_EXPLAIN("Unable to open DDS texture file: "+p_path); + ERR_FAIL_COND_V(err!=OK,RES()); + + uint32_t magic = f->get_32(); + uint32_t hsize = f->get_32(); + uint32_t flags = f->get_32(); + uint32_t width = f->get_32(); + uint32_t height = f->get_32(); + uint32_t pitch = f->get_32(); + uint32_t depth = f->get_32(); + uint32_t mipmaps = f->get_32(); + + //skip 11 + for(int i=0;i<11;i++) + f->get_32(); + + //validate + + if (magic!=DDS_MAGIC || hsize!=124 || !(flags&DDSD_PIXELFORMAT) || !(flags&DDSD_CAPS)) { + + ERR_EXPLAIN("Invalid or Unsupported DDS texture file: "+p_path); + ERR_FAIL_V(RES()); + } + + + uint32_t format_size = f->get_32(); + uint32_t format_flags = f->get_32(); + uint32_t format_fourcc = f->get_32(); + uint32_t format_rgb_bits = f->get_32(); + uint32_t format_red_mask = f->get_32(); + uint32_t format_green_mask = f->get_32(); + uint32_t format_blue_mask = f->get_32(); + uint32_t format_alpha_mask = f->get_32(); + + uint32_t caps_1 = f->get_32(); + uint32_t caps_2 = f->get_32(); + uint32_t caps_ddsx = f->get_32(); + + //reserved skip + f->get_32(); + f->get_32(); + + /*print_line("DDS width: "+itos(width)); + print_line("DDS height: "+itos(height)); + print_line("DDS mipmaps: "+itos(mipmaps));*/ + + //printf("fourcc: %x fflags: %x, rgbbits: %x, fsize: %x\n",format_fourcc,format_flags,format_rgb_bits,format_size); + //printf("rmask: %x gmask: %x, bmask: %x, amask: %x\n",format_red_mask,format_green_mask,format_blue_mask,format_alpha_mask); + + //must avoid this later + while(f->get_pos()<128) + f->get_8(); + + + DDSFormat dds_format; + + if (format_flags&DDPF_FOURCC && format_fourcc==0x31545844) { //'1TXD' + + dds_format=DDS_DXT1; + } else if (format_flags&DDPF_FOURCC && format_fourcc==0x33545844) { //'3TXD' + + dds_format=DDS_DXT3; + + } else if (format_flags&DDPF_FOURCC && format_fourcc==0x35545844) { //'5TXD' + + dds_format=DDS_DXT5; + } else if (format_flags&DDPF_FOURCC && format_fourcc==0x31495441) { //'1ITA' + + dds_format=DDS_ATI1; + } else if (format_flags&DDPF_FOURCC && format_fourcc==0x32495441) { //'2ITA' + + dds_format=DDS_ATI2; + + } else if (format_flags&DDPF_RGB && format_flags&DDPF_ALPHAPIXELS && format_rgb_bits==32 && format_red_mask==0xff0000 && format_green_mask==0xff00 && format_blue_mask==0xff && format_alpha_mask==0xff000000) { + + dds_format=DDS_BGRA8; + } else if (format_flags&DDPF_RGB && !(format_flags&DDPF_ALPHAPIXELS ) && format_rgb_bits==24 && format_red_mask==0xff0000 && format_green_mask==0xff00 && format_blue_mask==0xff) { + + dds_format=DDS_BGR8; + } else if (format_flags&DDPF_RGB && format_flags&DDPF_ALPHAPIXELS && format_rgb_bits==32 && format_red_mask==0xff && format_green_mask==0xff00 && format_blue_mask==0xff0000 && format_alpha_mask==0xff000000) { + + dds_format=DDS_RGBA8; + } else if (format_flags&DDPF_RGB && !(format_flags&DDPF_ALPHAPIXELS ) && format_rgb_bits==24 && format_red_mask==0xff && format_green_mask==0xff00 && format_blue_mask==0xff0000) { + + dds_format=DDS_RGB8; + + } else if (format_flags&DDPF_RGB && format_flags&DDPF_ALPHAPIXELS && format_rgb_bits==16 && format_red_mask==0x00007c00 && format_green_mask==0x000003e0 && format_blue_mask==0x0000001f && format_alpha_mask==0x00008000) { + + dds_format=DDS_BGR5A1; + } else if (format_flags&DDPF_RGB && format_flags&DDPF_ALPHAPIXELS && format_rgb_bits==32 && format_red_mask==0x3ff00000 && format_green_mask==0xffc00 && format_blue_mask==0x3ff && format_alpha_mask==0xc0000000) { + + dds_format=DDS_BGR10A2; + } else if (format_flags&DDPF_RGB && !(format_flags&DDPF_ALPHAPIXELS) && format_rgb_bits==16 && format_red_mask==0x0000f800 && format_green_mask==0x000007e0 && format_blue_mask==0x0000001f) { + + dds_format=DDS_BGR565; + } else if (!(format_flags&DDPF_ALPHAPIXELS) && format_rgb_bits==8 && format_red_mask==0xff && format_green_mask==0xff && format_blue_mask==0xff) { + + dds_format=DDS_LUMINANCE; + } else if ((format_flags&DDPF_ALPHAPIXELS) && format_rgb_bits==16 && format_red_mask==0xff && format_green_mask==0xff && format_blue_mask==0xff && format_alpha_mask==0xff00) { + + dds_format=DDS_LUMINANCE_ALPHA; + } else if (format_flags&DDPF_INDEXED && format_rgb_bits==8) { + + dds_format=DDS_BGR565; + } else { + + printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n",format_fourcc,format_flags,format_rgb_bits,format_red_mask,format_green_mask,format_blue_mask,format_alpha_mask); + ERR_EXPLAIN("Unrecognized or Unsupported color layout in DDS: "+p_path); + + ERR_FAIL_V(RES()); + + } + + if (!(flags&DDSD_MIPMAPCOUNT)) + mipmaps=1; + + //print_line("found format: "+String(dds_format_info[dds_format].name)); + + PoolVector<uint8_t> src_data; + + const DDSFormatInfo &info=dds_format_info[dds_format]; + uint32_t w = width; + uint32_t h = height; + + + if (info.compressed) { + //compressed bc + + uint32_t size = MAX( info.divisor, w )/info.divisor * MAX( info.divisor, h )/info.divisor * info.block_size; + ERR_FAIL_COND_V( size!=pitch, RES() ); + ERR_FAIL_COND_V( !(flags&DDSD_LINEARSIZE), RES() ); + + for(uint32_t i=1;i<mipmaps;i++) { + + w=MAX(1,w>>1); + h=MAX(1,h>>1); + uint32_t bsize = MAX( info.divisor, w )/info.divisor * MAX( info.divisor, h )/info.divisor * info.block_size; + //printf("%i x %i - block: %i\n",w,h,bsize); + size+= bsize; + } + + src_data.resize(size); + PoolVector<uint8_t>::Write wb = src_data.write(); + f->get_buffer(wb.ptr(),size); + wb=PoolVector<uint8_t>::Write(); + + } else if (info.palette) { + + //indexed + ERR_FAIL_COND_V( !(flags&DDSD_PITCH), RES()); + ERR_FAIL_COND_V( format_rgb_bits!=8, RES() ); + + uint32_t size = pitch*height; + ERR_FAIL_COND_V( size != width*height * info.block_size, RES()); + + uint8_t pallete[256*4]; + f->get_buffer(pallete,256*4); + + int colsize=3; + for(int i=0;i<256;i++) { + + if (pallete[i*4+3]<255) + colsize=4; + } + + int w = width; + int h = height; + + for(uint32_t i=1;i<mipmaps;i++) { + + w=(w+1)>>1; + h=(h+1)>>1; + size+= w*h*info.block_size; + } + + src_data.resize(size + 256*colsize ); + PoolVector<uint8_t>::Write wb = src_data.write(); + f->get_buffer(wb.ptr(),size); + + for(int i=0;i<256;i++) { + + int dst_ofs = size+i*colsize; + int src_ofs = i*4; + wb[dst_ofs+0]=pallete[src_ofs+2]; + wb[dst_ofs+1]=pallete[src_ofs+1]; + wb[dst_ofs+2]=pallete[src_ofs+0]; + if (colsize==4) + wb[dst_ofs+3]=pallete[src_ofs+3]; + } + + + wb=PoolVector<uint8_t>::Write(); + } else { + //uncompressed generic... + + uint32_t size = width*height*info.block_size; + + + for(uint32_t i=1;i<mipmaps;i++) { + + w=(w+1)>>1; + h=(h+1)>>1; + size+= w*h*info.block_size; + } + + if (dds_format==DDS_BGR565) + size=size*3/2; + else if (dds_format==DDS_BGR5A1) + size=size*2; + + src_data.resize(size); + PoolVector<uint8_t>::Write wb = src_data.write(); + f->get_buffer(wb.ptr(),size); + + + switch(dds_format) { + + case DDS_BGR5A1: { + + // TO RGBA + int colcount = size/4; + + for(int i=colcount-1;i>=0;i--) { + + int src_ofs = i*2; + int dst_ofs = i*4; + + uint8_t a=wb[src_ofs+1]&0x80; + uint8_t b= wb[src_ofs]&0x1F; + uint8_t g= (wb[src_ofs]>>5) | ((wb[src_ofs+1]&0x3)<<3); + uint8_t r= (wb[src_ofs+1]>>2)&0x1F; + wb[dst_ofs+0]=r<<3; + wb[dst_ofs+1]=g<<3; + wb[dst_ofs+2]=b<<3; + wb[dst_ofs+3]=a?255:0; + } + } break; + case DDS_BGR565: { + + int colcount = size/3; + + for(int i=colcount-1;i>=0;i--) { + + int src_ofs = i*2; + int dst_ofs = i*3; + + uint8_t b= wb[src_ofs]&0x1F; + uint8_t g= (wb[src_ofs]>>5) | ((wb[src_ofs+1]&0x7)<<3); + uint8_t r= wb[src_ofs+1]>>3; + wb[dst_ofs+0]=r<<3; + wb[dst_ofs+1]=g<<2; + wb[dst_ofs+2]=b<<3;//b<<3; + + } + + } break; + case DDS_BGR10A2: { + + // TO RGBA + int colcount = size/4; + + for(int i=colcount-1;i>=0;i--) { + + int ofs = i*4; + + uint32_t w32 = uint32_t(wb[ofs+0]) | (uint32_t(wb[ofs+1])<<8) | (uint32_t(wb[ofs+2])<<16) | (uint32_t(wb[ofs+3])<<24); + + uint8_t a= (w32&0xc0000000) >> 24; + uint8_t r= (w32&0x3ff00000) >> 22; + uint8_t g= (w32&0xffc00) >> 12; + uint8_t b= (w32&0x3ff) >> 2; + + + wb[ofs+0]=r; + wb[ofs+1]=g; + wb[ofs+2]=b; + wb[ofs+3]=a==0xc0 ? 255 : a; //0xc0 should be opaque + + } + } break; + case DDS_BGRA8: { + + int colcount = size/4; + + for(int i=0;i<colcount;i++) { + + SWAP( wb[i*4+0],wb[i*4+2] ); + } + + } break; + case DDS_BGR8: { + + int colcount = size/3; + + for(int i=0;i<colcount;i++) { + + SWAP( wb[i*3+0],wb[i*3+2] ); + } + } break; + case DDS_RGBA8: { + + /* do nothing either + int colcount = size/4; + + for(int i=0;i<colcount;i++) { + + uint8_t r = wb[i*4+1]; + uint8_t g = wb[i*4+2]; + uint8_t b = wb[i*4+3]; + uint8_t a = wb[i*4+0]; + + wb[i*4+0]=r; + wb[i*4+1]=g; + wb[i*4+2]=b; + wb[i*4+3]=a; + } + */ + } break; + case DDS_RGB8: { + + // do nothing + /* + int colcount = size/3; + + for(int i=0;i<colcount;i++) { + + SWAP( wb[i*3+0],wb[i*3+2] ); + }*/ + } break; + case DDS_LUMINANCE: { + + // do nothing i guess? + + } break; + case DDS_LUMINANCE_ALPHA: { + + // do nothing i guess? + + } break; + + default: {} + + } + + wb=PoolVector<uint8_t>::Write(); + } + + + Image img(width,height,mipmaps-1,info.format,src_data); + + Ref<ImageTexture> texture = memnew( ImageTexture ); + texture->create_from_image(img); + + if (r_error) + *r_error=OK; + + + return texture; + +} + +void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("dds"); +} + +bool ResourceFormatDDS::handles_type(const String& p_type) const { + + return ClassDB::is_parent_class(p_type,"Texture"); +} + +String ResourceFormatDDS::get_resource_type(const String &p_path) const { + + if (p_path.get_extension().to_lower()=="dds") + return "ImageTexture"; + return ""; +} diff --git a/modules/dds/texture_loader_dds.h b/modules/dds/texture_loader_dds.h new file mode 100644 index 000000000..d09af680c --- /dev/null +++ b/modules/dds/texture_loader_dds.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* texture_loader_dds.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef TEXTURE_LOADER_DDS_H +#define TEXTURE_LOADER_DDS_H + +#include "scene/resources/texture.h" +#include "io/resource_loader.h" + +class ResourceFormatDDS : public ResourceFormatLoader{ +public: + + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + + virtual ~ResourceFormatDDS() {} +}; + +#endif // TEXTURE_LOADER_DDS_H diff --git a/modules/enet/SCsub b/modules/enet/SCsub new file mode 100644 index 000000000..fb22d1cff --- /dev/null +++ b/modules/enet/SCsub @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +# Thirdparty source files + +env_enet = env_modules.Clone() + +if (env['builtin_enet'] != 'no'): + thirdparty_dir = "#thirdparty/enet/" + thirdparty_sources = [ + "callbacks.c", + "compress.c", + "host.c", + "list.c", + "packet.c", + "peer.c", + "protocol.c", + "unix.c", + "win32.c", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_enet.add_source_files(env.modules_sources, thirdparty_sources) + env_enet.Append(CPPPATH=[thirdparty_dir]) + +env_enet.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/enet/config.py b/modules/enet/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/enet/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp new file mode 100644 index 000000000..d10634e9e --- /dev/null +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -0,0 +1,691 @@ +/*************************************************************************/ +/* networked_multiplayer_enet.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "os/os.h" +#include "io/marshalls.h" +#include "networked_multiplayer_enet.h" + +void NetworkedMultiplayerENet::set_transfer_mode(TransferMode p_mode) { + + transfer_mode=p_mode; +} + +void NetworkedMultiplayerENet::set_target_peer(int p_peer){ + + target_peer=p_peer; +} + +int NetworkedMultiplayerENet::get_packet_peer() const{ + + ERR_FAIL_COND_V(!active,1); + ERR_FAIL_COND_V(incoming_packets.size()==0,1); + + return incoming_packets.front()->get().from; + +} + +Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int p_in_bandwidth, int p_out_bandwidth){ + + ERR_FAIL_COND_V(active,ERR_ALREADY_IN_USE); + + ENetAddress address; + address.host = bind_ip; + + address.port = p_port; + + host = enet_host_create (& address /* the address to bind the server host to */, + p_max_clients /* allow up to 32 clients and/or outgoing connections */, + SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, + p_in_bandwidth /* assume any amount of incoming bandwidth */, + p_out_bandwidth /* assume any amount of outgoing bandwidth */); + + ERR_FAIL_COND_V(!host,ERR_CANT_CREATE); + + _setup_compressor(); + active=true; + server=true; + refuse_connections=false; + unique_id=1; + connection_status=CONNECTION_CONNECTED; + return OK; +} +Error NetworkedMultiplayerENet::create_client(const IP_Address& p_ip, int p_port, int p_in_bandwidth, int p_out_bandwidth){ + + ERR_FAIL_COND_V(active,ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(!p_ip.is_ipv4(), ERR_INVALID_PARAMETER); + + host = enet_host_create (NULL /* create a client host */, + 1 /* only allow 1 outgoing connection */, + SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, + p_in_bandwidth /* 56K modem with 56 Kbps downstream bandwidth */, + p_out_bandwidth /* 56K modem with 14 Kbps upstream bandwidth */); + + ERR_FAIL_COND_V(!host,ERR_CANT_CREATE); + + + _setup_compressor(); + + ENetAddress address; + address.host=*((uint32_t *)p_ip.get_ipv4()); + address.port=p_port; + + //enet_address_set_host (& address, "localhost"); + //address.port = p_port; + + unique_id=_gen_unique_id(); + + /* Initiate the connection, allocating the enough channels */ + ENetPeer *peer = enet_host_connect (host, & address, SYSCH_MAX, unique_id); + + if (peer == NULL) { + enet_host_destroy(host); + ERR_FAIL_COND_V(!peer,ERR_CANT_CREATE); + } + + //technically safe to ignore the peer or anything else. + + connection_status=CONNECTION_CONNECTING; + active=true; + server=false; + refuse_connections=false; + + return OK; +} + +void NetworkedMultiplayerENet::poll(){ + + ERR_FAIL_COND(!active); + + _pop_current_packet(); + + ENetEvent event; + /* Wait up to 1000 milliseconds for an event. */ + while (true) { + + if (!host || !active) //might have been disconnected while emitting a notification + return; + + int ret = enet_host_service (host, & event, 1); + + if (ret<0) { + //error, do something? + break; + } else if (ret==0) { + break; + } + + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: { + /* Store any relevant client information here. */ + + if (server && refuse_connections) { + enet_peer_reset(event.peer); + break; + } + + IP_Address ip; + ip.set_ipv4((uint8_t *)&(event.peer -> address.host)); + + int *new_id = memnew( int ); + *new_id = event.data; + + if (*new_id==0) { //data zero is sent by server (enet won't let you configure this). Server is always 1 + *new_id=1; + } + + event.peer->data=new_id; + + peer_map[*new_id]=event.peer; + + connection_status=CONNECTION_CONNECTED; //if connecting, this means it connected t something! + + emit_signal("peer_connected",*new_id); + + if (server) { + //someone connected, let it know of all the peers available + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (E->key()==*new_id) + continue; + //send existing peers to new peer + ENetPacket * packet = enet_packet_create (NULL,8,ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_ADD_PEER,&packet->data[0]); + encode_uint32(E->key(),&packet->data[4]); + enet_peer_send(event.peer,SYSCH_CONFIG,packet); + //send the new peer to existing peers + packet = enet_packet_create (NULL,8,ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_ADD_PEER,&packet->data[0]); + encode_uint32(*new_id,&packet->data[4]); + enet_peer_send(E->get(),SYSCH_CONFIG,packet); + } + } else { + + emit_signal("connection_succeeded"); + } + + } break; + case ENET_EVENT_TYPE_DISCONNECT: { + + /* Reset the peer's client information. */ + + int *id = (int*)event.peer -> data; + + + + if (!id) { + if (!server) { + emit_signal("connection_failed"); + } + } else { + + if (server) { + //someone disconnected, let it know to everyone else + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (E->key()==*id) + continue; + //send the new peer to existing peers + ENetPacket* packet = enet_packet_create (NULL,8,ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_REMOVE_PEER,&packet->data[0]); + encode_uint32(*id,&packet->data[4]); + enet_peer_send(E->get(),SYSCH_CONFIG,packet); + } + } else if (!server) { + emit_signal("server_disconnected"); + close_connection(); + return; + } + + emit_signal("peer_disconnected",*id); + peer_map.erase(*id); + memdelete( id ); + + } + + + } break; + case ENET_EVENT_TYPE_RECEIVE: { + + + if (event.channelID==SYSCH_CONFIG) { + //some config message + ERR_CONTINUE( event.packet->dataLength < 8); + + // Only server can send config messages + ERR_CONTINUE( server ); + + int msg = decode_uint32(&event.packet->data[0]); + int id = decode_uint32(&event.packet->data[4]); + + switch(msg) { + case SYSMSG_ADD_PEER: { + + peer_map[id]=NULL; + emit_signal("peer_connected",id); + + } break; + case SYSMSG_REMOVE_PEER: { + + peer_map.erase(id); + emit_signal("peer_disconnected",id); + } break; + } + + enet_packet_destroy(event.packet); + } else if (event.channelID < SYSCH_MAX){ + + Packet packet; + packet.packet = event.packet; + + uint32_t *id = (uint32_t*)event.peer->data; + + ERR_CONTINUE(event.packet->dataLength<12) + + + uint32_t source = decode_uint32(&event.packet->data[0]); + int target = decode_uint32(&event.packet->data[4]); + uint32_t flags = decode_uint32(&event.packet->data[8]); + + packet.from=source; + + if (server) { + // Someone is cheating and trying to fake the source! + ERR_CONTINUE(source!=*id); + + packet.from=*id; + + if (target==0) { + //re-send the everyone but sender :| + + incoming_packets.push_back(packet); + //and make copies for sending + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (uint32_t(E->key())==source) //do not resend to self + continue; + + ENetPacket* packet2 = enet_packet_create (packet.packet->data,packet.packet->dataLength,flags); + + enet_peer_send(E->get(),event.channelID,packet2); + } + + } else if (target<0) { + //to all but one + + //and make copies for sending + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + + if (uint32_t(E->key())==source || E->key()==-target) //do not resend to self, also do not send to excluded + continue; + + ENetPacket* packet2 = enet_packet_create (packet.packet->data,packet.packet->dataLength,flags); + + enet_peer_send(E->get(),event.channelID,packet2); + } + + if (-target != 1) { + //server is not excluded + incoming_packets.push_back(packet); + } else { + //server is excluded, erase packet + enet_packet_destroy(packet.packet); + } + + } else if (target==1) { + //to myself and only myself + incoming_packets.push_back(packet); + } else { + //to someone else, specifically + ERR_CONTINUE(!peer_map.has(target)); + enet_peer_send(peer_map[target],event.channelID,packet.packet); + } + } else { + + incoming_packets.push_back(packet); + } + + + //destroy packet later.. + } else { + ERR_CONTINUE(true); + } + + + }break; + case ENET_EVENT_TYPE_NONE: { + //do nothing + } break; + } + } +} + +bool NetworkedMultiplayerENet::is_server() const { + ERR_FAIL_COND_V(!active,false); + + return server; +} + +void NetworkedMultiplayerENet::close_connection() { + + if (!active) + return; + + _pop_current_packet(); + + bool peers_disconnected=false; + for (Map<int,ENetPeer*>::Element *E=peer_map.front();E;E=E->next()) { + if (E->get()) { + enet_peer_disconnect_now(E->get(),unique_id); + peers_disconnected=true; + } + } + + if (peers_disconnected) { + enet_host_flush(host); + OS::get_singleton()->delay_usec(100); //wait 100ms for disconnection packets to send + + } + + enet_host_destroy(host); + active=false; + incoming_packets.clear(); + unique_id=1; //server is 1 + connection_status=CONNECTION_DISCONNECTED; +} + +int NetworkedMultiplayerENet::get_available_packet_count() const { + + return incoming_packets.size(); +} +Error NetworkedMultiplayerENet::get_packet(const uint8_t **r_buffer,int &r_buffer_size) const{ + + ERR_FAIL_COND_V(incoming_packets.size()==0,ERR_UNAVAILABLE); + + _pop_current_packet(); + + current_packet = incoming_packets.front()->get(); + incoming_packets.pop_front(); + + *r_buffer=(const uint8_t*)(¤t_packet.packet->data[12]); + r_buffer_size=current_packet.packet->dataLength-12; + + return OK; +} +Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer,int p_buffer_size){ + + ERR_FAIL_COND_V(!active,ERR_UNCONFIGURED); + ERR_FAIL_COND_V(connection_status!=CONNECTION_CONNECTED,ERR_UNCONFIGURED); + + int packet_flags=0; + int channel=SYSCH_RELIABLE; + + switch(transfer_mode) { + case TRANSFER_MODE_UNRELIABLE: { + packet_flags=ENET_PACKET_FLAG_UNSEQUENCED; + channel=SYSCH_UNRELIABLE; + } break; + case TRANSFER_MODE_UNRELIABLE_ORDERED: { + packet_flags=0; + channel=SYSCH_UNRELIABLE; + } break; + case TRANSFER_MODE_RELIABLE: { + packet_flags=ENET_PACKET_FLAG_RELIABLE; + channel=SYSCH_RELIABLE; + } break; + } + + Map<int,ENetPeer*>::Element *E=NULL; + + if (target_peer!=0) { + + E = peer_map.find(ABS(target_peer)); + if (!E) { + ERR_EXPLAIN("Invalid Target Peer: "+itos(target_peer)); + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + } + + ENetPacket * packet = enet_packet_create (NULL,p_buffer_size+12,packet_flags); + encode_uint32(unique_id,&packet->data[0]); //source ID + encode_uint32(target_peer,&packet->data[4]); //dest ID + encode_uint32(packet_flags,&packet->data[8]); //dest ID + copymem(&packet->data[12],p_buffer,p_buffer_size); + + if (server) { + + if (target_peer==0) { + enet_host_broadcast(host,channel,packet); + } else if (target_peer<0) { + //send to all but one + //and make copies for sending + + int exclude=-target_peer; + + for (Map<int,ENetPeer*>::Element *F=peer_map.front();F;F=F->next()) { + + if (F->key()==exclude) // exclude packet + continue; + + ENetPacket* packet2 = enet_packet_create (packet->data,packet->dataLength,packet_flags); + + enet_peer_send(F->get(),channel,packet2); + } + + enet_packet_destroy(packet); //original packet no longer needed + } else { + enet_peer_send (E->get(), channel, packet); + + } + } else { + + ERR_FAIL_COND_V(!peer_map.has(1),ERR_BUG); + enet_peer_send (peer_map[1], channel, packet); //send to server for broadcast.. + + } + + enet_host_flush(host); + + return OK; +} + +int NetworkedMultiplayerENet::get_max_packet_size() const { + + return 1<<24; //anything is good +} + +void NetworkedMultiplayerENet::_pop_current_packet() const { + + if (current_packet.packet) { + enet_packet_destroy(current_packet.packet); + current_packet.packet=NULL; + current_packet.from=0; + } + +} + +NetworkedMultiplayerPeer::ConnectionStatus NetworkedMultiplayerENet::get_connection_status() const { + + return connection_status; +} + +uint32_t NetworkedMultiplayerENet::_gen_unique_id() const { + + uint32_t hash = 0; + + while (hash==0 || hash==1) { + + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_ticks_usec() ); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_unix_time(), hash ); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_data_dir().hash64(), hash ); + /* + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_unique_ID().hash64(), hash ); + */ + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)this), hash ); //rely on aslr heap + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)&hash), hash ); //rely on aslr stack + + hash=hash&0x7FFFFFFF; // make it compatible with unsigned, since negatie id is used for exclusion + } + + return hash; +} + +int NetworkedMultiplayerENet::get_unique_id() const { + + ERR_FAIL_COND_V(!active,0); + return unique_id; +} + +void NetworkedMultiplayerENet::set_refuse_new_connections(bool p_enable) { + + refuse_connections=p_enable; +} + +bool NetworkedMultiplayerENet::is_refusing_new_connections() const { + + return refuse_connections; +} + +void NetworkedMultiplayerENet::set_compression_mode(CompressionMode p_mode) { + + compression_mode=p_mode; +} + +NetworkedMultiplayerENet::CompressionMode NetworkedMultiplayerENet::get_compression_mode() const{ + + return compression_mode; +} + +size_t NetworkedMultiplayerENet::enet_compress(void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit) { + + NetworkedMultiplayerENet *enet = (NetworkedMultiplayerENet*)(context); + + if (size_t(enet->src_compressor_mem.size())<inLimit) { + enet->src_compressor_mem.resize( inLimit ); + } + + int total = inLimit; + int ofs=0; + while(total) { + for(size_t i=0;i<inBufferCount;i++) { + int to_copy = MIN(total,int(inBuffers[i].dataLength)); + copymem(&enet->src_compressor_mem[ofs],inBuffers[i].data,to_copy); + ofs+=to_copy; + total-=to_copy; + } + } + + Compression::Mode mode; + + switch(enet->compression_mode) { + case COMPRESS_FASTLZ: { + mode=Compression::MODE_FASTLZ; + } break; + case COMPRESS_ZLIB: { + mode=Compression::MODE_DEFLATE; + } break; + default: { ERR_FAIL_V(0); } + } + + int req_size = Compression::get_max_compressed_buffer_size(ofs,mode); + if (enet->dst_compressor_mem.size()<req_size) { + enet->dst_compressor_mem.resize(req_size); + } + int ret=Compression::compress(enet->dst_compressor_mem.ptr(),enet->src_compressor_mem.ptr(),ofs,mode); + + if (ret<0) + return 0; + + + if (ret>int(outLimit)) + return 0; //do not bother + + copymem(outData,enet->dst_compressor_mem.ptr(),ret); + + return ret; +} + +size_t NetworkedMultiplayerENet::enet_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit){ + + NetworkedMultiplayerENet *enet = (NetworkedMultiplayerENet*)(context); + int ret = -1; + switch(enet->compression_mode) { + case COMPRESS_FASTLZ: { + + ret=Compression::decompress(outData,outLimit,inData,inLimit,Compression::MODE_FASTLZ); + } break; + case COMPRESS_ZLIB: { + + ret=Compression::decompress(outData,outLimit,inData,inLimit,Compression::MODE_DEFLATE); + } break; + default: {} + } + if (ret<0) { + return 0; + } else { + return ret; + } +} + +void NetworkedMultiplayerENet::_setup_compressor() { + + switch(compression_mode) { + + case COMPRESS_NONE: { + + enet_host_compress(host,NULL); + } break; + case COMPRESS_RANGE_CODER: { + enet_host_compress_with_range_coder(host); + } break; + case COMPRESS_FASTLZ: + case COMPRESS_ZLIB: { + + enet_host_compress(host,&enet_compressor); + } break; + } +} + +void NetworkedMultiplayerENet::enet_compressor_destroy(void * context){ + + //do none +} + + +void NetworkedMultiplayerENet::_bind_methods() { + + ClassDB::bind_method(_MD("create_server","port","max_clients","in_bandwidth","out_bandwidth"),&NetworkedMultiplayerENet::create_server,DEFVAL(32),DEFVAL(0),DEFVAL(0)); + ClassDB::bind_method(_MD("create_client","ip","port","in_bandwidth","out_bandwidth"),&NetworkedMultiplayerENet::create_client,DEFVAL(0),DEFVAL(0)); + ClassDB::bind_method(_MD("close_connection"),&NetworkedMultiplayerENet::close_connection); + ClassDB::bind_method(_MD("set_compression_mode","mode"),&NetworkedMultiplayerENet::set_compression_mode); + ClassDB::bind_method(_MD("get_compression_mode"),&NetworkedMultiplayerENet::get_compression_mode); + ClassDB::bind_method(_MD("set_bind_ip", "ip"),&NetworkedMultiplayerENet::set_bind_ip); + + BIND_CONSTANT( COMPRESS_NONE ); + BIND_CONSTANT( COMPRESS_RANGE_CODER ); + BIND_CONSTANT( COMPRESS_FASTLZ ); + BIND_CONSTANT( COMPRESS_ZLIB ); +} + + +NetworkedMultiplayerENet::NetworkedMultiplayerENet(){ + + active=false; + server=false; + refuse_connections=false; + unique_id=0; + target_peer=0; + current_packet.packet=NULL; + transfer_mode=TRANSFER_MODE_RELIABLE; + connection_status=CONNECTION_DISCONNECTED; + compression_mode=COMPRESS_NONE; + enet_compressor.context=this; + enet_compressor.compress=enet_compress; + enet_compressor.decompress=enet_decompress; + enet_compressor.destroy=enet_compressor_destroy; + + bind_ip=ENET_HOST_ANY; +} + +NetworkedMultiplayerENet::~NetworkedMultiplayerENet(){ + + close_connection(); +} + +// sets IP for ENet to bind when using create_server +// if no IP is set, then ENet bind to ENET_HOST_ANY +void NetworkedMultiplayerENet::set_bind_ip(const IP_Address& p_ip){ + ERR_FAIL_COND(!p_ip.is_ipv4()); + bind_ip=*(uint32_t *)p_ip.get_ipv4(); +} diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h new file mode 100644 index 000000000..dcf8c2429 --- /dev/null +++ b/modules/enet/networked_multiplayer_enet.h @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* networked_multiplayer_enet.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef NETWORKED_MULTIPLAYER_ENET_H +#define NETWORKED_MULTIPLAYER_ENET_H + +#include "io/networked_multiplayer_peer.h" +#include "enet/enet.h" +#include "io/compression.h" + +class NetworkedMultiplayerENet : public NetworkedMultiplayerPeer { + + GDCLASS(NetworkedMultiplayerENet,NetworkedMultiplayerPeer) +public: + enum CompressionMode { + COMPRESS_NONE, + COMPRESS_RANGE_CODER, + COMPRESS_FASTLZ, + COMPRESS_ZLIB + }; +private: + + + enum { + SYSMSG_ADD_PEER, + SYSMSG_REMOVE_PEER + }; + + enum { + SYSCH_CONFIG, + SYSCH_RELIABLE, + SYSCH_UNRELIABLE, + SYSCH_MAX + }; + + bool active; + bool server; + + uint32_t unique_id; + + int target_peer; + TransferMode transfer_mode; + + ENetEvent event; + ENetPeer *peer; + ENetHost *host; + + bool refuse_connections; + + ConnectionStatus connection_status; + + Map<int,ENetPeer*> peer_map; + + struct Packet { + + ENetPacket *packet; + int from; + }; + + CompressionMode compression_mode; + + mutable List<Packet> incoming_packets; + + mutable Packet current_packet; + + uint32_t _gen_unique_id() const; + void _pop_current_packet() const; + + Vector<uint8_t> src_compressor_mem; + Vector<uint8_t> dst_compressor_mem; + + ENetCompressor enet_compressor; + static size_t enet_compress(void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); + static size_t enet_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); + static void enet_compressor_destroy(void * context); + void _setup_compressor(); + + enet_uint32 bind_ip; +protected: + static void _bind_methods(); +public: + + virtual void set_transfer_mode(TransferMode p_mode); + virtual void set_target_peer(int p_peer); + + + virtual int get_packet_peer() const; + + + Error create_server(int p_port, int p_max_peers=32, int p_in_bandwidth=0, int p_out_bandwidth=0); + Error create_client(const IP_Address& p_ip, int p_port, int p_in_bandwidth=0, int p_out_bandwidth=0); + + void close_connection(); + + virtual void poll(); + + virtual bool is_server() const; + + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer,int &r_buffer_size) const; ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer,int p_buffer_size); + + virtual int get_max_packet_size() const; + + virtual ConnectionStatus get_connection_status() const; + + virtual void set_refuse_new_connections(bool p_enable); + virtual bool is_refusing_new_connections() const; + + virtual int get_unique_id() const; + + void set_compression_mode(CompressionMode p_mode); + CompressionMode get_compression_mode() const; + + NetworkedMultiplayerENet(); + ~NetworkedMultiplayerENet(); + + void set_bind_ip(const IP_Address& p_ip); +}; + +VARIANT_ENUM_CAST(NetworkedMultiplayerENet::CompressionMode); + + +#endif // NETWORKED_MULTIPLAYER_ENET_H diff --git a/modules/enet/register_types.cpp b/modules/enet/register_types.cpp new file mode 100644 index 000000000..2b4dd35d3 --- /dev/null +++ b/modules/enet/register_types.cpp @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" +#include "error_macros.h" +#include "networked_multiplayer_enet.h" + +static bool enet_ok=false; + +void register_enet_types() { + + if (enet_initialize() !=0 ) { + ERR_PRINT("ENet initialization failure"); + } else { + enet_ok=true; + } + + ClassDB::register_class<NetworkedMultiplayerENet>(); +} + +void unregister_enet_types() { + + if (enet_ok) + enet_deinitialize(); + +} diff --git a/modules/enet/register_types.h b/modules/enet/register_types.h new file mode 100644 index 000000000..14cb1ba86 --- /dev/null +++ b/modules/enet/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_enet_types(); +void unregister_enet_types(); diff --git a/modules/etc1/SCsub b/modules/etc1/SCsub new file mode 100644 index 000000000..0c5dc66d2 --- /dev/null +++ b/modules/etc1/SCsub @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_etc1 = env_modules.Clone() + +# Thirdparty source files +# Not unbundled so far since not widespread as shared library +thirdparty_dir = "#thirdparty/rg-etc1/" +thirdparty_sources = [ + "rg_etc1.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_etc1.add_source_files(env.modules_sources, thirdparty_sources) +env_etc1.Append(CPPPATH=[thirdparty_dir]) + +# Godot source files +env_etc1.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/etc1/config.py b/modules/etc1/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/etc1/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/etc1/image_etc.cpp b/modules/etc1/image_etc.cpp new file mode 100644 index 000000000..36ff1b86d --- /dev/null +++ b/modules/etc1/image_etc.cpp @@ -0,0 +1,201 @@ +/*************************************************************************/ +/* image_etc.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "image_etc.h" +#include "image.h" +#include "rg_etc1.h" +#include "print_string.h" +#include "os/copymem.h" +static void _decompress_etc(Image *p_img) { + + ERR_FAIL_COND(p_img->get_format()!=Image::FORMAT_ETC); + + int imgw = p_img->get_width(); + int imgh = p_img->get_height(); + PoolVector<uint8_t> src=p_img->get_data(); + PoolVector<uint8_t> dst; + + PoolVector<uint8_t>::Read r = src.read(); + + int mmc=p_img->get_mipmap_count(); + + + for(int i=0;i<=mmc;i++) { + + dst.resize(dst.size()+imgw*imgh*3); + const uint8_t *srcbr=&r[p_img->get_mipmap_offset(i)]; + PoolVector<uint8_t>::Write w = dst.write(); + + uint8_t *wptr = &w[dst.size()-imgw*imgh*3]; + + int bw=MAX(imgw/4,1); + int bh=MAX(imgh/4,1); + + for(int y=0;y<bh;y++) { + + for(int x=0;x<bw;x++) { + + uint8_t block[4*4*4]; + + + rg_etc1::unpack_etc1_block(srcbr,(unsigned int*)block); + srcbr+=8; + + int maxx=MIN(imgw,4); + int maxy=MIN(imgh,4); + + for(int yy=0;yy<maxy;yy++) { + + for(int xx=0;xx<maxx;xx++) { + + uint32_t src_ofs = (yy*4+xx)*4; + uint32_t dst_ofs = ((y*4+yy)*imgw+x*4+xx)*3; + wptr[dst_ofs+0]=block[src_ofs+0]; + wptr[dst_ofs+1]=block[src_ofs+1]; + wptr[dst_ofs+2]=block[src_ofs+2]; + + } + } + + } + + } + + imgw=MAX(1,imgw/2); + imgh=MAX(1,imgh/2); + } + + + r=PoolVector<uint8_t>::Read(); + //print_line("Re Creating ETC into regular image: w "+itos(p_img->get_width())+" h "+itos(p_img->get_height())+" mm "+itos(p_img->get_mipmaps())); + *p_img=Image(p_img->get_width(),p_img->get_height(),p_img->has_mipmaps(),Image::FORMAT_RGB8,dst); + if (p_img->has_mipmaps()) + p_img->generate_mipmaps(true); + + +} + +static void _compress_etc(Image *p_img) { + + Image img = *p_img; + + int imgw=img.get_width(),imgh=img.get_height(); + + ERR_FAIL_COND( nearest_power_of_2(imgw)!=imgw || nearest_power_of_2(imgh)!=imgh ); + + if (img.get_format()!=Image::FORMAT_RGB8) + img.convert(Image::FORMAT_RGB8); + + + int mmc=img.get_mipmap_count(); + if (mmc==0) + img.generate_mipmaps(); // force mipmaps, so it works on most hardware + + + PoolVector<uint8_t> res_data; + PoolVector<uint8_t> dst_data; + PoolVector<uint8_t>::Read r = img.get_data().read(); + + int mc=0; + + + rg_etc1::etc1_pack_params pp; + pp.m_quality=rg_etc1::cLowQuality; + for(int i=0;i<=mmc;i++) { + + + int bw=MAX(imgw/4,1); + int bh=MAX(imgh/4,1); + const uint8_t *src = &r[img.get_mipmap_offset(i)]; + int mmsize = MAX(bw,1)*MAX(bh,1)*8; + dst_data.resize(dst_data.size()+mmsize); + PoolVector<uint8_t>::Write w=dst_data.write(); + uint8_t *dst = &w[dst_data.size()-mmsize]; + + + //print_line("bh: "+itos(bh)+" bw: "+itos(bw)); + + for(int y=0;y<bh;y++) { + + for(int x=0;x<bw;x++) { + + //print_line("x: "+itos(x)+" y: "+itos(y)); + + uint8_t block[4*4*4]; + zeromem(block,4*4*4); + uint8_t cblock[8]; + + int maxy = MIN(imgh,4); + int maxx = MIN(imgw,4); + + + for(int yy=0;yy<maxy;yy++) { + + for(int xx=0;xx<maxx;xx++) { + + + uint32_t dst_ofs = (yy*4+xx)*4; + uint32_t src_ofs = ((y*4+yy)*imgw+x*4+xx)*3; + block[dst_ofs+0]=src[src_ofs+0]; + block[dst_ofs+1]=src[src_ofs+1]; + block[dst_ofs+2]=src[src_ofs+2]; + block[dst_ofs+3]=255; + + } + } + + rg_etc1::pack_etc1_block(cblock, (const unsigned int*)block, pp); + for(int j=0;j<8;j++) { + + dst[j]=cblock[j]; + } + + dst+=8; + } + + } + + imgw=MAX(1,imgw/2); + imgh=MAX(1,imgh/2); + mc++; + + } + + *p_img=Image(p_img->get_width(),p_img->get_height(),(mc-1)?true:false,Image::FORMAT_ETC,dst_data); + + +} + +void _register_etc1_compress_func() { + + rg_etc1::pack_etc1_block_init(); + Image::_image_compress_etc_func=_compress_etc; + Image::_image_decompress_etc=_decompress_etc; + + +} diff --git a/modules/etc1/image_etc.h b/modules/etc1/image_etc.h new file mode 100644 index 000000000..6ab10126f --- /dev/null +++ b/modules/etc1/image_etc.h @@ -0,0 +1,35 @@ +/*************************************************************************/ +/* image_etc.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef IMAGE_ETC1_H +#define IMAGE_ETC1_H + + +void _register_etc1_compress_func(); + +#endif // IMAGE_ETC_H diff --git a/modules/etc1/register_types.cpp b/modules/etc1/register_types.cpp new file mode 100644 index 000000000..d02ef8347 --- /dev/null +++ b/modules/etc1/register_types.cpp @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "image_etc.h" +#include "texture_loader_pkm.h" + +static ResourceFormatPKM *resource_loader_pkm = NULL; + +void register_etc1_types() { + + resource_loader_pkm = memnew( ResourceFormatPKM ); + ResourceLoader::add_resource_format_loader(resource_loader_pkm); + + _register_etc1_compress_func(); +} + +void unregister_etc1_types() { + + memdelete(resource_loader_pkm); +} diff --git a/modules/etc1/register_types.h b/modules/etc1/register_types.h new file mode 100644 index 000000000..fe92496ce --- /dev/null +++ b/modules/etc1/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_etc1_types(); +void unregister_etc1_types(); diff --git a/modules/etc1/texture_loader_pkm.cpp b/modules/etc1/texture_loader_pkm.cpp new file mode 100644 index 000000000..e720e1fb4 --- /dev/null +++ b/modules/etc1/texture_loader_pkm.cpp @@ -0,0 +1,84 @@ +#include "texture_loader_pkm.h" +#include "os/file_access.h" +#include <string.h> + +struct ETC1Header { + char tag[6]; // "PKM 10" + uint16_t format; // Format == number of mips (== zero) + uint16_t texWidth; // Texture dimensions, multiple of 4 (big-endian) + uint16_t texHeight; + uint16_t origWidth; // Original dimensions (big-endian) + uint16_t origHeight; +}; + +RES ResourceFormatPKM::load(const String &p_path, const String& p_original_path, Error *r_error) { + + if (r_error) + *r_error=ERR_CANT_OPEN; + + Error err; + FileAccess *f = FileAccess::open(p_path,FileAccess::READ,&err); + if (!f) + return RES(); + + FileAccessRef fref(f); + if (r_error) + *r_error=ERR_FILE_CORRUPT; + + ERR_EXPLAIN("Unable to open PKM texture file: "+p_path); + ERR_FAIL_COND_V(err!=OK,RES()); + + // big endian + f->set_endian_swap(true); + + ETC1Header h; + ERR_EXPLAIN("Invalid or Unsupported PKM texture file: "+p_path); + f->get_buffer((uint8_t *) &h.tag, sizeof(h.tag)); + if(strncmp(h.tag, "PKM 10", sizeof(h.tag))) + ERR_FAIL_V(RES()); + + h.format = f->get_16(); + h.texWidth = f->get_16(); + h.texHeight = f->get_16(); + h.origWidth = f->get_16(); + h.origHeight = f->get_16(); + + PoolVector<uint8_t> src_data; + + uint32_t size = h.texWidth * h.texHeight / 2; + src_data.resize(size); + PoolVector<uint8_t>::Write wb = src_data.write(); + f->get_buffer(wb.ptr(),size); + wb=PoolVector<uint8_t>::Write(); + + int mipmaps = h.format; + int width = h.origWidth; + int height = h.origHeight; + + Image img(width,height,mipmaps,Image::FORMAT_ETC,src_data); + + Ref<ImageTexture> texture = memnew( ImageTexture ); + texture->create_from_image(img); + + if (r_error) + *r_error=OK; + + return texture; +} + +void ResourceFormatPKM::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("pkm"); +} + +bool ResourceFormatPKM::handles_type(const String& p_type) const { + + return ClassDB::is_parent_class(p_type,"Texture"); +} + +String ResourceFormatPKM::get_resource_type(const String &p_path) const { + + if (p_path.get_extension().to_lower()=="pkm") + return "ImageTexture"; + return ""; +} diff --git a/modules/etc1/texture_loader_pkm.h b/modules/etc1/texture_loader_pkm.h new file mode 100644 index 000000000..5788716d9 --- /dev/null +++ b/modules/etc1/texture_loader_pkm.h @@ -0,0 +1,18 @@ +#ifndef TEXTURE_LOADER_PKM_H +#define TEXTURE_LOADER_PKM_H + +#include "scene/resources/texture.h" +#include "io/resource_loader.h" + +class ResourceFormatPKM : public ResourceFormatLoader{ +public: + + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + + virtual ~ResourceFormatPKM() {} +}; + +#endif // TEXTURE_LOADER_PKM_H diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub new file mode 100644 index 000000000..e4dd1c36a --- /dev/null +++ b/modules/freetype/SCsub @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +Import('env') + +# Not building in a separate env as core needs it + +# Thirdparty source files +if (env['builtin_freetype'] != 'no'): + thirdparty_dir = "#thirdparty/freetype/" + thirdparty_sources = [ + "src/autofit/autofit.c", + "src/base/ftapi.c", + "src/base/ftbase.c", + "src/base/ftbbox.c", + "src/base/ftbdf.c", + "src/base/ftbitmap.c", + "src/base/ftcid.c", + "src/base/ftdebug.c", + "src/base/ftfntfmt.c", + "src/base/ftfstype.c", + "src/base/ftgasp.c", + "src/base/ftglyph.c", + "src/base/ftgxval.c", + "src/base/ftinit.c", + "src/base/ftlcdfil.c", + "src/base/ftmm.c", + "src/base/ftotval.c", + "src/base/ftpatent.c", + "src/base/ftpfr.c", + "src/base/ftpic.c", + "src/base/ftstroke.c", + "src/base/ftsynth.c", + "src/base/ftsystem.c", + "src/base/fttype1.c", + "src/base/ftwinfnt.c", + "src/bdf/bdf.c", + "src/cache/ftcache.c", + "src/cff/cff.c", + "src/cid/type1cid.c", + "src/gxvalid/gxvalid.c", + "src/otvalid/otvalid.c", + "src/pcf/pcf.c", + "src/pfr/pfr.c", + "src/psaux/psaux.c", + "src/pshinter/pshinter.c", + "src/psnames/psnames.c", + "src/raster/raster.c", + "src/sfnt/sfnt.c", + "src/smooth/smooth.c", + "src/truetype/truetype.c", + "src/type1/type1.c", + "src/type42/type42.c", + "src/winfonts/winfnt.c", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + # Include header for UWP to fix build issues + if "platform" in env and env["platform"] == "uwp": + env.Append(CCFLAGS=['/FI', '"modules/freetype/uwpdef.h"']) + + env.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include"]) + + # also requires libpng headers + if (env['builtin_libpng'] != 'no'): + env.Append(CPPPATH=["#thirdparty/libpng"]) + + """ FIXME: Remove this commented code if Windows can handle the monolithic lib + # fix for Windows' shell miserably failing on long lines, split in two libraries + half1 = [] + half2 = [] + for x in thirdparty_sources: + if (x.find("src/base") != -1 and x.find("src/sfnt") != -1): + half1.append(x) + else: + half2.append(x) + + lib = env.Library("freetype_builtin1", half2) + env.Append(LIBS = [lib]) + lib = env.Library("freetype_builtin2", half1) + env.Append(LIBS = [lib]) + """ + + lib = env.Library("freetype_builtin", thirdparty_sources) + env.Append(LIBS=[lib]) + +# Godot source files +env.add_source_files(env.modules_sources, "*.cpp") +env.Append(CCFLAGS=['-DFREETYPE_ENABLED']) + +Export('env') diff --git a/modules/freetype/config.py b/modules/freetype/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/freetype/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/freetype/register_types.cpp b/modules/freetype/register_types.cpp new file mode 100644 index 000000000..2579a925d --- /dev/null +++ b/modules/freetype/register_types.cpp @@ -0,0 +1,33 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +void register_freetype_types() {} + +void unregister_freetype_types() {} diff --git a/modules/freetype/register_types.h b/modules/freetype/register_types.h new file mode 100644 index 000000000..283789812 --- /dev/null +++ b/modules/freetype/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_freetype_types(); +void unregister_freetype_types(); diff --git a/modules/freetype/uwpdef.h b/modules/freetype/uwpdef.h new file mode 100644 index 000000000..b4aabb192 --- /dev/null +++ b/modules/freetype/uwpdef.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* uwpdef.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +// "generic" is a reserved keyword in C++/CX code +// this avoids the errors in the variable name from Freetype code +#define generic freetype_generic diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 403fe68f6..088240676 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -1,5 +1,7 @@ +#!/usr/bin/env python + Import('env') -env.add_source_files(env.modules_sources,"*.cpp") +env.add_source_files(env.modules_sources, "*.cpp") Export('env') diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index ea7e83378..5698a3729 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -1,11 +1,8 @@ def can_build(platform): - return True + return True def configure(env): - pass - - - + pass diff --git a/modules/gdscript/gd_compiler.cpp b/modules/gdscript/gd_compiler.cpp index 5a6299bcf..8e9e94cc4 100644 --- a/modules/gdscript/gd_compiler.cpp +++ b/modules/gdscript/gd_compiler.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,31 @@ #include "gd_compiler.h" #include "gd_script.h" +bool GDCompiler::_is_class_member_property(CodeGen & codegen, const StringName & p_name) { + + if (!codegen.function_node || codegen.function_node->_static) + return false; + + return _is_class_member_property(codegen.script,p_name); +} + +bool GDCompiler::_is_class_member_property(GDScript *owner, const StringName & p_name) { + + + GDScript *scr = owner; + GDNativeClass *nc=NULL; + while(scr) { + + if (scr->native.is_valid()) + nc=scr->native.ptr(); + scr=scr->_base; + } + + ERR_FAIL_COND_V(!nc,false); + + return ClassDB::has_property(nc->get_name(),p_name); +} + void GDCompiler::_set_error(const String& p_error,const GDParser::Node *p_node) { @@ -164,6 +189,17 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre StringName identifier = in->name; + + if (_is_class_member_property(codegen,identifier)) { + //get property + codegen.opcodes.push_back(GDFunction::OPCODE_GET_MEMBER); // perform operator + codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) + int dst_addr=(p_stack_level)|(GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode + codegen.alloc_stack(p_stack_level); + return dst_addr; + } + // TRY STACK! if (!p_initializer && codegen.stack_identifiers.has(identifier)) { @@ -208,7 +244,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre if (nc) { bool success=false; - int constant = ObjectTypeDB::get_integer_constant(nc->get_name(),identifier,&success); + int constant = ClassDB::get_integer_constant(nc->get_name(),identifier,&success); if (success) { Variant key=constant; int idx; @@ -460,7 +496,6 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre const GDParser::Node *instance = on->arguments[0]; - bool in_static=false; if (instance->type==GDParser::Node::TYPE_SELF) { //room for optimization @@ -550,17 +585,25 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre int index; if (named) { -#ifdef DEBUG_ENABLED if (on->arguments[0]->type==GDParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) { - const Map<StringName,GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name); + GDParser::IdentifierNode* identifier = static_cast<GDParser::IdentifierNode*>(on->arguments[1]); + const Map<StringName,GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name); + +#ifdef DEBUG_ENABLED if (MI && MI->get().getter==codegen.function_node->name) { String n = static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name; _set_error("Must use '"+n+"' instead of 'self."+n+"' in getter.",on); return -1; } - } #endif + + if (MI && MI->get().getter=="") { + // Faster than indexing self (as if no self. had been used) + return (MI->get().index)|(GDFunction::ADDR_TYPE_MEMBER<<GDFunction::ADDR_BITS); + } + } + index=codegen.get_name_map_pos(static_cast<GDParser::IdentifierNode*>(on->arguments[1])->name); } else { @@ -655,6 +698,46 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre return p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS; } break; + // ternary operators + case GDParser::OperatorNode::OP_TERNARY_IF: { + + // x IF a ELSE y operator with early out on failure + + int res = _parse_expression(codegen,on->arguments[0],p_stack_level); + if (res<0) + return res; + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP_IF_NOT); + codegen.opcodes.push_back(res); + int jump_fail_pos=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + + + res = _parse_expression(codegen,on->arguments[1],p_stack_level); + if (res<0) + return res; + + codegen.alloc_stack(p_stack_level); //it will be used.. + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN); + codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + codegen.opcodes.push_back(res); + codegen.opcodes.push_back(GDFunction::OPCODE_JUMP); + int jump_past_pos=codegen.opcodes.size(); + codegen.opcodes.push_back(0); + + codegen.opcodes[jump_fail_pos]=codegen.opcodes.size(); + res = _parse_expression(codegen,on->arguments[2],p_stack_level); + if (res<0) + return res; + + codegen.opcodes.push_back(GDFunction::OPCODE_ASSIGN); + codegen.opcodes.push_back(p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS); + codegen.opcodes.push_back(res); + + codegen.opcodes[jump_past_pos]=codegen.opcodes.size(); + + return p_stack_level|GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS; + + } break; //unary operators case GDParser::OperatorNode::OP_NEG: { if (!_create_unary_operator(codegen,on,Variant::OP_NEGATE,p_stack_level)) return -1;} break; case GDParser::OperatorNode::OP_NOT: { if (!_create_unary_operator(codegen,on,Variant::OP_NOT,p_stack_level)) return -1;} break; @@ -729,6 +812,8 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre /* Find chain of sets */ + StringName assign_property; + List<GDParser::OperatorNode*> chain; { @@ -737,8 +822,20 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre while(true) { chain.push_back(n); - if (n->arguments[0]->type!=GDParser::Node::TYPE_OPERATOR) + if (n->arguments[0]->type!=GDParser::Node::TYPE_OPERATOR) { + + //check for a built-in property + if (n->arguments[0]->type==GDParser::Node::TYPE_IDENTIFIER) { + + GDParser::IdentifierNode *identifier = static_cast<GDParser::IdentifierNode*>(n->arguments[0]); + if (_is_class_member_property(codegen,identifier->name)) { + assign_property = identifier->name; + + } + + } break; + } n = static_cast<GDParser::OperatorNode*>(n->arguments[0]); if (n->op!=GDParser::OperatorNode::OP_INDEX && n->op!=GDParser::OperatorNode::OP_INDEX_NAMED) break; @@ -763,7 +860,16 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre Vector<int> setchain; - int prev_key_idx=-1; + + if (assign_property!=StringName()) { + + // recover and assign at the end, this allows stuff like + // position.x+=2.0 + // in Node2D + setchain.push_back(prev_pos); + setchain.push_back(codegen.get_name_map_pos(assign_property)); + setchain.push_back(GDFunction::OPCODE_SET_MEMBER); + } for(List<GDParser::OperatorNode*>::Element *E=chain.back();E;E=E->prev()) { @@ -795,7 +901,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre } - if (key_idx<0) + if (key_idx<0) //error return key_idx; codegen.opcodes.push_back(named ? GDFunction::OPCODE_GET_NAMED : GDFunction::OPCODE_GET); @@ -807,14 +913,16 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre codegen.opcodes.push_back(dst_pos); + //add in reverse order, since it will be reverted + + setchain.push_back(dst_pos); setchain.push_back(key_idx); setchain.push_back(prev_pos); setchain.push_back(named ? GDFunction::OPCODE_SET_NAMED : GDFunction::OPCODE_SET); prev_pos=dst_pos; - prev_key_idx=key_idx; } @@ -837,7 +945,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre } - if (set_index<0) + if (set_index<0) //error return set_index; if (set_index&GDFunction::ADDR_TYPE_STACK<<GDFunction::ADDR_BITS) { @@ -847,7 +955,7 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre int set_value = _parse_assign_right_expression(codegen,on,slevel+1); - if (set_value<0) + if (set_value<0) //error return set_value; codegen.opcodes.push_back(named?GDFunction::OPCODE_SET_NAMED:GDFunction::OPCODE_SET); @@ -855,20 +963,36 @@ int GDCompiler::_parse_expression(CodeGen& codegen,const GDParser::Node *p_expre codegen.opcodes.push_back(set_index); codegen.opcodes.push_back(set_value); - for(int i=0;i<setchain.size();i+=4) { + for(int i=0;i<setchain.size();i++) { - codegen.opcodes.push_back(setchain[i+0]); - codegen.opcodes.push_back(setchain[i+1]); - codegen.opcodes.push_back(setchain[i+2]); - codegen.opcodes.push_back(setchain[i+3]); + codegen.opcodes.push_back(setchain[i]); } return retval; + } else if (on->arguments[0]->type==GDParser::Node::TYPE_IDENTIFIER && _is_class_member_property(codegen,static_cast<GDParser::IdentifierNode*>(on->arguments[0])->name)) { + //assignment to member property + + int slevel = p_stack_level; + + int src_address = _parse_assign_right_expression(codegen,on,slevel); + if (src_address<0) + return -1; + + StringName name = static_cast<GDParser::IdentifierNode*>(on->arguments[0])->name; + + codegen.opcodes.push_back(GDFunction::OPCODE_SET_MEMBER); + codegen.opcodes.push_back(codegen.get_name_map_pos(name)); + codegen.opcodes.push_back(src_address); + + return GDFunction::ADDR_TYPE_NIL<<GDFunction::ADDR_BITS; } else { - //ASSIGNMENT MODE!! + + + + //REGULAR ASSIGNMENT MODE!! int slevel = p_stack_level; @@ -961,12 +1085,12 @@ Error GDCompiler::_parse_block(CodeGen& codegen,const GDParser::BlockNode *p_blo switch(s->type) { case GDParser::Node::TYPE_NEWLINE: { - +#ifdef DEBUG_ENABLED const GDParser::NewLineNode *nl = static_cast<const GDParser::NewLineNode*>(s); codegen.opcodes.push_back(GDFunction::OPCODE_LINE); codegen.opcodes.push_back(nl->line); codegen.current_line=nl->line; - +#endif } break; case GDParser::Node::TYPE_CONTROL_FLOW: { // try subblocks @@ -1157,14 +1281,21 @@ Error GDCompiler::_parse_block(CodeGen& codegen,const GDParser::BlockNode *p_blo codegen.opcodes.push_back(ret); } break; case GDParser::Node::TYPE_BREAKPOINT: { +#ifdef DEBUG_ENABLED // try subblocks codegen.opcodes.push_back(GDFunction::OPCODE_BREAKPOINT); +#endif } break; case GDParser::Node::TYPE_LOCAL_VAR: { const GDParser::LocalVarNode *lv = static_cast<const GDParser::LocalVarNode*>(s); + if (_is_class_member_property(codegen,lv->name)) { + _set_error("Name for local variable '"+String(lv->name)+"' can't shadow class property of the same name.",lv); + return ERR_ALREADY_EXISTS; + } + codegen.add_stack_identifier(lv->name,p_stack_level++); codegen.alloc_stack(p_stack_level); new_identifiers++; @@ -1203,7 +1334,10 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode * if (p_func) { for(int i=0;i<p_func->arguments.size();i++) { - int idx = i; + if (_is_class_member_property(p_script,p_func->arguments[i])) { + _set_error("Name for argument '"+String(p_func->arguments[i])+"' can't shadow class property of the same name.",p_func); + return ERR_ALREADY_EXISTS; + } codegen.add_stack_identifier(p_func->arguments[i],i); #ifdef TOOLS_ENABLED argnames.push_back(p_func->arguments[i]); @@ -1286,16 +1420,19 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode * GDFunction *gdfunc=NULL; - //if (String(p_func->name)=="") { //initializer func - // gdfunc = &p_script->initializer; - + /* + if (String(p_func->name)=="") { //initializer func + gdfunc = &p_script->initializer; + */ //} else { //regular func p_script->member_functions[func_name]=memnew(GDFunction); gdfunc = p_script->member_functions[func_name]; //} - if (p_func) + if (p_func) { gdfunc->_static=p_func->_static; + gdfunc->rpc_mode=p_func->rpc_mode; + } #ifdef TOOLS_ENABLED gdfunc->arg_names=argnames; @@ -1398,6 +1535,10 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode * #endif if (p_func) { gdfunc->_initial_line=p_func->line; +#ifdef TOOLS_ENABLED + + p_script->member_lines[func_name]=p_func->line; +#endif } else { gdfunc->_initial_line=0; } @@ -1414,8 +1555,13 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode * -Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class) { +Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDParser::ClassNode *p_class, bool p_keep_state) { + Map<StringName,Ref<GDScript> > old_subclasses; + + if (p_keep_state) { + old_subclasses=p_script->subclasses; + } p_script->native=Ref<GDNativeClass>(); p_script->base=Ref<GDScript>(); @@ -1428,14 +1574,15 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars p_script->member_functions.clear(); p_script->member_indices.clear(); p_script->member_info.clear(); + p_script->_signals.clear(); p_script->initializer=NULL; + p_script->subclasses.clear(); p_script->_owner=p_owner; p_script->tool=p_class->tool; p_script->name=p_class->name; - int index_from=0; Ref<GDNativeClass> native; if (p_class->extends_used) { @@ -1491,7 +1638,8 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars String sub = p_class->extends_class[i]; if (script->subclasses.has(sub)) { - script=script->subclasses[sub]; + Ref<Script> subclass = script->subclasses[sub]; //avoid reference from dissapearing + script=subclass; } else { _set_error("Could not find subclass: "+sub,p_class); @@ -1582,9 +1730,14 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars } + }else { + // without extends, implicitly extend Reference + int native_idx = GDScriptLanguage::get_singleton()->get_global_map()["Reference"]; + native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(native.is_null(), ERR_BUG); + p_script->native=native; } - //print_line("Script: "+p_script->get_path()+" indices: "+itos(p_script->member_indices.size())); @@ -1595,6 +1748,10 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars _set_error("Member '"+name+"' already exists (in current or parent class)",p_class); return ERR_ALREADY_EXISTS; } + if (_is_class_member_property(p_script,name)) { + _set_error("Member '"+name+"' already exists as a class property.",p_class); + return ERR_ALREADY_EXISTS; + } if (p_class->variables[i]._export.type!=Variant::NIL) { @@ -1605,6 +1762,9 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars p_script->member_default_values[name]=p_class->variables[i].default_value; } #endif + } else { + + p_script->member_info[name]=PropertyInfo(Variant::NIL,name,PROPERTY_HINT_NONE,"",PROPERTY_USAGE_SCRIPT_VARIABLE); } //int new_idx = p_script->member_indices.size(); @@ -1612,9 +1772,17 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars minfo.index = p_script->member_indices.size(); minfo.setter = p_class->variables[i].setter; minfo.getter = p_class->variables[i].getter; + minfo.rpc_mode=p_class->variables[i].rpc_mode; + p_script->member_indices[name]=minfo; p_script->members.insert(name); +#ifdef TOOLS_ENABLED + + p_script->member_lines[name]=p_class->variables[i].line; +#endif + + } for(int i=0;i<p_class->constant_expressions.size();i++) { @@ -1622,10 +1790,20 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars StringName name = p_class->constant_expressions[i].identifier; ERR_CONTINUE( p_class->constant_expressions[i].expression->type!=GDParser::Node::TYPE_CONSTANT ); + if (_is_class_member_property(p_script,name)) { + _set_error("Member '"+name+"' already exists as a class property.",p_class); + return ERR_ALREADY_EXISTS; + } + GDParser::ConstantNode *constant = static_cast<GDParser::ConstantNode*>(p_class->constant_expressions[i].expression); p_script->constants.insert(name,constant->value); //p_script->constants[constant->value].make_const(); +#ifdef TOOLS_ENABLED + + p_script->member_lines[name]=p_class->constant_expressions[i].expression->line; +#endif + } for(int i=0;i<p_class->_signals.size();i++) { @@ -1649,7 +1827,7 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars } if (native.is_valid()) { - if (ObjectTypeDB::has_signal(native->get_name(),name)) { + if (ClassDB::has_signal(native->get_name(),name)) { _set_error("Signal '"+name+"' redefined (original in native class '"+String(native->get_name())+"')",p_class); return ERR_ALREADY_EXISTS; } @@ -1662,12 +1840,23 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars for(int i=0;i<p_class->subclasses.size();i++) { StringName name = p_class->subclasses[i]->name; - Ref<GDScript> subclass = memnew( GDScript ); + Ref<GDScript> subclass; - Error err = _parse_class(subclass.ptr(),p_script,p_class->subclasses[i]); + if (old_subclasses.has(name)) { + subclass=old_subclasses[name]; + } else { + subclass.instance(); + } + + Error err = _parse_class(subclass.ptr(),p_script,p_class->subclasses[i],p_keep_state); if (err) return err; +#ifdef TOOLS_ENABLED + + p_script->member_lines[name]=p_class->subclasses[i]->line; +#endif + p_script->constants.insert(name,subclass); //once parsed, goes to the list of constants p_script->subclasses.insert(name,subclass); @@ -1755,13 +1944,67 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars } } + + //validate instances if keeping state + + if (p_keep_state) { + + print_line("RELOAD KEEP "+p_script->path); + for (Set<Object*>::Element *E=p_script->instances.front();E;) { + + Set<Object*>::Element *N = E->next(); + + ScriptInstance *si = E->get()->get_script_instance(); + if (si->is_placeholder()) { +#ifdef TOOLS_ENABLED + PlaceHolderScriptInstance *psi = static_cast<PlaceHolderScriptInstance*>(si); + + if (p_script->is_tool()) { + //re-create as an instance + p_script->placeholders.erase(psi); //remove placeholder + + GDInstance* instance = memnew( GDInstance ); + instance->base_ref=E->get()->cast_to<Reference>(); + instance->members.resize(p_script->member_indices.size()); + instance->script=Ref<GDScript>(p_script); + instance->owner=E->get(); + + //needed for hot reloading + for(Map<StringName,GDScript::MemberInfo>::Element *E=p_script->member_indices.front();E;E=E->next()) { + instance->member_indices_cache[E->key()]=E->get().index; + } + instance->owner->set_script_instance(instance); + + + /* STEP 2, INITIALIZE AND CONSRTUCT */ + + Variant::CallError ce; + p_script->initializer->call(instance,NULL,0,ce); + + if (ce.error!=Variant::CallError::CALL_OK) { + //well, tough luck, not goinna do anything here + } + } +#endif + } else { + + GDInstance *gi = static_cast<GDInstance*>(si); + gi->reload_members(); + } + + E=N; + + } + + + } #endif p_script->valid=true; return OK; } -Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script) { +Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script,bool p_keep_state) { err_line=-1; err_column=-1; @@ -1772,9 +2015,7 @@ Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script) { source=p_script->get_path(); - - - Error err = _parse_class(p_script,NULL,static_cast<const GDParser::ClassNode*>(root)); + Error err = _parse_class(p_script,NULL,static_cast<const GDParser::ClassNode*>(root),p_keep_state); if (err) return err; diff --git a/modules/gdscript/gd_compiler.h b/modules/gdscript/gd_compiler.h index 32e18c6dc..dd211a852 100644 --- a/modules/gdscript/gd_compiler.h +++ b/modules/gdscript/gd_compiler.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,6 +37,7 @@ class GDCompiler { const GDParser *parser; struct CodeGen { + GDScript *script; const GDParser::ClassNode *class_node; const GDParser::FunctionNode *function_node; @@ -134,6 +135,9 @@ class GDCompiler { Ref<GDScript> _parse_class(GDParser::ClassNode *p_class); #endif + bool _is_class_member_property(CodeGen & codegen, const StringName & p_name); + bool _is_class_member_property(GDScript *owner, const StringName & p_name); + void _set_error(const String& p_error,const GDParser::Node *p_node); bool _create_unary_operator(CodeGen& codegen,const GDParser::OperatorNode *on,Variant::Operator op, int p_stack_level); @@ -144,7 +148,7 @@ class GDCompiler { int _parse_expression(CodeGen& codegen,const GDParser::Node *p_expression, int p_stack_level,bool p_root=false,bool p_initializer=false); Error _parse_block(CodeGen& codegen,const GDParser::BlockNode *p_block,int p_stack_level=0,int p_break_addr=-1,int p_continue_addr=-1); Error _parse_function(GDScript *p_script,const GDParser::ClassNode *p_class,const GDParser::FunctionNode *p_func,bool p_for_ready=false); - Error _parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class); + Error _parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class,bool p_keep_state); int err_line; int err_column; StringName source; @@ -152,7 +156,7 @@ class GDCompiler { public: - Error compile(const GDParser *p_parser,GDScript *p_script); + Error compile(const GDParser *p_parser, GDScript *p_script, bool p_keep_state=false); String get_error() const; int get_error_line() const; diff --git a/modules/gdscript/gd_editor.cpp b/modules/gdscript/gd_editor.cpp index 7e5ff620c..19472d3d4 100644 --- a/modules/gdscript/gd_editor.cpp +++ b/modules/gdscript/gd_editor.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -44,21 +44,26 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { } -String GDScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { +Ref<Script> GDScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { String _template = String()+ - "\nextends %BASE%\n\n"+ - "# member variables here, example:\n"+ - "# var a=2\n"+ - "# var b=\"textvar\"\n\n"+ + "extends %BASE%\n\n"+ + "# class member variables go here, for example:\n"+ + "# var a = 2\n"+ + "# var b = \"textvar\"\n\n"+ "func _ready():\n"+ "\t# Called every time the node is added to the scene.\n"+ "\t# Initialization here\n"+ - "\tpass\n"+ - "\n"+ - "\n"; + "\tpass\n"; + + _template = _template.replace("%BASE%",p_base_class_name); + + Ref<GDScript> script; + script.instance(); + script->set_source_code(_template); + + return script; - return _template.replace("%BASE%",p_base_class_name); } @@ -212,7 +217,7 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); int l = _debug_call_stack_pos - p_level -1; - return _call_stack[l].function->get_script()->get_path(); + return _call_stack[l].function->get_source(); } void GDScriptLanguage::debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { @@ -297,7 +302,7 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const } { MethodInfo mi; - mi.name="yield"; + mi.name="yield:GDFunctionState"; mi.arguments.push_back(PropertyInfo(Variant::OBJECT,"object")); mi.arguments.push_back(PropertyInfo(Variant::STRING,"signal")); mi.default_arguments.push_back(Variant::NIL); @@ -320,7 +325,7 @@ void GDScriptLanguage::get_public_constants(List<Pair<String,Variant> > *p_const p_constants->push_back(pi); } -String GDScriptLanguage::make_function(const String& p_class,const String& p_name,const StringArray& p_args) const { +String GDScriptLanguage::make_function(const String& p_class,const String& p_name,const PoolStringArray& p_args) const { String s="func "+p_name+"("; if (p_args.size()) { @@ -328,7 +333,7 @@ String GDScriptLanguage::make_function(const String& p_class,const String& p_nam for(int i=0;i<p_args.size();i++) { if (i>0) s+=", "; - s+=p_args[i]; + s+=p_args[i].get_slice(":",0); } s+=" "; } @@ -346,6 +351,7 @@ struct GDCompletionIdentifier { Ref<GDScript> script; Variant::Type type; Variant value; //im case there is a value, also return it + }; @@ -358,11 +364,13 @@ static GDCompletionIdentifier _get_type_from_variant(const Variant& p_variant) { if (p_variant.get_type()==Variant::OBJECT) { Object *obj = p_variant; if (obj) { - //if (obj->cast_to<GDNativeClass>()) { - // t.obj_type=obj->cast_to<GDNativeClass>()->get_name(); - // t.value=Variant(); - //} else { - t.obj_type=obj->get_type(); + /* + if (obj->cast_to<GDNativeClass>()) { + t.obj_type=obj->cast_to<GDNativeClass>()->get_name(); + t.value=Variant(); + } else { + */ + t.obj_type=obj->get_class(); //} } } @@ -449,62 +457,21 @@ static Ref<Reference> _get_parent_class(GDCompletionContext& context) { } String base=context._class->extends_class[0]; - const GDParser::ClassNode *p = context._class->owner; - Ref<GDScript> base_class; -#if 0 - while(p) { - if (p->subclasses.has(base)) { + if (context._class->extends_class.size()>1) { - base_class=p->subclasses[base]; - break; - } - p=p->_owner; - } -#endif - if (base_class.is_valid()) { -#if 0 - for(int i=1;i<context._class->extends_class.size();i++) { - - String subclass=context._class->extends_class[i]; - - if (base_class->subclasses.has(subclass)) { - - base_class=base_class->subclasses[subclass]; - } else { - - //print_line("Could not find subclass: "+subclass); - return _get_type_from_class(context); //fail please - } - } - - script=base_class; -#endif - - } else { - - if (context._class->extends_class.size()>1) { - - return REF(); - - - } - //if not found, try engine classes - if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - - return REF(); - } - - int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; - native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; - if (!native.is_valid()) { + return REF(); - print_line("Global not a class: '"+base+"'"); + } + //if not found, try engine classes + if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { - } - return native; + return REF(); } + int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; + native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; + return native; } @@ -649,10 +616,10 @@ static bool _guess_expression_type(GDCompletionContext& context,const GDParser:: } } - if (ObjectTypeDB::has_method(base.obj_type,id)) { + if (ClassDB::has_method(base.obj_type,id)) { #ifdef TOOLS_ENABLED - MethodBind *mb = ObjectTypeDB::get_method(base.obj_type,id); + MethodBind *mb = ClassDB::get_method(base.obj_type,id); PropertyInfo pi = mb->get_argument_info(-1); //try calling the function if constant and all args are constant, should not crash.. @@ -678,14 +645,14 @@ static bool _guess_expression_type(GDCompletionContext& context,const GDParser:: } } - if (all_valid && String(id)=="get_node" && ObjectTypeDB::is_type(base.obj_type,"Node") && args.size()) { + if (all_valid && String(id)=="get_node" && ClassDB::is_parent_class(base.obj_type,"Node") && args.size()) { String arg1=args[0]; if (arg1.begins_with("/root/")) { String which = arg1.get_slice("/",2); if (which!="") { List<PropertyInfo> props; - Globals::get_singleton()->get_property_list(&props); + GlobalConfig::get_singleton()->get_property_list(&props); //print_line("find singleton"); for(List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { @@ -697,7 +664,7 @@ static bool _guess_expression_type(GDCompletionContext& context,const GDParser:: String name = s.get_slice("/",1); //print_line("name: "+name+", which: "+which); if (name==which) { - String script = Globals::get_singleton()->get(s); + String script = GlobalConfig::get_singleton()->get(s); if (!script.begins_with("res://")) { script="res://"+script; @@ -706,7 +673,7 @@ static bool _guess_expression_type(GDCompletionContext& context,const GDParser:: if (!script.ends_with(".gd")) { //not a script, try find the script anyway, //may have some success - script=script.basename()+".gd"; + script=script.get_basename()+".gd"; } if (FileAccess::exists(script)) { @@ -971,9 +938,19 @@ static bool _guess_expression_type(GDCompletionContext& context,const GDParser:: return false; } + static bool _guess_identifier_type_in_block(GDCompletionContext& context,int p_line,const StringName& p_identifier,GDCompletionIdentifier &r_type) { + GDCompletionIdentifier gdi = _get_native_class(context); + if (gdi.obj_type!=StringName()) { + bool valid; + Variant::Type t = ClassDB::get_property_type(gdi.obj_type,p_identifier,&valid); + if (t!=Variant::NIL && valid) { + r_type.type=t; + return true; + } + } const GDParser::Node *last_assign=NULL; int last_assign_line=-1; @@ -1024,7 +1001,7 @@ static bool _guess_identifier_type_in_block(GDCompletionContext& context,int p_l } -static bool _guess_identifier_from_assignment_in_function(GDCompletionContext& context,const StringName& p_identifier, const StringName& p_function,GDCompletionIdentifier &r_type) { +static bool _guess_identifier_from_assignment_in_function(GDCompletionContext& context, int p_src_line, const StringName& p_identifier, const StringName& p_function,GDCompletionIdentifier &r_type) { const GDParser::FunctionNode* func=NULL; for(int i=0;i<context._class->functions.size();i++) { @@ -1039,7 +1016,9 @@ static bool _guess_identifier_from_assignment_in_function(GDCompletionContext& c for(int i=0;i<func->body->statements.size();i++) { - + if (func->body->statements[i]->line == p_src_line) { + break; + } if (func->body->statements[i]->type==GDParser::BlockNode::TYPE_OPERATOR) { const GDParser::OperatorNode *op = static_cast<const GDParser::OperatorNode *>(func->body->statements[i]); @@ -1100,7 +1079,7 @@ static bool _guess_identifier_type(GDCompletionContext& context,int p_line,const //this kinda sucks but meh List<MethodInfo> vmethods; - ObjectTypeDB::get_virtual_methods(id.obj_type,&vmethods); + ClassDB::get_virtual_methods(id.obj_type,&vmethods); for (List<MethodInfo>::Element *E=vmethods.front();E;E=E->next()) { @@ -1160,11 +1139,11 @@ static bool _guess_identifier_type(GDCompletionContext& context,int p_line,const } //try to guess from assignment in construtor or _ready - if (_guess_identifier_from_assignment_in_function(context,p_identifier,"_ready",r_type)) + if (_guess_identifier_from_assignment_in_function(context,p_line+1,p_identifier,"_ready",r_type)) return true; - if (_guess_identifier_from_assignment_in_function(context,p_identifier,"_enter_tree",r_type)) + if (_guess_identifier_from_assignment_in_function(context,p_line+1,p_identifier,"_enter_tree",r_type)) return true; - if (_guess_identifier_from_assignment_in_function(context,p_identifier,"_init",r_type)) + if (_guess_identifier_from_assignment_in_function(context,p_line+1,p_identifier,"_init",r_type)) return true; return false; @@ -1174,7 +1153,7 @@ static bool _guess_identifier_type(GDCompletionContext& context,int p_line,const //autoloads as singletons List<PropertyInfo> props; - Globals::get_singleton()->get_property_list(&props); + GlobalConfig::get_singleton()->get_property_list(&props); for(List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { @@ -1184,14 +1163,14 @@ static bool _guess_identifier_type(GDCompletionContext& context,int p_line,const String name = s.get_slice("/",1); if (name==String(p_identifier)) { - String path = Globals::get_singleton()->get(s); + String path = GlobalConfig::get_singleton()->get(s); if (path.begins_with("*")) { String script =path.substr(1,path.length()); if (!script.ends_with(".gd")) { //not a script, try find the script anyway, //may have some success - script=script.basename()+".gd"; + script=script.get_basename()+".gd"; } if (FileAccess::exists(script)) { @@ -1330,26 +1309,43 @@ static void _find_identifiers_in_class(GDCompletionContext& context,bool p_stati base=script->get_native(); } else if (nc.is_valid()) { + StringName type = nc->get_name(); + if (!p_only_functions) { - StringName type = nc->get_name(); + List<String> constants; - ObjectTypeDB::get_integer_constant_list(type,&constants); + ClassDB::get_integer_constant_list(type,&constants); for(List<String>::Element *E=constants.front();E;E=E->next()) { result.insert(E->get()); } - List<MethodInfo> methods; - ObjectTypeDB::get_method_list(type,&methods); - for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { - if (E->get().name.begins_with("_")) + List<PropertyInfo> pinfo; + + ClassDB::get_property_list(type,&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + if (E->get().usage&(PROPERTY_USAGE_GROUP|PROPERTY_USAGE_CATEGORY)) continue; - if (E->get().arguments.size()) - result.insert(E->get().name+"("); - else - result.insert(E->get().name+"()"); + if (E->get().name.find("/")!=-1) + continue; + result.insert(E->get().name); } + + } + List<MethodInfo> methods; + ClassDB::get_method_list(type,&methods); + for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { + if (E->get().name.begins_with("_")) + continue; + if (E->get().arguments.size()) + result.insert(E->get().name+"("); + else + result.insert(E->get().name+"()"); } + + + break; } else break; @@ -1399,7 +1395,7 @@ static void _find_identifiers(GDCompletionContext& context,int p_line,bool p_onl } static const char*_type_names[Variant::VARIANT_MAX]={ - "null","bool","int","float","String","Vector2","Rect2","Vector3","Matrix32","Plane","Quat","AABB","Matrix3","Transform", + "null","bool","int","float","String","Vector2","Rect2","Vector3","Transform2D","Plane","Quat","AABB","Basis","Transform", "Color","Image","NodePath","RID","Object","InputEvent","Dictionary","Array","RawArray","IntArray","FloatArray","StringArray", "Vector2Array","Vector3Array","ColorArray"}; @@ -1409,7 +1405,7 @@ static void _find_identifiers(GDCompletionContext& context,int p_line,bool p_onl //autoload singletons List<PropertyInfo> props; - Globals::get_singleton()->get_property_list(&props); + GlobalConfig::get_singleton()->get_property_list(&props); for(List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { @@ -1417,7 +1413,7 @@ static void _find_identifiers(GDCompletionContext& context,int p_line,bool p_onl if (!s.begins_with("autoload/")) continue; String name = s.get_slice("/",1); - String path = Globals::get_singleton()->get(s); + String path = GlobalConfig::get_singleton()->get(s); if (path.begins_with("*")) { result.insert(name); } @@ -1495,14 +1491,14 @@ static void _make_function_hint(const GDParser::FunctionNode* p_func,int p_argid } -static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const StringName& p_method,const GDCompletionIdentifier& id, int p_argidx, Set<String>& result, String& arghint) { +static void _find_type_arguments(GDCompletionContext& context,const GDParser::Node*p_node,int p_line,const StringName& p_method,const GDCompletionIdentifier& id, int p_argidx, Set<String>& result, String& arghint) { //print_line("find type arguments?"); if (id.type==Variant::INPUT_EVENT && String(p_method)=="is_action" && p_argidx==0) { List<PropertyInfo> pinfo; - Globals::get_singleton()->get_property_list(&pinfo); + GlobalConfig::get_singleton()->get_property_list(&pinfo); for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { const PropertyInfo &pi=E->get(); @@ -1518,7 +1514,7 @@ static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const St } else if (id.type==Variant::OBJECT && id.obj_type!=StringName()) { - MethodBind *m = ObjectTypeDB::get_method(id.obj_type,p_method); + MethodBind *m = ClassDB::get_method(id.obj_type,p_method); if (!m) { //not in static method, see script @@ -1731,10 +1727,32 @@ static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const St if (p_argidx==0) { List<MethodInfo> sigs; - ObjectTypeDB::get_signal_list(id.obj_type,&sigs); + ClassDB::get_signal_list(id.obj_type,&sigs); + + if (id.script.is_valid()) { + id.script->get_script_signal_list(&sigs); + } else if (id.value.get_type()==Variant::OBJECT) { + Object *obj = id.value; + if (obj && !obj->get_script().is_null()) { + Ref<Script> scr=obj->get_script(); + if (scr.is_valid()) { + scr->get_script_signal_list(&sigs); + } + } + } + for (List<MethodInfo>::Element *E=sigs.front();E;E=E->next()) { result.insert("\""+E->get().name+"\""); } + + } else if (p_argidx==2){ + + + if (context._class) { + for(int i=0;i<context._class->functions.size();i++) { + result.insert("\""+context._class->functions[i]->name+"\""); + } + } } /*if (p_argidx==2) { @@ -1745,17 +1763,17 @@ static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const St }*/ } else { - if (p_argidx==0 && (String(p_method)=="get_node" || String(p_method)=="has_node") && ObjectTypeDB::is_type(id.obj_type,"Node")) { + if (p_argidx==0 && (String(p_method)=="get_node" || String(p_method)=="has_node") && ClassDB::is_parent_class(id.obj_type,"Node")) { List<PropertyInfo> props; - Globals::get_singleton()->get_property_list(&props); + GlobalConfig::get_singleton()->get_property_list(&props); for(List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { String s = E->get().name; if (!s.begins_with("autoload/")) continue; - // print_line("found "+s); + //print_line("found "+s); String name = s.get_slice("/",1); result.insert("\"/root/"+name+"\""); } @@ -1976,16 +1994,16 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No if (!context._class->owner) ci.value=context.base; - _find_type_arguments(p_node,p_line,id->name,ci,p_argidx,result,arghint); + _find_type_arguments(context,p_node,p_line,id->name,ci,p_argidx,result,arghint); //guess type.. /* List<MethodInfo> methods; - ObjectTypeDB::get_method_list(type,&methods); + ClassDB::get_method_list(type,&methods); for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { - //if (E->get().arguments.size()) - // result.insert(E->get().name+"("); - //else - // result.insert(E->get().name+"()"); + if (E->get().arguments.size()) + result.insert(E->get().name+"("); + else + result.insert(E->get().name+"()"); }*/ } break; @@ -1999,7 +2017,7 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No GDCompletionIdentifier ci; if (_guess_expression_type(context,op->arguments[0],p_line,ci)) { - _find_type_arguments(p_node,p_line,id->name,ci,p_argidx,result,arghint); + _find_type_arguments(context,p_node,p_line,id->name,ci,p_argidx,result,arghint); return; } @@ -2073,13 +2091,13 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No StringName type = nc->get_name(); List<String> constants; - ObjectTypeDB::get_integer_constant_list(type,&constants); + ClassDB::get_integer_constant_list(type,&constants); for(List<String>::Element *E=constants.front();E;E=E->next()) { result.insert(E->get()); } List<MethodInfo> methods; - ObjectTypeDB::get_method_list(type,&methods); + ClassDB::get_method_list(type,&methods); for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { if (E->get().arguments.size()) result.insert(E->get().name+"("); @@ -2103,12 +2121,10 @@ static void _find_call_arguments(GDCompletionContext& context,const GDParser::No } Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base_path, Object*p_owner, List<String>* r_options, String &r_call_hint) { - //print_line( p_code.replace(String::chr(0xFFFF),"<cursor>")); GDParser p; - //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false); - Error err = p.parse(p_code,p_base_path,false,"",true); + p.parse(p_code,p_base_path,false,"",true); bool isfunction=false; Set<String> options; @@ -2122,10 +2138,8 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base switch(p.get_completion_type()) { case GDParser::COMPLETION_NONE: { - print_line("No completion"); } break; case GDParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { - print_line("Built in type constant"); List<StringName> constants; Variant::get_numeric_constants_for_type(p.get_completion_built_in_constant(),&constants); for(List<StringName>::Element *E=constants.front();E;E=E->next()) { @@ -2141,9 +2155,29 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base _find_identifiers(context,p.get_completion_line(),isfunction,options); } break; case GDParser::COMPLETION_PARENT_FUNCTION: { - print_line("parent function"); } break; + case GDParser::COMPLETION_GET_NODE: { + + if (p_owner) { + List<String> opts; + p_owner->get_argument_options("get_node",0,&opts); + + for (List<String>::Element *E=opts.front();E;E=E->next()) { + + String opt = E->get().strip_edges(); + if (opt.begins_with("\"") && opt.ends_with("\"")) { + String idopt=opt.substr(1,opt.length()-2); + if (idopt.replace("/","_").is_valid_identifier()) { + options.insert(idopt); + } else { + options.insert(opt); + } + } + } + + } + } break; case GDParser::COMPLETION_METHOD: isfunction=true; case GDParser::COMPLETION_INDEX: { @@ -2158,7 +2192,29 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base GDCompletionIdentifier t; if (_guess_expression_type(context,static_cast<const GDParser::OperatorNode *>(node)->arguments[0],p.get_completion_line(),t)) { - if (t.type==Variant::OBJECT && t.obj_type!=StringName()) { + if (t.type==Variant::OBJECT && t.obj_type=="GDNativeClass") { + //native enum + Ref<GDNativeClass> gdn = t.value; + if (gdn.is_valid()) { + StringName cn = gdn->get_name(); + List<String> cnames; + ClassDB::get_integer_constant_list(cn,&cnames); + for (List<String>::Element *E=cnames.front();E;E=E->next()) { + options.insert(E->get()); + } + + List<PropertyInfo> pinfo; + ClassDB::get_property_list(cn,&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + if (E->get().usage&(PROPERTY_USAGE_GROUP|PROPERTY_USAGE_CATEGORY)) + continue; + if (E->get().name.find("/")!=-1) + continue; + options.insert(E->get().name); + } + } + } else if (t.type==Variant::OBJECT && t.obj_type!=StringName()) { Ref<GDScript> on_script; @@ -2210,7 +2266,6 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base if (code!="") { //if there is code, parse it. This way is slower but updates in real-time GDParser p; - //Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false); Error err = p.parse(scr->get_source_code(),scr->get_path().get_base_dir(),true,"",false); @@ -2293,10 +2348,23 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base if (!isfunction) { - ObjectTypeDB::get_integer_constant_list(t.obj_type,r_options); + ClassDB::get_integer_constant_list(t.obj_type,r_options); + + List<PropertyInfo> pinfo; + ClassDB::get_property_list(t.obj_type,&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + if (E->get().usage&(PROPERTY_USAGE_GROUP|PROPERTY_USAGE_CATEGORY)) + continue; + if (E->get().name.find("/")!=-1) + continue; + r_options->push_back(E->get().name); + } } + + List<MethodInfo> mi; - ObjectTypeDB::get_method_list(t.obj_type,&mi); + ClassDB::get_method_list(t.obj_type,&mi); for (List<MethodInfo>::Element *E=mi.front();E;E=E->next()) { if (E->get().name.begins_with("_")) @@ -2325,8 +2393,8 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base "# Key", "# MouseMotion", "# MouseButton", - "# JoystickMotion", - "# JoystickButton", + "# JoypadMotion", + "# JoypadButton", "# ScreenTouch", "# ScreenDrag", "# Action" @@ -2402,7 +2470,7 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base if (cid.obj_type!=StringName()) { List<MethodInfo> vm; - ObjectTypeDB::get_virtual_methods(cid.obj_type,&vm); + ClassDB::get_virtual_methods(cid.obj_type,&vm); for(List<MethodInfo>::Element *E=vm.front();E;E=E->next()) { MethodInfo &mi=E->get(); @@ -2427,7 +2495,24 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base } } } break; + case GDParser::COMPLETION_YIELD: { + const GDParser::Node *node = p.get_completion_node(); + + GDCompletionIdentifier t; + if (!_guess_expression_type(context,node,p.get_completion_line(),t)) + break; + + if (t.type==Variant::OBJECT && t.obj_type!=StringName()) { + + List<MethodInfo> sigs; + ClassDB::get_signal_list(t.obj_type,&sigs); + for (List<MethodInfo>::Element *E=sigs.front();E;E=E->next()) { + options.insert("\""+E->get().name+"\""); + } + } + + } break; } @@ -2512,3 +2597,456 @@ void GDScriptLanguage::auto_indent_code(String& p_code,int p_from_line,int p_to_ } } + +#ifdef TOOLS_ENABLED + +Error GDScriptLanguage::lookup_code(const String& p_code, const String& p_symbol,const String& p_base_path, Object*p_owner,LookupResult& r_result) { + + + //before parsing, try the usual stuff + if (ClassDB::class_exists(p_symbol)) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS; + r_result.class_name=p_symbol; + return OK; + } + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + Variant::Type t = Variant::Type(i); + if (Variant::get_type_name(t)==p_symbol) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS; + r_result.class_name=Variant::get_type_name(t); + return OK; + } + } + + for(int i=0;i<GDFunctions::FUNC_MAX;i++) { + if (GDFunctions::get_func_name(GDFunctions::Function(i))==p_symbol) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name="@GDScript"; + r_result.class_member=p_symbol; + return OK; + } + } + + GDParser p; + p.parse(p_code,p_base_path,false,"",true); + + if (p.get_completion_type()==GDParser::COMPLETION_NONE) + return ERR_CANT_RESOLVE; + + GDCompletionContext context; + + context._class=p.get_completion_class(); + context.block=p.get_completion_block(); + context.function=p.get_completion_function(); + context.base=p_owner; + context.base_path=p_base_path; + bool isfunction=false; + + switch(p.get_completion_type()) { + + case GDParser::COMPLETION_NONE: { + } break; + case GDParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name=Variant::get_type_name(p.get_completion_built_in_constant()); + r_result.class_member=p_symbol; + return OK; + + } break; + case GDParser::COMPLETION_FUNCTION: { + + + if (context._class && context._class->functions.size()) { + for(int i=0;i<context._class->functions.size();i++) { + if (context._class->functions[i]->name==p_symbol) { + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=context._class->functions[i]->line; + return OK; + } + } + } + + Ref<GDScript> parent = _get_parent_class(context); + while(parent.is_valid()) { + int line = parent->get_member_line(p_symbol); + if (line>=0) { + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=line; + r_result.script=parent; + return OK; + + } + + parent=parent->get_base(); + } + + GDCompletionIdentifier identifier = _get_native_class(context); + print_line("identifier: "+String(identifier.obj_type)); + + if (ClassDB::has_method(identifier.obj_type,p_symbol)) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name=identifier.obj_type; + r_result.class_member=p_symbol; + return OK; + } + + + } break; + case GDParser::COMPLETION_IDENTIFIER: { + + //check if a function + if (p.get_completion_identifier_is_function()) { + if (context._class && context._class->functions.size()) { + for(int i=0;i<context._class->functions.size();i++) { + if (context._class->functions[i]->name==p_symbol) { + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=context._class->functions[i]->line; + return OK; + } + } + } + + Ref<GDScript> parent = _get_parent_class(context); + while(parent.is_valid()) { + int line = parent->get_member_line(p_symbol); + if (line>=0) { + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=line; + r_result.script=parent; + return OK; + + } + + parent=parent->get_base(); + } + + GDCompletionIdentifier identifier = _get_native_class(context); + + + if (ClassDB::has_method(identifier.obj_type,p_symbol)) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name=identifier.obj_type; + r_result.class_member=p_symbol; + return OK; + } + } else { + + + GDCompletionIdentifier gdi = _get_native_class(context); + if (gdi.obj_type!=StringName()) { + bool valid; + Variant::Type t = ClassDB::get_property_type(gdi.obj_type,p_symbol,&valid); + if (t!=Variant::NIL && valid) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name=gdi.obj_type; + r_result.class_member=p_symbol; + return OK; + + } + } + + const GDParser::BlockNode *block=context.block; + //search in blocks going up (local var?) + while(block) { + + + + for (int i=0;i<block->statements.size();i++) { + + if (block->statements[i]->line>p.get_completion_line()) + continue; + + + if (block->statements[i]->type==GDParser::BlockNode::TYPE_LOCAL_VAR) { + + const GDParser::LocalVarNode *lv=static_cast<const GDParser::LocalVarNode *>(block->statements[i]); + + if (lv->assign && lv->name==p_symbol) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=block->statements[i]->line; + return OK; + } + } + } + block=block->parent_block; + } + + //guess from function arguments + if (context.function && context.function->name!=StringName()) { + + for(int i=0;i<context.function->arguments.size();i++) { + + if (context.function->arguments[i]==p_symbol) { + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=context.function->line; + return OK; + } + + } + } + + //guess in class constants + + for(int i=0;i<context._class->constant_expressions.size();i++) { + + if (context._class->constant_expressions[i].identifier==p_symbol) { + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=context._class->constant_expressions[i].expression->line; + return OK; + } + } + + //guess in class variables + if (!(context.function && context.function->_static)) { + + for(int i=0;i<context._class->variables.size();i++) { + + if (context._class->variables[i].identifier==p_symbol) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=context._class->variables[i].line; + return OK; + } + } + } + + //guess in autoloads as singletons + List<PropertyInfo> props; + GlobalConfig::get_singleton()->get_property_list(&props); + + for(List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) + continue; + String name = s.get_slice("/",1); + if (name==String(p_symbol)) { + + String path = GlobalConfig::get_singleton()->get(s); + if (path.begins_with("*")) { + String script =path.substr(1,path.length()); + + if (!script.ends_with(".gd")) { + //not a script, try find the script anyway, + //may have some success + script=script.get_basename()+".gd"; + } + + if (FileAccess::exists(script)) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=0; + r_result.script=ResourceLoader::load(script); + return OK; + } + } + } + } + + //global + for(Map<StringName,int>::Element *E=GDScriptLanguage::get_singleton()->get_global_map().front();E;E=E->next()) { + if (E->key()==p_symbol) { + + Variant value = GDScriptLanguage::get_singleton()->get_global_array()[E->get()]; + if (value.get_type()==Variant::OBJECT) { + Object *obj = value; + if (obj) { + + if (obj->cast_to<GDNativeClass>()) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS; + r_result.class_name=obj->cast_to<GDNativeClass>()->get_name(); + + } else { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS; + r_result.class_name=obj->get_class(); + } + return OK; + } + } else { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name="@Global Scope"; + r_result.class_member=p_symbol; + return OK; + } + } + + } +#if 0 + GDCompletionIdentifier identifier; + if (_guess_identifier_type(context,p.get_completion_line(),p_symbol,identifier)) { + + print_line("var type: "+Variant::get_type_name(identifier.type)); + if (identifier.script.is_valid()) { + print_line("var script: "+identifier.script->get_path()); + } + print_line("obj type: "+String(identifier.obj_type)); + print_line("value: "+String(identifier.value)); + } +#endif + } + + } break; + case GDParser::COMPLETION_PARENT_FUNCTION: { + + } break; + case GDParser::COMPLETION_METHOD: + isfunction=true; + case GDParser::COMPLETION_INDEX: { + + const GDParser::Node *node = p.get_completion_node(); + if (node->type!=GDParser::Node::TYPE_OPERATOR) + break; + + + + + GDCompletionIdentifier t; + if (_guess_expression_type(context,static_cast<const GDParser::OperatorNode *>(node)->arguments[0],p.get_completion_line(),t)) { + + if (t.type==Variant::OBJECT && t.obj_type=="GDNativeClass") { + //native enum + Ref<GDNativeClass> gdn = t.value; + if (gdn.is_valid()) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name=gdn->get_name();; + r_result.class_member=p_symbol; + return OK; + + } + } else if (t.type==Variant::OBJECT && t.obj_type!=StringName()) { + + Ref<GDScript> on_script; + + if (t.value.get_type()) { + Object *obj=t.value; + + + if (obj) { + + + on_script=obj->get_script(); + + if (on_script.is_valid()) { + int loc = on_script->get_member_line(p_symbol); + if (loc>=0) { + r_result.script=on_script; + r_result.type=ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; + r_result.location=loc; + return OK; + } + } + } + } + + if (ClassDB::has_method(t.obj_type,p_symbol)) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name=t.obj_type; + r_result.class_member=p_symbol; + return OK; + + } + + bool success; + ClassDB::get_integer_constant(t.obj_type,p_symbol,&success); + if (success) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name=t.obj_type; + r_result.class_member=p_symbol; + return OK; + } + + + ClassDB::get_property_type(t.obj_type,p_symbol,&success); + + if (success) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name=t.obj_type; + r_result.class_member=p_symbol; + return OK; + } + + + } else { + + Variant::CallError ce; + Variant v = Variant::construct(t.type,NULL,0,ce); + + bool valid; + v.get_numeric_constant_value(t.type,p_symbol,&valid); + if (valid) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name=Variant::get_type_name(t.type); + r_result.class_member=p_symbol; + return OK; + } + + //todo check all inputevent types for property + + v.get(p_symbol,&valid); + + if (valid) { + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY; + r_result.class_name=Variant::get_type_name(t.type); + r_result.class_member=p_symbol; + return OK; + } + + if (v.has_method(p_symbol)) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name=Variant::get_type_name(t.type); + r_result.class_member=p_symbol; + return OK; + + } + + + } + } + + + } break; + case GDParser::COMPLETION_CALL_ARGUMENTS: { + + return ERR_CANT_RESOLVE; + } break; + case GDParser::COMPLETION_VIRTUAL_FUNC: { + + GDCompletionIdentifier cid = _get_native_class(context); + + if (cid.obj_type!=StringName()) { + List<MethodInfo> vm; + ClassDB::get_virtual_methods(cid.obj_type,&vm); + for(List<MethodInfo>::Element *E=vm.front();E;E=E->next()) { + + if (p_symbol==E->get().name) { + + r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name=cid.obj_type; + r_result.class_member=p_symbol; + return OK; + + } + } + } + } break; + case GDParser::COMPLETION_YIELD: { + + return ERR_CANT_RESOLVE; + + } break; + + } + + + return ERR_CANT_RESOLVE; +} + +#endif diff --git a/modules/gdscript/gd_function.cpp b/modules/gdscript/gd_function.cpp new file mode 100644 index 000000000..665998860 --- /dev/null +++ b/modules/gdscript/gd_function.cpp @@ -0,0 +1,1499 @@ +#include "gd_function.h" +#include "gd_script.h" +#include "os/os.h" +#include "gd_functions.h" + +Variant *GDFunction::_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self, Variant *p_stack,String& r_error) const{ + + int address = p_address&ADDR_MASK; + + //sequential table (jump table generated by compiler) + switch((p_address&ADDR_TYPE_MASK)>>ADDR_BITS) { + + case ADDR_TYPE_SELF: { + + if (!p_instance) { + r_error="Cannot access self without instance."; + return NULL; + } + return &self; + } break; + case ADDR_TYPE_CLASS: { + + return &p_script->_static_ref; + } break; + case ADDR_TYPE_MEMBER: { + //member indexing is O(1) + if (!p_instance) { + r_error="Cannot access member without instance."; + return NULL; + } + return &p_instance->members[address]; + } break; + case ADDR_TYPE_CLASS_CONSTANT: { + + //todo change to index! + GDScript *o=p_script; + ERR_FAIL_INDEX_V(address,_global_names_count,NULL); + const StringName *sn = &_global_names_ptr[address]; + + while(o) { + GDScript *s=o; + while(s) { + + Map<StringName,Variant>::Element *E=s->constants.find(*sn); + if (E) { + return &E->get(); + } + s=s->_base; + } + o=o->_owner; + } + + + ERR_EXPLAIN("GDCompiler bug.."); + ERR_FAIL_V(NULL); + } break; + case ADDR_TYPE_LOCAL_CONSTANT: { + ERR_FAIL_INDEX_V(address,_constant_count,NULL); + return &_constants_ptr[address]; + } break; + case ADDR_TYPE_STACK: + case ADDR_TYPE_STACK_VARIABLE: { + ERR_FAIL_INDEX_V(address,_stack_size,NULL); + return &p_stack[address]; + } break; + case ADDR_TYPE_GLOBAL: { + + + ERR_FAIL_INDEX_V(address,GDScriptLanguage::get_singleton()->get_global_array_size(),NULL); + + + return &GDScriptLanguage::get_singleton()->get_global_array()[address]; + } break; + case ADDR_TYPE_NIL: { + return &nil; + } break; + } + + ERR_EXPLAIN("Bad Code! (Addressing Mode)"); + ERR_FAIL_V(NULL); + return NULL; +} + + +String GDFunction::_get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const { + + + + String err_text; + + if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { + int errorarg=p_err.argument; + err_text="Invalid type in "+p_where+". Cannot convert argument "+itos(errorarg+1)+" from "+Variant::get_type_name(argptrs[errorarg]->get_type())+" to "+Variant::get_type_name(p_err.expected)+"."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { + err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { + err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { + err_text="Invalid call. Nonexistent "+p_where+"."; + } else if (p_err.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) { + err_text="Attempt to call "+p_where+" on a null instance."; + } else { + err_text="Bug, call error: #"+itos(p_err.error); + } + + return err_text; + +} + +static String _get_var_type(const Variant* p_type) { + + String basestr; + + if (p_type->get_type()==Variant::OBJECT) { + Object *bobj = *p_type; + if (!bobj) { + basestr = "null instance"; + } else { +#ifdef DEBUG_ENABLED + if (ObjectDB::instance_validate(bobj)) { + if (bobj->get_script_instance()) + basestr= bobj->get_class()+" ("+bobj->get_script_instance()->get_script()->get_path().get_file()+")"; + else + basestr = bobj->get_class(); + } else { + basestr="previously freed instance"; + } + +#else + basestr="Object"; +#endif + } + + } else { + basestr = Variant::get_type_name(p_type->get_type()); + } + + return basestr; + +} + +Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError& r_err, CallState *p_state) { + + + if (!_code_ptr) { + + return Variant(); + } + + r_err.error=Variant::CallError::CALL_OK; + + Variant self; + Variant retvalue; + Variant *stack = NULL; + Variant **call_args; + int defarg=0; + +#ifdef DEBUG_ENABLED + + //GDScriptLanguage::get_singleton()->calls++; + +#endif + + uint32_t alloca_size=0; + GDScript *_class; + int ip=0; + int line=_initial_line; + + + + if (p_state) { + //use existing (supplied) state (yielded) + stack=(Variant*)p_state->stack.ptr(); + call_args=(Variant**)&p_state->stack[sizeof(Variant)*p_state->stack_size]; + line=p_state->line; + ip=p_state->ip; + alloca_size=p_state->stack.size(); + _class=p_state->_class; + p_instance=p_state->instance; + defarg=p_state->defarg; + self=p_state->self; + //stack[p_state->result_pos]=p_state->result; //assign stack with result + + } else { + + if (p_argcount!=_argument_count) { + + if (p_argcount>_argument_count) { + + r_err.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_err.argument=_argument_count; + + + return Variant(); + } else if (p_argcount < _argument_count - _default_arg_count) { + + r_err.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_err.argument=_argument_count - _default_arg_count; + return Variant(); + } else { + + defarg=_argument_count-p_argcount; + } + } + + alloca_size = sizeof(Variant*)*_call_size + sizeof(Variant)*_stack_size; + + if (alloca_size) { + + uint8_t *aptr = (uint8_t*)alloca(alloca_size); + + if (_stack_size) { + + stack=(Variant*)aptr; + for(int i=0;i<p_argcount;i++) + memnew_placement(&stack[i],Variant(*p_args[i])); + for(int i=p_argcount;i<_stack_size;i++) + memnew_placement(&stack[i],Variant); + } else { + stack=NULL; + } + + if (_call_size) { + + call_args = (Variant**)&aptr[sizeof(Variant)*_stack_size]; + } else { + + call_args=NULL; + } + + + } else { + stack=NULL; + call_args=NULL; + } + + if (p_instance) { + if (p_instance->base_ref && static_cast<Reference*>(p_instance->owner)->is_referenced()) { + + self=REF(static_cast<Reference*>(p_instance->owner)); + } else { + self=p_instance->owner; + } + _class=p_instance->script.ptr(); + } else { + _class=_script; + } + } + + String err_text; + +#ifdef DEBUG_ENABLED + + if (ScriptDebugger::get_singleton()) + GDScriptLanguage::get_singleton()->enter_function(p_instance,this,stack,&ip,&line); + +#define CHECK_SPACE(m_space)\ + ERR_BREAK((ip+m_space)>_code_size) + +#define GET_VARIANT_PTR(m_v,m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);\ + if (!m_v)\ + break; + + +#else +#define CHECK_SPACE(m_space) +#define GET_VARIANT_PTR(m_v,m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text); + +#endif + + +#ifdef DEBUG_ENABLED + + uint64_t function_start_time; + uint64_t function_call_time; + + if (GDScriptLanguage::get_singleton()->profiling) { + function_start_time=OS::get_singleton()->get_ticks_usec(); + function_call_time=0; + profile.call_count++; + profile.frame_call_count++; + } +#endif + bool exit_ok=false; + + while(ip<_code_size) { + + + int last_opcode=_code_ptr[ip]; + switch(_code_ptr[ip]) { + + case OPCODE_OPERATOR: { + + CHECK_SPACE(5); + + bool valid; + Variant::Operator op = (Variant::Operator)_code_ptr[ip+1]; + ERR_BREAK(op>=Variant::OP_MAX); + + GET_VARIANT_PTR(a,2); + GET_VARIANT_PTR(b,3); + GET_VARIANT_PTR(dst,4); + +#ifdef DEBUG_ENABLED + Variant ret; + Variant::evaluate(op,*a,*b,ret,valid); +#else + Variant::evaluate(op,*a,*b,*dst,valid); +#endif + + if (!valid) { +#ifdef DEBUG_ENABLED + + if (ret.get_type()==Variant::STRING) { + //return a string when invalid with the error + err_text=ret; + err_text += " in operator '"+Variant::get_operator_name(op)+"'."; + } else { + err_text="Invalid operands '"+Variant::get_type_name(a->get_type())+"' and '"+Variant::get_type_name(b->get_type())+"' in operator '"+Variant::get_operator_name(op)+"'."; + } +#endif + break; + + } +#ifdef DEBUG_ENABLED + *dst=ret; +#endif + + ip+=5; + + } continue; + case OPCODE_EXTENDS_TEST: { + + CHECK_SPACE(4); + + GET_VARIANT_PTR(a,1); + GET_VARIANT_PTR(b,2); + GET_VARIANT_PTR(dst,3); + +#ifdef DEBUG_ENABLED + + if (a->get_type()!=Variant::OBJECT || a->operator Object*()==NULL) { + + err_text="Left operand of 'extends' is not an instance of anything."; + break; + + } + if (b->get_type()!=Variant::OBJECT || b->operator Object*()==NULL) { + + err_text="Right operand of 'extends' is not a class."; + break; + + } +#endif + + + Object *obj_A = *a; + Object *obj_B = *b; + + + GDScript *scr_B = obj_B->cast_to<GDScript>(); + + bool extends_ok=false; + + if (scr_B) { + //if B is a script, the only valid condition is that A has an instance which inherits from the script + //in other situation, this shoul return false. + + if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language()==GDScriptLanguage::get_singleton()) { + + GDScript *cmp = static_cast<GDScript*>(obj_A->get_script_instance()->get_script().ptr()); + //bool found=false; + while(cmp) { + + if (cmp==scr_B) { + //inherits from script, all ok + extends_ok=true; + break; + + } + + cmp=cmp->_base; + } + + } + + + } else { + + GDNativeClass *nc= obj_B->cast_to<GDNativeClass>(); + + if (!nc) { + + err_text="Right operand of 'extends' is not a class (type: '"+obj_B->get_class()+"')."; + break; + } + + extends_ok=ClassDB::is_parent_class(obj_A->get_class_name(),nc->get_name()); + } + + *dst=extends_ok; + ip+=4; + + } continue; + case OPCODE_SET: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(dst,1); + GET_VARIANT_PTR(index,2); + GET_VARIANT_PTR(value,3); + + bool valid; + dst->set(*index,*value,&valid); + + if (!valid) { + String v = index->operator String(); + if (v!="") { + v="'"+v+"'"; + } else { + v="of type '"+_get_var_type(index)+"'"; + } + err_text="Invalid set index "+v+" (on base: '"+_get_var_type(dst)+"')."; + break; + } + + ip+=4; + } continue; + case OPCODE_GET: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(src,1); + GET_VARIANT_PTR(index,2); + GET_VARIANT_PTR(dst,3); + + bool valid; +#ifdef DEBUG_ENABLED + //allow better error message in cases where src and dst are the same stack position + Variant ret = src->get(*index,&valid); +#else + *dst = src->get(*index,&valid); + +#endif + if (!valid) { + String v = index->operator String(); + if (v!="") { + v="'"+v+"'"; + } else { + v="of type '"+_get_var_type(index)+"'"; + } + err_text="Invalid get index "+v+" (on base: '"+_get_var_type(src)+"')."; + break; + } +#ifdef DEBUG_ENABLED + *dst=ret; +#endif + ip+=4; + } continue; + case OPCODE_SET_NAMED: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(dst,1); + GET_VARIANT_PTR(value,3); + + int indexname = _code_ptr[ip+2]; + + ERR_BREAK(indexname<0 || indexname>=_global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; + dst->set_named(*index,*value,&valid); + + if (!valid) { + String err_type; + err_text="Invalid set index '"+String(*index)+"' (on base: '"+_get_var_type(dst)+"')."; + break; + } + + ip+=4; + } continue; + case OPCODE_GET_NAMED: { + + + CHECK_SPACE(4); + + GET_VARIANT_PTR(src,1); + GET_VARIANT_PTR(dst,3); + + int indexname = _code_ptr[ip+2]; + + ERR_BREAK(indexname<0 || indexname>=_global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; +#ifdef DEBUG_ENABLED + //allow better error message in cases where src and dst are the same stack position + Variant ret = src->get_named(*index,&valid); + +#else + *dst = src->get_named(*index,&valid); +#endif + + if (!valid) { + if (src->has_method(*index)) { + err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"'). Did you mean '."+index->operator String()+"()' ?"; + } else { + err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"')."; + } + break; + } +#ifdef DEBUG_ENABLED + *dst=ret; +#endif + ip+=4; + } continue; + case OPCODE_SET_MEMBER: { + + CHECK_SPACE(3); + int indexname = _code_ptr[ip+1]; + ERR_BREAK(indexname<0 || indexname>=_global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + GET_VARIANT_PTR(src,2); + + bool valid; + bool ok = ClassDB::set_property(p_instance->owner,*index,*src,&valid); +#ifdef DEBUG_ENABLED + if (!ok) { + err_text="Internal error setting property: "+String(*index); + break; + } else if (!valid) { + err_text="Error setting property '"+String(*index)+"' with value of type "+Variant::get_type_name(src->get_type())+"."; + break; + + } +#endif + ip+=3; + } continue; + case OPCODE_GET_MEMBER: { + + CHECK_SPACE(3); + int indexname = _code_ptr[ip+1]; + ERR_BREAK(indexname<0 || indexname>=_global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + GET_VARIANT_PTR(dst,2); + bool ok = ClassDB::get_property(p_instance->owner,*index,*dst); + +#ifdef DEBUG_ENABLED + if (!ok) { + err_text="Internal error getting property: "+String(*index); + break; + } +#endif + ip+=3; + + } continue; + case OPCODE_ASSIGN: { + + CHECK_SPACE(3); + GET_VARIANT_PTR(dst,1); + GET_VARIANT_PTR(src,2); + + *dst = *src; + + ip+=3; + + } continue; + case OPCODE_ASSIGN_TRUE: { + + CHECK_SPACE(2); + GET_VARIANT_PTR(dst,1); + + *dst = true; + + ip+=2; + } continue; + case OPCODE_ASSIGN_FALSE: { + + CHECK_SPACE(2); + GET_VARIANT_PTR(dst,1); + + *dst = false; + + ip+=2; + } continue; + case OPCODE_CONSTRUCT: { + + CHECK_SPACE(2); + Variant::Type t=Variant::Type(_code_ptr[ip+1]); + int argc=_code_ptr[ip+2]; + CHECK_SPACE(argc+2); + Variant **argptrs = call_args; + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,3+i); + argptrs[i]=v; + } + + GET_VARIANT_PTR(dst,3+argc); + Variant::CallError err; + *dst = Variant::construct(t,(const Variant**)argptrs,argc,err); + + if (err.error!=Variant::CallError::CALL_OK) { + + err_text=_get_call_error(err,"'"+Variant::get_type_name(t)+"' constructor",(const Variant**)argptrs); + break; + } + + ip+=4+argc; + //construct a basic type + } continue; + case OPCODE_CONSTRUCT_ARRAY: { + + CHECK_SPACE(1); + int argc=_code_ptr[ip+1]; + Array array; //arrays are always shared + array.resize(argc); + CHECK_SPACE(argc+2); + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,2+i); + array[i]=*v; + + } + + GET_VARIANT_PTR(dst,2+argc); + + *dst=array; + + ip+=3+argc; + + } continue; + case OPCODE_CONSTRUCT_DICTIONARY: { + + CHECK_SPACE(1); + int argc=_code_ptr[ip+1]; + Dictionary dict; //arrays are always shared + + CHECK_SPACE(argc*2+2); + + for(int i=0;i<argc;i++) { + + GET_VARIANT_PTR(k,2+i*2+0); + GET_VARIANT_PTR(v,2+i*2+1); + dict[*k]=*v; + + } + + GET_VARIANT_PTR(dst,2+argc*2); + + *dst=dict; + + ip+=3+argc*2; + + } continue; + case OPCODE_CALL_RETURN: + case OPCODE_CALL: { + + + CHECK_SPACE(4); + bool call_ret = _code_ptr[ip]==OPCODE_CALL_RETURN; + + int argc=_code_ptr[ip+1]; + GET_VARIANT_PTR(base,2); + int nameg=_code_ptr[ip+3]; + + ERR_BREAK(nameg<0 || nameg>=_global_names_count); + const StringName *methodname = &_global_names_ptr[nameg]; + + ERR_BREAK(argc<0); + ip+=4; + CHECK_SPACE(argc+1); + Variant **argptrs = call_args; + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,i); + argptrs[i]=v; + } + +#ifdef DEBUG_ENABLED + uint64_t call_time; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time=OS::get_singleton()->get_ticks_usec(); + } + +#endif + Variant::CallError err; + if (call_ret) { + + GET_VARIANT_PTR(ret,argc); + base->call_ptr(*methodname,(const Variant**)argptrs,argc,ret,err); + } else { + + base->call_ptr(*methodname,(const Variant**)argptrs,argc,NULL,err); + } +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time+=OS::get_singleton()->get_ticks_usec() - call_time; + } +#endif + + if (err.error!=Variant::CallError::CALL_OK) { + + + String methodstr = *methodname; + String basestr = _get_var_type(base); + + if (methodstr=="call") { + if (argc>=1) { + methodstr=String(*argptrs[0])+" (via call)"; + if (err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument-=1; + } + } + } if (methodstr=="free") { + + if (err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { + + if (base->is_ref()) { + err_text="Attempted to free a reference."; + break; + } else if (base->get_type()==Variant::OBJECT) { + + err_text="Attempted to free a locked object (calling or emitting)."; + break; + } + } + } + err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs); + break; + } + + //_call_func(NULL,base,*methodname,ip,argc,p_instance,stack); + ip+=argc+1; + + } continue; + case OPCODE_CALL_BUILT_IN: { + + CHECK_SPACE(4); + + GDFunctions::Function func = GDFunctions::Function(_code_ptr[ip+1]); + int argc=_code_ptr[ip+2]; + ERR_BREAK(argc<0); + + ip+=3; + CHECK_SPACE(argc+1); + Variant **argptrs = call_args; + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,i); + argptrs[i]=v; + } + + GET_VARIANT_PTR(dst,argc); + + Variant::CallError err; + + GDFunctions::call(func,(const Variant**)argptrs,argc,*dst,err); + + if (err.error!=Variant::CallError::CALL_OK) { + + + String methodstr = GDFunctions::get_func_name(func); + if (dst->get_type()==Variant::STRING) { + //call provided error string + err_text="Error calling built-in function '"+methodstr+"': "+String(*dst); + } else { + err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs); + } + break; + } + ip+=argc+1; + + } continue; + case OPCODE_CALL_SELF: { + + + } break; + case OPCODE_CALL_SELF_BASE: { + + CHECK_SPACE(2); + int self_fun = _code_ptr[ip+1]; +#ifdef DEBUG_ENABLED + + if (self_fun<0 || self_fun>=_global_names_count) { + + err_text="compiler bug, function name not found"; + break; + } +#endif + const StringName *methodname = &_global_names_ptr[self_fun]; + + int argc=_code_ptr[ip+2]; + + CHECK_SPACE(2+argc+1); + + Variant **argptrs = call_args; + + for(int i=0;i<argc;i++) { + GET_VARIANT_PTR(v,i+3); + argptrs[i]=v; + } + + GET_VARIANT_PTR(dst,argc+3); + + const GDScript *gds = _script; + + + const Map<StringName,GDFunction*>::Element *E=NULL; + while (gds->base.ptr()) { + gds=gds->base.ptr(); + E=gds->member_functions.find(*methodname); + if (E) + break; + } + + Variant::CallError err; + + if (E) { + + *dst=E->get()->call(p_instance,(const Variant**)argptrs,argc,err); + } else if (gds->native.ptr()) { + + if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) { + + MethodBind *mb = ClassDB::get_method(gds->native->get_name(),*methodname); + if (!mb) { + err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } else { + *dst=mb->call(p_instance->owner,(const Variant**)argptrs,argc,err); + } + } else { + err.error=Variant::CallError::CALL_OK; + } + } else { + + if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) { + err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } else { + err.error=Variant::CallError::CALL_OK; + } + } + + + if (err.error!=Variant::CallError::CALL_OK) { + + + String methodstr = *methodname; + err_text=_get_call_error(err,"function '"+methodstr+"'",(const Variant**)argptrs); + + break; + } + + ip+=4+argc; + + } continue; + case OPCODE_YIELD: + case OPCODE_YIELD_SIGNAL: { + + int ipofs=1; + if (_code_ptr[ip]==OPCODE_YIELD_SIGNAL) { + CHECK_SPACE(4); + ipofs+=2; + } else { + CHECK_SPACE(2); + + } + + Ref<GDFunctionState> gdfs = memnew( GDFunctionState ); + gdfs->function=this; + + gdfs->state.stack.resize(alloca_size); + //copy variant stack + for(int i=0;i<_stack_size;i++) { + memnew_placement(&gdfs->state.stack[sizeof(Variant)*i],Variant(stack[i])); + } + gdfs->state.stack_size=_stack_size; + gdfs->state.self=self; + gdfs->state.alloca_size=alloca_size; + gdfs->state._class=_class; + gdfs->state.ip=ip+ipofs; + gdfs->state.line=line; + gdfs->state.instance_id=(p_instance && p_instance->get_owner())?p_instance->get_owner()->get_instance_ID():0; + gdfs->state.script_id=_class->get_instance_ID(); + //gdfs->state.result_pos=ip+ipofs-1; + gdfs->state.defarg=defarg; + gdfs->state.instance=p_instance; + gdfs->function=this; + + retvalue=gdfs; + + if (_code_ptr[ip]==OPCODE_YIELD_SIGNAL) { + GET_VARIANT_PTR(argobj,1); + GET_VARIANT_PTR(argname,2); + //do the oneshot connect + + if (argobj->get_type()!=Variant::OBJECT) { + err_text="First argument of yield() not of type object."; + break; + } + if (argname->get_type()!=Variant::STRING) { + err_text="Second argument of yield() not a string (for signal name)."; + break; + } + Object *obj=argobj->operator Object *(); + String signal = argname->operator String(); +#ifdef DEBUG_ENABLED + + if (!obj) { + err_text="First argument of yield() is null."; + break; + } + if (ScriptDebugger::get_singleton()) { + if (!ObjectDB::instance_validate(obj)) { + err_text="First argument of yield() is a previously freed instance."; + break; + } + } + if (signal.length()==0) { + + err_text="Second argument of yield() is an empty string (for signal name)."; + break; + } + +#endif + Error err = obj->connect(signal,gdfs.ptr(),"_signal_callback",varray(gdfs),Object::CONNECT_ONESHOT); + if (err!=OK) { + err_text="Error connecting to signal: "+signal+" during yield()."; + break; + } + + + } + + exit_ok=true; + + } break; + case OPCODE_YIELD_RESUME: { + + CHECK_SPACE(2); + if (!p_state) { + err_text=("Invalid Resume (bug?)"); + break; + } + GET_VARIANT_PTR(result,1); + *result=p_state->result; + ip+=2; + + } continue; + case OPCODE_JUMP: { + + CHECK_SPACE(2); + int to = _code_ptr[ip+1]; + + ERR_BREAK(to<0 || to>_code_size); + ip=to; + + } continue; + case OPCODE_JUMP_IF: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(test,1); + + bool valid; + bool result = test->booleanize(valid); +#ifdef DEBUG_ENABLED + if (!valid) { + + err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); + break; + } +#endif + if (result) { + int to = _code_ptr[ip+2]; + ERR_BREAK(to<0 || to>_code_size); + ip=to; + continue; + } + ip+=3; + } continue; + case OPCODE_JUMP_IF_NOT: { + + CHECK_SPACE(3); + + GET_VARIANT_PTR(test,1); + + bool valid; + bool result = test->booleanize(valid); +#ifdef DEBUG_ENABLED + if (!valid) { + + err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); + break; + } +#endif + if (!result) { + int to = _code_ptr[ip+2]; + ERR_BREAK(to<0 || to>_code_size); + ip=to; + continue; + } + ip+=3; + } continue; + case OPCODE_JUMP_TO_DEF_ARGUMENT: { + + CHECK_SPACE(2); + ip=_default_arg_ptr[defarg]; + + } continue; + case OPCODE_RETURN: { + + CHECK_SPACE(2); + GET_VARIANT_PTR(r,1); + retvalue=*r; + exit_ok=true; + + } break; + case OPCODE_ITERATE_BEGIN: { + + CHECK_SPACE(8); //space for this an regular iterate + + GET_VARIANT_PTR(counter,1); + GET_VARIANT_PTR(container,2); + + bool valid; + if (!container->iter_init(*counter,valid)) { + if (!valid) { + err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"'."; + break; + } + int jumpto=_code_ptr[ip+3]; + ERR_BREAK(jumpto<0 || jumpto>_code_size); + ip=jumpto; + continue; + } + GET_VARIANT_PTR(iterator,4); + + + *iterator=container->iter_get(*counter,valid); + if (!valid) { + err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"'."; + break; + } + + + ip+=5; //skip regular iterate which is always next + + } continue; + case OPCODE_ITERATE: { + + CHECK_SPACE(4); + + GET_VARIANT_PTR(counter,1); + GET_VARIANT_PTR(container,2); + + bool valid; + if (!container->iter_next(*counter,valid)) { + if (!valid) { + err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"' (type changed since first iteration?)."; + break; + } + int jumpto=_code_ptr[ip+3]; + ERR_BREAK(jumpto<0 || jumpto>_code_size); + ip=jumpto; + continue; + } + GET_VARIANT_PTR(iterator,4); + + *iterator=container->iter_get(*counter,valid); + if (!valid) { + err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"' (but was obtained on first iteration?)."; + break; + } + + ip+=5; //loop again + } continue; + case OPCODE_ASSERT: { + CHECK_SPACE(2); + GET_VARIANT_PTR(test,1); + +#ifdef DEBUG_ENABLED + bool valid; + bool result = test->booleanize(valid); + + + if (!valid) { + + err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); + break; + } + + + if (!result) { + + err_text="Assertion failed."; + break; + } + +#endif + + ip+=2; + } continue; + case OPCODE_BREAKPOINT: { +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement",true); + } +#endif + ip+=1; + } continue; + case OPCODE_LINE: { + CHECK_SPACE(2); + + line=_code_ptr[ip+1]; + ip+=2; + + if (ScriptDebugger::get_singleton()) { + // line + bool do_break=false; + + if (ScriptDebugger::get_singleton()->get_lines_left()>0) { + + if (ScriptDebugger::get_singleton()->get_depth()<=0) + ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()<=0) + do_break=true; + } + + if (ScriptDebugger::get_singleton()->is_breakpoint(line,source)) + do_break=true; + + if (do_break) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint",true); + } + + ScriptDebugger::get_singleton()->line_poll(); + + } + } continue; + case OPCODE_END: { + + exit_ok=true; + break; + + } break; + default: { + + err_text="Illegal opcode "+itos(_code_ptr[ip])+" at address "+itos(ip); + } break; + + } + + if (exit_ok) + break; + //error + // function, file, line, error, explanation + String err_file; + if (p_instance) + err_file=p_instance->script->path; + else if (_class) + err_file=_class->path; + if (err_file=="") + err_file="<built-in>"; + String err_func = name; + if (p_instance && p_instance->script->name!="") + err_func=p_instance->script->name+"."+err_func; + int err_line=line; + if (err_text=="") { + err_text="Internal Script Error! - opcode #"+itos(last_opcode)+" (report please)."; + } + + if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) { + // debugger break did not happen + + _err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,err_text.utf8().get_data(),ERR_HANDLER_SCRIPT); + } + + + break; + } + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; + profile.total_time+=time_taken; + profile.self_time+=time_taken-function_call_time; + profile.frame_total_time+=time_taken; + profile.frame_self_time+=time_taken-function_call_time; + GDScriptLanguage::get_singleton()->script_frame_time+=time_taken-function_call_time; + + } + +#endif + if (ScriptDebugger::get_singleton()) + GDScriptLanguage::get_singleton()->exit_function(); + + + if (_stack_size) { + //free stack + for(int i=0;i<_stack_size;i++) + stack[i].~Variant(); + } + + return retvalue; + +} + +const int* GDFunction::get_code() const { + + return _code_ptr; +} +int GDFunction::get_code_size() const{ + + return _code_size; +} + +Variant GDFunction::get_constant(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx,constants.size(),"<errconst>"); + return constants[p_idx]; +} + +StringName GDFunction::get_global_name(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx,global_names.size(),"<errgname>"); + return global_names[p_idx]; +} + +int GDFunction::get_default_argument_count() const { + + return default_arguments.size(); +} +int GDFunction::get_default_argument_addr(int p_arg) const{ + + ERR_FAIL_INDEX_V(p_arg,default_arguments.size(),-1); + return default_arguments[p_arg]; +} + + +StringName GDFunction::get_name() const { + + return name; +} + +int GDFunction::get_max_stack_size() const { + + return _stack_size; +} + +struct _GDFKC { + + int order; + List<int> pos; +}; + +struct _GDFKCS { + + int order; + StringName id; + int pos; + + bool operator<(const _GDFKCS &p_r) const { + + return order<p_r.order; + } +}; + +void GDFunction::debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const { + + + int oc=0; + Map<StringName,_GDFKC> sdmap; + for( const List<StackDebug>::Element *E=stack_debug.front();E;E=E->next()) { + + const StackDebug &sd=E->get(); + if (sd.line>p_line) + break; + + if (sd.added) { + + if (!sdmap.has(sd.identifier)) { + _GDFKC d; + d.order=oc++; + d.pos.push_back(sd.pos); + sdmap[sd.identifier]=d; + + } else { + sdmap[sd.identifier].pos.push_back(sd.pos); + } + } else { + + + ERR_CONTINUE(!sdmap.has(sd.identifier)); + + sdmap[sd.identifier].pos.pop_back(); + if (sdmap[sd.identifier].pos.empty()) + sdmap.erase(sd.identifier); + } + + } + + + List<_GDFKCS> stackpositions; + for(Map<StringName,_GDFKC>::Element *E=sdmap.front();E;E=E->next() ) { + + _GDFKCS spp; + spp.id=E->key(); + spp.order=E->get().order; + spp.pos=E->get().pos.back()->get(); + stackpositions.push_back(spp); + } + + stackpositions.sort(); + + for(List<_GDFKCS>::Element *E=stackpositions.front();E;E=E->next()) { + + Pair<StringName,int> p; + p.first=E->get().id; + p.second=E->get().pos; + r_stackvars->push_back(p); + } + + +} + +#if 0 +void GDFunction::clear() { + + name=StringName(); + constants.clear(); + _stack_size=0; + code.clear(); + _constants_ptr=NULL; + _constant_count=0; + _global_names_ptr=NULL; + _global_names_count=0; + _code_ptr=NULL; + _code_size=0; + +} +#endif +GDFunction::GDFunction() : function_list(this) { + + _stack_size=0; + _call_size=0; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; + name="<anonymous>"; +#ifdef DEBUG_ENABLED + _func_cname=NULL; + + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->lock(); + } + GDScriptLanguage::get_singleton()->function_list.add(&function_list); + + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->unlock(); + } + + profile.call_count=0; + profile.self_time=0; + profile.total_time=0; + profile.frame_call_count=0; + profile.frame_self_time=0; + profile.frame_total_time=0; + profile.last_frame_call_count=0; + profile.last_frame_self_time=0; + profile.last_frame_total_time=0; + +#endif +} + +GDFunction::~GDFunction() { +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->lock(); + } + GDScriptLanguage::get_singleton()->function_list.remove(&function_list); + + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->unlock(); + } +#endif +} + +///////////////////// + + +Variant GDFunctionState::_signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error) { + +#ifdef DEBUG_ENABLED + if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (state.script_id && !ObjectDB::get_instance(state.script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + + Variant arg; + r_error.error=Variant::CallError::CALL_OK; + + ERR_FAIL_COND_V(!function,Variant()); + + if (p_argcount==0) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=1; + return Variant(); + } else if (p_argcount==1) { + //noooneee + } else if (p_argcount==2) { + arg=*p_args[0]; + } else { + Array extra_args; + for(int i=0;i<p_argcount-1;i++) { + extra_args.push_back(*p_args[i]); + } + arg=extra_args; + } + + Ref<GDFunctionState> self = *p_args[p_argcount-1]; + + if (self.is_null()) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=p_argcount-1; + r_error.expected=Variant::OBJECT; + return Variant(); + } + + state.result=arg; + Variant ret = function->call(NULL,NULL,0,r_error,&state); + function=NULL; //cleaned up; + state.result=Variant(); + return ret; +} + + +bool GDFunctionState::is_valid() const { + + return function!=NULL; +} + +Variant GDFunctionState::resume(const Variant& p_arg) { + + ERR_FAIL_COND_V(!function,Variant()); +#ifdef DEBUG_ENABLED + if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (state.script_id && !ObjectDB::get_instance(state.script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + + state.result=p_arg; + Variant::CallError err; + Variant ret = function->call(NULL,NULL,0,err,&state); + function=NULL; //cleaned up; + state.result=Variant(); + return ret; +} + + +void GDFunctionState::_bind_methods() { + + ClassDB::bind_method(_MD("resume:Variant","arg"),&GDFunctionState::resume,DEFVAL(Variant())); + ClassDB::bind_method(_MD("is_valid"),&GDFunctionState::is_valid); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT,"_signal_callback",&GDFunctionState::_signal_callback,MethodInfo("_signal_callback")); + +} + +GDFunctionState::GDFunctionState() { + + function=NULL; +} + +GDFunctionState::~GDFunctionState() { + + if (function!=NULL) { + //never called, deinitialize stack + for(int i=0;i<state.stack_size;i++) { + Variant *v=(Variant*)&state.stack[sizeof(Variant)*i]; + v->~Variant(); + } + } +} + diff --git a/modules/gdscript/gd_function.h b/modules/gdscript/gd_function.h new file mode 100644 index 000000000..e5262e8ad --- /dev/null +++ b/modules/gdscript/gd_function.h @@ -0,0 +1,225 @@ +#ifndef GD_FUNCTION_H +#define GD_FUNCTION_H + +#include "self_list.h" +#include "os/thread.h" +#include "pair.h" +#include "variant.h" +#include "string_db.h" +#include "reference.h" +#include "script_language.h" + +class GDInstance; +class GDScript; + + +class GDFunction { +public: + + enum Opcode { + OPCODE_OPERATOR, + OPCODE_EXTENDS_TEST, + OPCODE_SET, + OPCODE_GET, + OPCODE_SET_NAMED, + OPCODE_GET_NAMED, + OPCODE_SET_MEMBER, + OPCODE_GET_MEMBER, + OPCODE_ASSIGN, + OPCODE_ASSIGN_TRUE, + OPCODE_ASSIGN_FALSE, + OPCODE_CONSTRUCT, //only for basic types!! + OPCODE_CONSTRUCT_ARRAY, + OPCODE_CONSTRUCT_DICTIONARY, + OPCODE_CALL, + OPCODE_CALL_RETURN, + OPCODE_CALL_BUILT_IN, + OPCODE_CALL_SELF, + OPCODE_CALL_SELF_BASE, + OPCODE_YIELD, + OPCODE_YIELD_SIGNAL, + OPCODE_YIELD_RESUME, + OPCODE_JUMP, + OPCODE_JUMP_IF, + OPCODE_JUMP_IF_NOT, + OPCODE_JUMP_TO_DEF_ARGUMENT, + OPCODE_RETURN, + OPCODE_ITERATE_BEGIN, + OPCODE_ITERATE, + OPCODE_ASSERT, + OPCODE_BREAKPOINT, + OPCODE_LINE, + OPCODE_END + }; + + enum Address { + ADDR_BITS=24, + ADDR_MASK=((1<<ADDR_BITS)-1), + ADDR_TYPE_MASK=~ADDR_MASK, + ADDR_TYPE_SELF=0, + ADDR_TYPE_CLASS=1, + ADDR_TYPE_MEMBER=2, + ADDR_TYPE_CLASS_CONSTANT=3, + ADDR_TYPE_LOCAL_CONSTANT=4, + ADDR_TYPE_STACK=5, + ADDR_TYPE_STACK_VARIABLE=6, + ADDR_TYPE_GLOBAL=7, + ADDR_TYPE_NIL=8 + }; + + enum RPCMode { + RPC_DISABLED, + RPC_ENABLED, + RPC_SYNC, + RPC_SYNC_MASTER, + RPC_SYNC_SLAVE + }; + + struct StackDebug { + + int line; + int pos; + bool added; + StringName identifier; + }; + +private: +friend class GDCompiler; + + StringName source; + + mutable Variant nil; + mutable Variant *_constants_ptr; + int _constant_count; + const StringName *_global_names_ptr; + int _global_names_count; + const int *_default_arg_ptr; + int _default_arg_count; + const int *_code_ptr; + int _code_size; + int _argument_count; + int _stack_size; + int _call_size; + int _initial_line; + bool _static; + ScriptInstance::RPCMode rpc_mode; + + GDScript *_script; + + StringName name; + Vector<Variant> constants; + Vector<StringName> global_names; + Vector<int> default_arguments; + Vector<int> code; + +#ifdef TOOLS_ENABLED + Vector<StringName> arg_names; +#endif + + List<StackDebug> stack_debug; + + _FORCE_INLINE_ Variant *_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self,Variant *p_stack,String& r_error) const; + _FORCE_INLINE_ String _get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const; + +friend class GDScriptLanguage; + + SelfList<GDFunction> function_list; +#ifdef DEBUG_ENABLED + CharString func_cname; + const char*_func_cname; + + struct Profile { + StringName signature; + uint64_t call_count; + uint64_t self_time; + uint64_t total_time; + uint64_t frame_call_count; + uint64_t frame_self_time; + uint64_t frame_total_time; + uint64_t last_frame_call_count; + uint64_t last_frame_self_time; + uint64_t last_frame_total_time; + } profile; + +#endif + +public: + + + + struct CallState { + + ObjectID instance_id; //by debug only + ObjectID script_id; + + GDInstance *instance; + Vector<uint8_t> stack; + int stack_size; + Variant self; + uint32_t alloca_size; + GDScript *_class; + int ip; + int line; + int defarg; + Variant result; + + }; + + _FORCE_INLINE_ bool is_static() const { return _static; } + + const int* get_code() const; //used for debug + int get_code_size() const; + Variant get_constant(int p_idx) const; + StringName get_global_name(int p_idx) const; + StringName get_name() const; + int get_max_stack_size() const; + int get_default_argument_count() const; + int get_default_argument_addr(int p_idx) const; + GDScript *get_script() const { return _script; } + StringName get_source() const { return source; } + + void debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const; + + _FORCE_INLINE_ bool is_empty() const { return _code_size==0; } + + int get_argument_count() const { return _argument_count; } + StringName get_argument_name(int p_idx) const { +#ifdef TOOLS_ENABLED + ERR_FAIL_INDEX_V(p_idx,arg_names.size(),StringName()); + return arg_names[p_idx]; +#endif + return StringName(); + + } + Variant get_default_argument(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx,default_arguments.size(),Variant()); + return default_arguments[p_idx]; + } + + Variant call(GDInstance *p_instance,const Variant **p_args, int p_argcount,Variant::CallError& r_err,CallState *p_state=NULL); + + _FORCE_INLINE_ ScriptInstance::RPCMode get_rpc_mode() const { return rpc_mode; } + GDFunction(); + ~GDFunction(); +}; + + +class GDFunctionState : public Reference { + + GDCLASS(GDFunctionState,Reference); +friend class GDFunction; + GDFunction *function; + GDFunction::CallState state; + Variant _signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error); +protected: + static void _bind_methods(); +public: + + bool is_valid() const; + Variant resume(const Variant& p_arg=Variant()); + GDFunctionState(); + ~GDFunctionState(); +}; + + +#endif // GD_FUNCTION_H diff --git a/modules/gdscript/gd_functions.cpp b/modules/gdscript/gd_functions.cpp index 1c05a71d0..1c41b2e73 100644 --- a/modules/gdscript/gd_functions.cpp +++ b/modules/gdscript/gd_functions.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -35,6 +35,7 @@ #include "os/os.h" #include "variant_parser.h" #include "io/marshalls.h" +#include "io/json.h" const char *GDFunctions::get_func_name(Function p_func) { @@ -87,6 +88,8 @@ const char *GDFunctions::get_func_name(Function p_func) { "funcref", "convert", "typeof", + "type_exists", + "char", "str", "print", "printt", @@ -101,8 +104,12 @@ const char *GDFunctions::get_func_name(Function p_func) { "load", "inst2dict", "dict2inst", + "validate_json", + "parse_json", + "to_json", "hash", "Color8", + "ColorN", "print_stack", "instance_from_id", }; @@ -120,11 +127,13 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_arg_count<m_count) {\ r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;\ r_error.argument=m_count;\ + r_ret=Variant();\ return;\ }\ if (p_arg_count>m_count) {\ r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;\ r_error.argument=m_count;\ + r_ret=Variant();\ return;\ } @@ -133,6 +142,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;\ r_error.argument=m_arg;\ r_error.expected=Variant::REAL;\ + r_ret=Variant();\ return;\ } @@ -244,6 +254,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::REAL; + r_ret=Variant(); } } break; case MATH_SIGN: { @@ -261,6 +272,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::REAL; + r_ret=Variant(); } } break; case MATH_POW: { @@ -298,7 +310,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va case MATH_DECIMALS: { VALIDATE_ARG_COUNT(1); VALIDATE_ARG_NUM(0); - r_ret=Math::decimals(*p_args[0]); + r_ret=Math::step_decimals(*p_args[0]); } break; case MATH_STEPIFY: { VALIDATE_ARG_COUNT(2); @@ -442,6 +454,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=Variant(); return; } @@ -479,7 +492,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; - r_ret=Variant(); + r_ret=Variant(); return; } @@ -495,7 +508,6 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va Ref<FuncRef> fr = memnew( FuncRef); - Object *obj = *p_args[0]; fr->set_instance(*p_args[0]); fr->set_function(*p_args[1]); @@ -508,8 +520,11 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va int type=*p_args[1]; if (type<0 || type>=Variant::VARIANT_MAX) { - ERR_PRINT("Invalid type argument to convert()"); - r_ret=Variant::NIL; + r_ret=RTR("Invalid type argument to convert(), use TYPE_* constants."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::INT; + return; } else { @@ -523,6 +538,18 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_ret = p_args[0]->get_type(); } break; + case TYPE_EXISTS: { + + VALIDATE_ARG_COUNT(1); + r_ret = ClassDB::class_exists(*p_args[0]); + + } break; + case TEXT_CHAR: { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_NUM(0); + CharType result[2] = {*p_args[0], 0}; + r_ret=String(result); + } break; case TEXT_STR: { String str; @@ -638,27 +665,28 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::STRING; - r_ret=Variant(); + r_ret="Parse error at line "+itos(line)+": "+errs; + return; } } break; case VAR_TO_BYTES: { VALIDATE_ARG_COUNT(1); - ByteArray barr; + PoolByteArray barr; int len; Error err = encode_variant(*p_args[0],NULL,len); if (err) { r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::NIL; - r_ret=Variant(); + r_ret="Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."; return; } barr.resize(len); { - ByteArray::Write w = barr.write(); + PoolByteArray::Write w = barr.write(); encode_variant(*p_args[0],w.ptr(),len); } @@ -666,25 +694,24 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va } break; case BYTES_TO_VAR: { VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type()!=Variant::RAW_ARRAY) { + if (p_args[0]->get_type()!=Variant::POOL_BYTE_ARRAY) { r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; - r_error.expected=Variant::RAW_ARRAY; + r_error.expected=Variant::POOL_BYTE_ARRAY; r_ret=Variant(); return; } - ByteArray varr=*p_args[0]; + PoolByteArray varr=*p_args[0]; Variant ret; { - ByteArray::Read r=varr.read(); + PoolByteArray::Read r=varr.read(); Error err = decode_variant(ret,r.ptr(),varr.size(),NULL); if (err!=OK) { - ERR_PRINT("Not enough bytes for decoding.."); + r_ret=RTR("Not enough bytes for decoding bytes, or invalid format."); r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; - r_error.expected=Variant::RAW_ARRAY; - r_ret=Variant(); + r_error.expected=Variant::POOL_BYTE_ARRAY; return; } @@ -701,13 +728,14 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument=1; + r_ret=Variant(); } break; case 1: { VALIDATE_ARG_NUM(0); int count=*p_args[0]; - Array arr(true); + Array arr; if (count<=0) { r_ret=arr; return; @@ -733,7 +761,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va int from=*p_args[0]; int to=*p_args[1]; - Array arr(true); + Array arr; if (from>=to) { r_ret=arr; return; @@ -759,12 +787,12 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va int incr=*p_args[2]; if (incr==0) { - ERR_EXPLAIN("step argument is zero!"); + r_ret=RTR("step argument is zero!"); r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; - ERR_FAIL(); + return; } - Array arr(true); + Array arr; if (from>=to && incr>0) { r_ret=arr; return; @@ -812,6 +840,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument=3; + r_ret=Variant(); + } break; } @@ -821,9 +851,11 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_args[0]->get_type()!=Variant::STRING) { r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; + r_error.expected=Variant::STRING; r_ret=Variant(); + } else { + r_ret=ResourceLoader::load(*p_args[0]); } - r_ret=ResourceLoader::load(*p_args[0]); } break; case INST2DICT: { @@ -847,8 +879,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; - ERR_PRINT("Not a script with an instance"); - + r_ret=RTR("Not a script with an instance"); + return; } else { GDInstance *ins = static_cast<GDInstance*>(obj->get_script_instance()); @@ -858,7 +890,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; - ERR_PRINT("Not based on a script"); + r_ret=RTR("Not based on a script"); return; } @@ -879,15 +911,17 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; - print_line("PATH: "+p->path); - ERR_PRINT("Not based on a resource file"); + r_ret=Variant(); + + + r_ret=RTR("Not based on a resource file"); return; } NodePath cp(sname,Vector<StringName>(),false); - Dictionary d(true); + Dictionary d; d["@subpath"]=cp; d["@path"]=p->path; @@ -926,6 +960,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::DICTIONARY; + r_ret=Variant(); + return; } @@ -936,6 +972,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=RTR("Invalid instance dictionary format (missing @path)"); + return; } @@ -945,6 +983,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=RTR("Invalid instance dictionary format (can't load script at @path)"); return; } @@ -955,6 +994,8 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=Variant(); + r_ret=RTR("Invalid instance dictionary format (invalid script at @path)"); return; } @@ -971,22 +1012,75 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; r_error.expected=Variant::OBJECT; + r_ret=Variant(); + r_ret=RTR("Invalid instance dictionary (invalid subclasses)"); return; } } r_ret = gdscr->_new(NULL,0,r_error); - GDInstance *ins = static_cast<GDInstance*>(static_cast<Object*>(r_ret)->get_script_instance()); - Ref<GDScript> gd_ref = ins->get_script(); + GDInstance *ins = static_cast<GDInstance*>(static_cast<Object*>(r_ret)->get_script_instance()); + Ref<GDScript> gd_ref = ins->get_script(); + + for(Map<StringName,GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { + if(d.has(E->key())) { + ins->members[E->get().index] = d[E->key()]; + } + } + + } break; + case VALIDATE_JSON: { + + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type()!=Variant::STRING) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::STRING; + r_ret=Variant(); + return; + } + + String errs; + int errl; + + Error err = JSON::parse(*p_args[0],r_ret,errs,errl); - for(Map<StringName,GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { - if(d.has(E->key())) { - ins->members[E->get().index] = d[E->key()]; - } - } + if (err!=OK) { + r_ret=itos(errl)+":"+errs; + } else { + r_ret=""; + } } break; + case PARSE_JSON: { + + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type()!=Variant::STRING) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::STRING; + r_ret=Variant(); + return; + } + + String errs; + int errl; + + Error err = JSON::parse(*p_args[0],r_ret,errs,errl); + + if (err!=OK) { + r_ret=Variant(); + } + + } break; + case TO_JSON: { + VALIDATE_ARG_COUNT(1); + + r_ret = JSON::print(*p_args[0]); + } break; case HASH: { VALIDATE_ARG_COUNT(1); @@ -998,11 +1092,15 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_arg_count<3) { r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument=3; + r_ret=Variant(); + return; } if (p_arg_count>4) { r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument=4; + r_ret=Variant(); + return; } @@ -1010,16 +1108,46 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va VALIDATE_ARG_NUM(1); VALIDATE_ARG_NUM(2); - Color color(*p_args[0],*p_args[1],*p_args[2]); + Color color((float)*p_args[0]/255.0f,(float)*p_args[1]/255.0f,(float)*p_args[2]/255.0f); if (p_arg_count==4) { VALIDATE_ARG_NUM(3); - color.a=*p_args[3]; + color.a=(float)*p_args[3]/255.0f; } r_ret=color; } break; + case COLORN: { + + if (p_arg_count<1) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=1; + r_ret=Variant(); + return; + } + + if (p_arg_count>2) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument=2; + r_ret=Variant(); + return; + } + + if (p_args[0]->get_type()!=Variant::STRING) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_ret=Variant(); + } else { + Color color = Color::named(*p_args[0]); + if (p_arg_count==2) { + VALIDATE_ARG_NUM(1); + color.a=*p_args[1]; + } + r_ret=color; + } + + } break; case PRINT_STACK: { @@ -1036,6 +1164,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va if (p_args[0]->get_type()!=Variant::INT && p_args[0]->get_type()!=Variant::REAL) { r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument=0; + r_error.expected=Variant::INT; r_ret=Variant(); break; } @@ -1098,6 +1227,8 @@ bool GDFunctions::is_deterministic(Function p_func) { case LOGIC_NEAREST_PO2: case TYPE_CONVERT: case TYPE_OF: + case TYPE_EXISTS: + case TEXT_CHAR: case TEXT_STR: case COLOR8: // enable for debug only, otherwise not desirable - case GEN_RANGE: @@ -1281,12 +1412,12 @@ MethodInfo GDFunctions::get_info(Function p_func) { return mi; } break; case MATH_SEED: { - MethodInfo mi("seed",PropertyInfo(Variant::REAL,"seed")); + MethodInfo mi("seed",PropertyInfo(Variant::INT,"seed")); mi.return_val.type=Variant::NIL; return mi; } break; case MATH_RANDSEED: { - MethodInfo mi("rand_seed",PropertyInfo(Variant::REAL,"seed")); + MethodInfo mi("rand_seed",PropertyInfo(Variant::INT,"seed")); mi.return_val.type=Variant::ARRAY; return mi; } break; @@ -1361,6 +1492,20 @@ MethodInfo GDFunctions::get_info(Function p_func) { return mi; } break; + case TYPE_EXISTS: { + + MethodInfo mi("type_exists",PropertyInfo(Variant::STRING,"type")); + mi.return_val.type=Variant::BOOL; + return mi; + + } break; + case TEXT_CHAR: { + + MethodInfo mi("char",PropertyInfo(Variant::INT,"ascii")); + mi.return_val.type=Variant::STRING; + return mi; + + } break; case TEXT_STR: { MethodInfo mi("str",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::NIL,"...")); @@ -1417,13 +1562,13 @@ MethodInfo GDFunctions::get_info(Function p_func) { } break; case VAR_TO_BYTES: { MethodInfo mi("var2bytes",PropertyInfo(Variant::NIL,"var")); - mi.return_val.type=Variant::RAW_ARRAY; + mi.return_val.type=Variant::POOL_BYTE_ARRAY; return mi; } break; case BYTES_TO_VAR: { - MethodInfo mi("bytes2var:Variant",PropertyInfo(Variant::RAW_ARRAY,"bytes")); + MethodInfo mi("bytes2var:Variant",PropertyInfo(Variant::POOL_BYTE_ARRAY,"bytes")); mi.return_val.type=Variant::NIL; return mi; } break; @@ -1452,6 +1597,24 @@ MethodInfo GDFunctions::get_info(Function p_func) { mi.return_val.type=Variant::OBJECT; return mi; } break; + case VALIDATE_JSON: { + + MethodInfo mi("validate_json:Variant",PropertyInfo(Variant::STRING,"json")); + mi.return_val.type=Variant::STRING; + return mi; + } break; + case PARSE_JSON: { + + MethodInfo mi("parse_json:Variant",PropertyInfo(Variant::STRING,"json")); + mi.return_val.type=Variant::NIL; + return mi; + } break; + case TO_JSON: { + + MethodInfo mi("to_json",PropertyInfo(Variant::NIL,"var:Variant")); + mi.return_val.type=Variant::STRING; + return mi; + } break; case HASH: { MethodInfo mi("hash",PropertyInfo(Variant::NIL,"var:Variant")); @@ -1464,6 +1627,12 @@ MethodInfo GDFunctions::get_info(Function p_func) { mi.return_val.type=Variant::COLOR; return mi; } break; + case COLORN: { + + MethodInfo mi("ColorN",PropertyInfo(Variant::STRING,"name"),PropertyInfo(Variant::REAL,"alpha")); + mi.return_val.type=Variant::COLOR; + return mi; + } break; case PRINT_STACK: { MethodInfo mi("print_stack"); diff --git a/modules/gdscript/gd_functions.h b/modules/gdscript/gd_functions.h index 8c8847256..6e30b4dbb 100644 --- a/modules/gdscript/gd_functions.h +++ b/modules/gdscript/gd_functions.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -81,6 +81,8 @@ public: FUNC_FUNCREF, TYPE_CONVERT, TYPE_OF, + TYPE_EXISTS, + TEXT_CHAR, TEXT_STR, TEXT_PRINT, TEXT_PRINT_TABBED, @@ -95,8 +97,12 @@ public: RESOURCE_LOAD, INST2DICT, DICT2INST, + VALIDATE_JSON, + PARSE_JSON, + TO_JSON, HASH, COLOR8, + COLORN, PRINT_STACK, INSTANCE_FROM_ID, FUNC_MAX diff --git a/modules/gdscript/gd_parser.cpp b/modules/gdscript/gd_parser.cpp index 901a45817..c783fac42 100644 --- a/modules/gdscript/gd_parser.cpp +++ b/modules/gdscript/gd_parser.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,6 +31,7 @@ #include "io/resource_loader.h" #include "os/file_access.h" #include "script_language.h" +#include "gd_script.h" template<class T> T* GDParser::alloc_node() { @@ -120,6 +121,7 @@ bool GDParser::_parse_arguments(Node* p_parent,Vector<Node*>& p_args,bool p_stat tokenizer->advance(); } else { + parenthesis ++; int argidx=0; while(true) { @@ -164,6 +166,7 @@ bool GDParser::_parse_arguments(Node* p_parent,Vector<Node*>& p_args,bool p_stat } } + parenthesis --; } return true; @@ -202,6 +205,7 @@ bool GDParser::_get_completable_identifier(CompletionType p_type,StringName& ide completion_line=tokenizer->get_token_line(); completion_block=current_block; completion_found=true; + completion_ident_is_call=false; tokenizer->advance(); if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER) { @@ -209,6 +213,9 @@ bool GDParser::_get_completable_identifier(CompletionType p_type,StringName& ide tokenizer->advance(); } + if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_OPEN) { + completion_ident_is_call=true; + } return true; } @@ -216,15 +223,17 @@ bool GDParser::_get_completable_identifier(CompletionType p_type,StringName& ide } -GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_allow_assign) { +GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_allow_assign,bool p_parsing_constant) { -// Vector<Node*> expressions; -// Vector<OperatorNode::Operator> operators; + //Vector<Node*> expressions; + //Vector<OperatorNode::Operator> operators; Vector<Expression> expression; Node *expr=NULL; + int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found) + while(true) { @@ -243,7 +252,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ //subexpression () tokenizer->advance(); parenthesis++; - Node* subexpr = _parse_expression(p_parent,p_static); + Node* subexpr = _parse_expression(p_parent,p_static,p_allow_assign,p_parsing_constant); parenthesis--; if (!subexpr) return NULL; @@ -256,6 +265,98 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ tokenizer->advance(); expr=subexpr; + } else if (tokenizer->get_token()==GDTokenizer::TK_DOLLAR) { + tokenizer->advance(); + + String path; + + bool need_identifier=true; + bool done=false; + + while(!done) { + + switch(tokenizer->get_token()) { + case GDTokenizer::TK_CURSOR: { + completion_cursor=StringName(); + completion_type=COMPLETION_GET_NODE; + completion_class=current_class; + completion_function=current_function; + completion_line=tokenizer->get_token_line(); + completion_cursor=path; + completion_argument=0; + completion_block=current_block; + completion_found=true; + tokenizer->advance(); + } break; + case GDTokenizer::TK_CONSTANT: { + + if (!need_identifier) { + done=true; + break; + } + + if (tokenizer->get_token_constant().get_type()!=Variant::STRING) { + _set_error("Expected string constant or identifier after '$' or '/'."); + return NULL; + } + + path+=String(tokenizer->get_token_constant()); + tokenizer->advance(); + need_identifier=false; + + } break; + case GDTokenizer::TK_IDENTIFIER: { + if (!need_identifier) { + done=true; + break; + } + + path+=String(tokenizer->get_token_identifier()); + tokenizer->advance(); + need_identifier=false; + + } break; + case GDTokenizer::TK_OP_DIV: { + + if (need_identifier) { + done=true; + break; + } + + path+="/"; + tokenizer->advance(); + need_identifier=true; + + } break; + default: { + done=true; + break; + } + } + } + + if (path=="") { + _set_error("Path expected after $."); + return NULL; + + } + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=OperatorNode::OP_CALL; + + op->arguments.push_back(alloc_node<SelfNode>()); + + IdentifierNode *funcname = alloc_node<IdentifierNode>(); + funcname->name="get_node"; + + op->arguments.push_back(funcname); + + ConstantNode *nodepath = alloc_node<ConstantNode>(); + nodepath->value = NodePath(StringName(path)); + op->arguments.push_back(nodepath); + + expr=op; + } else if (tokenizer->get_token()==GDTokenizer::TK_CURSOR) { tokenizer->advance(); continue; //no point in cursor in the middle of expression @@ -357,35 +458,56 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ OperatorNode *yield = alloc_node<OperatorNode>(); yield->op=OperatorNode::OP_YIELD; + while (tokenizer->get_token()==GDTokenizer::TK_NEWLINE) { + tokenizer->advance(); + } + if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_CLOSE) { expr=yield; tokenizer->advance(); } else { + parenthesis ++; + Node *object = _parse_and_reduce_expression(p_parent,p_static); if (!object) return NULL; yield->arguments.push_back(object); if (tokenizer->get_token()!=GDTokenizer::TK_COMMA) { - _set_error("Expected ',' after first argument of 'yield'"); return NULL; } tokenizer->advance(); + if (tokenizer->get_token()==GDTokenizer::TK_CURSOR) { + + + completion_cursor=StringName(); + completion_node=object; + completion_type=COMPLETION_YIELD; + completion_class=current_class; + completion_function=current_function; + completion_line=tokenizer->get_token_line(); + completion_argument=0; + completion_block=current_block; + completion_found=true; + tokenizer->advance(); + } + Node *signal = _parse_and_reduce_expression(p_parent,p_static); if (!signal) return NULL; yield->arguments.push_back(signal); if (tokenizer->get_token()!=GDTokenizer::TK_PARENTHESIS_CLOSE) { - _set_error("Expected ')' after second argument of 'yield'"); return NULL; } + parenthesis --; + tokenizer->advance(); expr=yield; @@ -477,20 +599,30 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ } else if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER) { //identifier (reference) - const ClassNode* cln = static_cast<const ClassNode*>(get_parse_tree()); + const ClassNode* cln = current_class; bool bfn = false; StringName identifier; if (_get_completable_identifier(COMPLETION_IDENTIFIER,identifier)) { } - for( int i=0; i<cln->constant_expressions.size(); ++i ) { + if (p_parsing_constant) { + for( int i=0; i<cln->constant_expressions.size(); ++i ) { - if( cln->constant_expressions[i].identifier == identifier ) { + if( cln->constant_expressions[i].identifier == identifier ) { - expr = cln->constant_expressions[i].expression; - bfn = true; - break; + expr = cln->constant_expressions[i].expression; + bfn = true; + break; + } + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + //check from constants + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->value = GDScriptLanguage::get_singleton()->get_global_array()[ GDScriptLanguage::get_singleton()->get_global_map()[identifier] ]; + expr=constant; + bfn = true; } } @@ -500,15 +632,15 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ expr = id; } - } else if (/*tokenizer->get_token()==GDTokenizer::TK_OP_ADD ||*/ tokenizer->get_token()==GDTokenizer::TK_OP_SUB || tokenizer->get_token()==GDTokenizer::TK_OP_NOT || tokenizer->get_token()==GDTokenizer::TK_OP_BIT_INVERT) { - - //single prefix operators like !expr -expr ++expr --expr - OperatorNode *op = alloc_node<OperatorNode>(); + } else if (tokenizer->get_token()==GDTokenizer::TK_OP_ADD || tokenizer->get_token()==GDTokenizer::TK_OP_SUB || tokenizer->get_token()==GDTokenizer::TK_OP_NOT || tokenizer->get_token()==GDTokenizer::TK_OP_BIT_INVERT) { + //single prefix operators like !expr +expr -expr ++expr --expr + alloc_node<OperatorNode>(); Expression e; e.is_op=true; switch(tokenizer->get_token()) { + case GDTokenizer::TK_OP_ADD: e.op=OperatorNode::OP_POS; break; case GDTokenizer::TK_OP_SUB: e.op=OperatorNode::OP_NEG; break; case GDTokenizer::TK_OP_NOT: e.op=OperatorNode::OP_NOT; break; case GDTokenizer::TK_OP_BIT_INVERT: e.op=OperatorNode::OP_BIT_INVERT;; break; @@ -567,7 +699,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ _set_error("',' or ']' expected"); return NULL; } - Node *n = _parse_expression(arr,p_static); + Node *n = _parse_expression(arr,p_static,p_allow_assign,p_parsing_constant); if (!n) return NULL; arr->elements.push_back(n); @@ -674,7 +806,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ expecting=DICT_EXPECT_VALUE; } else { //python/js style more flexible - key = _parse_expression(dict,p_static); + key = _parse_expression(dict,p_static,p_allow_assign,p_parsing_constant); if (!key) return NULL; expecting=DICT_EXPECT_COLON; @@ -682,7 +814,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ } if (expecting==DICT_EXPECT_VALUE) { - Node *value = _parse_expression(dict,p_static); + Node *value = _parse_expression(dict,p_static,p_allow_assign,p_parsing_constant); if (!value) return NULL; expecting=DICT_EXPECT_COMMA; @@ -730,7 +862,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ //find list [ or find dictionary { - print_line("found bug?"); + //print_line("found bug?"); _set_error("Error parsing expression, misplaced: "+String(tokenizer->get_token_name(tokenizer->get_token()))); return NULL; //nothing @@ -833,7 +965,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ tokenizer->advance(1); - Node *subexpr = _parse_expression(op,p_static); + Node *subexpr = _parse_expression(op,p_static,p_allow_assign,p_parsing_constant); if (!subexpr) { return NULL; } @@ -909,6 +1041,8 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ case GDTokenizer::TK_OP_BIT_OR: op=OperatorNode::OP_BIT_OR ; break; case GDTokenizer::TK_OP_BIT_XOR: op=OperatorNode::OP_BIT_XOR ; break; case GDTokenizer::TK_PR_EXTENDS: op=OperatorNode::OP_EXTENDS; break; + case GDTokenizer::TK_CF_IF: op=OperatorNode::OP_TERNARY_IF; break; + case GDTokenizer::TK_CF_ELSE: op=OperatorNode::OP_TERNARY_ELSE; break; default: valid=false; break; } @@ -931,6 +1065,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ int next_op=-1; int min_priority=0xFFFFF; bool is_unary=false; + bool is_ternary=false; for(int i=0;i<expression.size();i++) { @@ -944,6 +1079,8 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ int priority; bool unary=false; + bool ternary=false; + bool error=false; switch(expression[i].op) { @@ -951,6 +1088,7 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ case OperatorNode::OP_BIT_INVERT: priority=0; unary=true; break; case OperatorNode::OP_NEG: priority=1; unary=true; break; + case OperatorNode::OP_POS: priority=1; unary=true; break; case OperatorNode::OP_MUL: priority=2; break; case OperatorNode::OP_DIV: priority=2; break; @@ -974,25 +1112,27 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ case OperatorNode::OP_EQUAL: priority=8; break; case OperatorNode::OP_NOT_EQUAL: priority=8; break; + case OperatorNode::OP_IN: priority=10; break; - + case OperatorNode::OP_NOT: priority=11; unary=true; break; case OperatorNode::OP_AND: priority=12; break; case OperatorNode::OP_OR: priority=13; break; + + case OperatorNode::OP_TERNARY_IF: priority=14; ternary=true; break; + case OperatorNode::OP_TERNARY_ELSE: priority=14; error=true; break; // Errors out when found without IF (since IF would consume it) - // ?: = 10 - - case OperatorNode::OP_ASSIGN: priority=14; break; - case OperatorNode::OP_ASSIGN_ADD: priority=14; break; - case OperatorNode::OP_ASSIGN_SUB: priority=14; break; - case OperatorNode::OP_ASSIGN_MUL: priority=14; break; - case OperatorNode::OP_ASSIGN_DIV: priority=14; break; - case OperatorNode::OP_ASSIGN_MOD: priority=14; break; - case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority=14; break; - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority=14; break; - case OperatorNode::OP_ASSIGN_BIT_AND: priority=14; break; - case OperatorNode::OP_ASSIGN_BIT_OR: priority=14; break; - case OperatorNode::OP_ASSIGN_BIT_XOR: priority=14; break; + case OperatorNode::OP_ASSIGN: priority=15; break; + case OperatorNode::OP_ASSIGN_ADD: priority=15; break; + case OperatorNode::OP_ASSIGN_SUB: priority=15; break; + case OperatorNode::OP_ASSIGN_MUL: priority=15; break; + case OperatorNode::OP_ASSIGN_DIV: priority=15; break; + case OperatorNode::OP_ASSIGN_MOD: priority=15; break; + case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority=15; break; + case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority=15; break; + case OperatorNode::OP_ASSIGN_BIT_AND: priority=15; break; + case OperatorNode::OP_ASSIGN_BIT_OR: priority=15; break; + case OperatorNode::OP_ASSIGN_BIT_XOR: priority=15; break; default: { @@ -1003,11 +1143,16 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ } if (priority<min_priority) { + if(error) { + _set_error("Unexpected operator"); + return NULL; + } // < is used for left to right (default) // <= is used for right to left next_op=i; min_priority=priority; is_unary=unary; + is_ternary=ternary; } } @@ -1041,21 +1186,31 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ OperatorNode *op = alloc_node<OperatorNode>(); op->op=expression[i].op; op->arguments.push_back(expression[i+1].node); + op->line=op_line; //line might have been changed from a \n expression[i].is_op=false; expression[i].node=op; expression.remove(i+1); } - } else { - + } else if(is_ternary) { if (next_op <1 || next_op>=(expression.size()-1)) { _set_error("Parser bug.."); ERR_FAIL_V(NULL); } + + if(next_op>=(expression.size()-2) || expression[next_op+2].op != OperatorNode::OP_TERNARY_ELSE) { + _set_error("Expected else after ternary if."); + ERR_FAIL_V(NULL); + } + if(next_op>=(expression.size()-3)) { + _set_error("Expected value after ternary else."); + ERR_FAIL_V(NULL); + } OperatorNode *op = alloc_node<OperatorNode>(); op->op=expression[next_op].op; + op->line=op_line; //line might have been changed from a \n if (expression[next_op-1].is_op) { @@ -1069,8 +1224,56 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_ // can be followed by an unary op in a valid combination, // due to how precedence works, unaries will always dissapear first + _set_error("Unexpected two consecutive operators after ternary if."); + return NULL; + } + + if (expression[next_op+3].is_op) { + // this is not invalid and can really appear + // but it becomes invalid anyway because no binary op + // can be followed by an unary op in a valid combination, + // due to how precedence works, unaries will always dissapear first + + _set_error("Unexpected two consecutive operators after ternary else."); + return NULL; + } + + + op->arguments.push_back(expression[next_op+1].node); //next expression goes as first + op->arguments.push_back(expression[next_op-1].node); //left expression goes as when-true + op->arguments.push_back(expression[next_op+3].node); //expression after next goes as when-false + + //replace all 3 nodes by this operator and make it an expression + expression[next_op-1].node=op; + expression.remove(next_op); + expression.remove(next_op); + expression.remove(next_op); + expression.remove(next_op); + } else { + + if (next_op <1 || next_op>=(expression.size()-1)) { _set_error("Parser bug.."); + ERR_FAIL_V(NULL); + } + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=expression[next_op].op; + op->line=op_line; //line might have been changed from a \n + if (expression[next_op-1].is_op) { + + _set_error("Parser bug.."); + ERR_FAIL_V(NULL); + } + + if (expression[next_op+1].is_op) { + // this is not invalid and can really appear + // but it becomes invalid anyway because no binary op + // can be followed by an unary op in a valid combination, + // due to how precedence works, unaries will always dissapear first + + _set_error("Unexpected two consecutive operators."); + return NULL; } @@ -1114,7 +1317,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { //reduce constant array expression ConstantNode *cn = alloc_node<ConstantNode>(); - Array arr(!p_to_const); + Array arr; //print_line("mk array "+itos(!p_to_const)); arr.resize(an->elements.size()); for(int i=0;i<an->elements.size();i++) { @@ -1149,7 +1352,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { //reduce constant array expression ConstantNode *cn = alloc_node<ConstantNode>(); - Dictionary dict(!p_to_const); + Dictionary dict; for(int i=0;i<dn->elements.size();i++) { ConstantNode *key_c = static_cast<ConstantNode*>(dn->elements[i].key); ConstantNode *value_c = static_cast<ConstantNode*>(dn->elements[i].value); @@ -1258,6 +1461,8 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { } break; } + error_line=op->line; + return p_node; } @@ -1293,6 +1498,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant v = ca->value.get(cb->value,&valid); if (!valid) { _set_error("invalid index in constant expression"); + error_line=op->line; return op; } @@ -1330,6 +1536,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant v = ca->value.get_named(ib->name,&valid); if (!valid) { _set_error("invalid index '"+String(ib->name)+"' in constant expression"); + error_line=op->line; return op; } @@ -1359,9 +1566,19 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { if (op->arguments[0]->type==Node::TYPE_CONSTANT) { _set_error("Can't assign to constant",tokenizer->get_token_line()-1); + error_line=op->line; return op; } + if (op->arguments[0]->type==Node::TYPE_OPERATOR) { + OperatorNode *on = static_cast<OperatorNode*>(op->arguments[0]); + if (on->op != OperatorNode::OP_INDEX && on->op != OperatorNode::OP_INDEX_NAMED) { + _set_error("Can't assign to an expression",tokenizer->get_token_line()-1); + error_line=op->line; + return op; + } + } + } break; default: { break; } } @@ -1374,6 +1591,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant::evaluate(m_vop,static_cast<ConstantNode*>(op->arguments[0])->value,Variant(),res,valid);\ if (!valid) {\ _set_error("Invalid operand for unary operator");\ + error_line=op->line;\ return p_node;\ }\ ConstantNode *cn = alloc_node<ConstantNode>();\ @@ -1386,6 +1604,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { Variant::evaluate(m_vop,static_cast<ConstantNode*>(op->arguments[0])->value,static_cast<ConstantNode*>(op->arguments[1])->value,res,valid);\ if (!valid) {\ _set_error("Invalid operands for operator");\ + error_line=op->line;\ return p_node;\ }\ ConstantNode *cn = alloc_node<ConstantNode>();\ @@ -1396,6 +1615,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { //unary operators case OperatorNode::OP_NEG: { _REDUCE_UNARY(Variant::OP_NEGATE); } break; + case OperatorNode::OP_POS: { _REDUCE_UNARY(Variant::OP_POSITIVE); } break; case OperatorNode::OP_NOT: { _REDUCE_UNARY(Variant::OP_NOT); } break; case OperatorNode::OP_BIT_INVERT: { _REDUCE_UNARY(Variant::OP_BIT_NEGATE); } break; //binary operators (in precedence order) @@ -1432,7 +1652,7 @@ GDParser::Node* GDParser::_reduce_expression(Node *p_node,bool p_to_const) { GDParser::Node* GDParser::_parse_and_reduce_expression(Node *p_parent,bool p_static,bool p_reduce_const,bool p_allow_assign) { - Node* expr=_parse_expression(p_parent,p_static,p_allow_assign); + Node* expr=_parse_expression(p_parent,p_static,p_allow_assign,p_reduce_const); if (!expr || error_set) return NULL; expr = _reduce_expression(expr,p_reduce_const); @@ -1526,6 +1746,10 @@ void GDParser::_parse_block(BlockNode *p_block,bool p_static) { return; } tokenizer->advance(); + if(tokenizer->get_token()==GDTokenizer::TK_SEMICOLON) { + // Ignore semicolon after 'pass' + tokenizer->advance(); + } } break; case GDTokenizer::TK_PR_VAR: { //variale declaration and (eventual) initialization @@ -1611,6 +1835,7 @@ void GDParser::_parse_block(BlockNode *p_block,bool p_static) { case GDTokenizer::TK_CF_IF: { tokenizer->advance(); + Node *condition = _parse_and_reduce_expression(p_block,p_static); if (!condition) { if (_recover_from_completion()) { @@ -1797,6 +2022,64 @@ void GDParser::_parse_block(BlockNode *p_block,bool p_static) { return; } + if (container->type==Node::TYPE_OPERATOR) { + + OperatorNode* op = static_cast<OperatorNode*>(container); + if (op->op==OperatorNode::OP_CALL && op->arguments[0]->type==Node::TYPE_BUILT_IN_FUNCTION && static_cast<BuiltInFunctionNode*>(op->arguments[0])->function==GDFunctions::GEN_RANGE) { + //iterating a range, so see if range() can be optimized without allocating memory, by replacing it by vectors (which can work as iterable too!) + + Vector<Node*> args; + Vector<double> constants; + + bool constant=true; + + for(int i=1;i<op->arguments.size();i++) { + args.push_back(op->arguments[i]); + if (constant && op->arguments[i]->type==Node::TYPE_CONSTANT) { + ConstantNode *c = static_cast<ConstantNode*>(op->arguments[i]); + if (c->value.get_type()==Variant::REAL || c->value.get_type()==Variant::INT) { + constants.push_back(c->value); + } else { + constant=false; + } + } + } + + if (args.size()>0 || args.size()<4) { + + if (constant) { + + ConstantNode *cn = alloc_node<ConstantNode>(); + switch(args.size()) { + case 1: cn->value=constants[0]; break; + case 2: cn->value=Vector2(constants[0],constants[1]); break; + case 3: cn->value=Vector3(constants[0],constants[1],constants[2]); break; + } + container=cn; + } else { + OperatorNode *on = alloc_node<OperatorNode>(); + on->op=OperatorNode::OP_CALL; + + TypeNode *tn = alloc_node<TypeNode>(); + on->arguments.push_back(tn); + + switch(args.size()) { + case 1: tn->vtype=Variant::REAL; break; + case 2: tn->vtype=Variant::VECTOR2; break; + case 3: tn->vtype=Variant::VECTOR3; break; + } + + for(int i=0;i<args.size();i++) { + on->arguments.push_back(args[i]); + } + + container=on; + } + } + } + + } + ControlFlowNode *cf_for = alloc_node<ControlFlowNode>(); cf_for->cf_type=ControlFlowNode::CF_FOR; @@ -2060,6 +2343,7 @@ void GDParser::_parse_class(ClassNode *p_class) { if (error_set) return; + if (indent_level>tab_level.back()->get()) { p_class->end_line=tokenizer->get_token_line(); return; //go back a level @@ -2217,6 +2501,11 @@ void GDParser::_parse_class(ClassNode *p_class) { bool defaulting=false; while(true) { + if (tokenizer->get_token()==GDTokenizer::TK_NEWLINE) { + tokenizer->advance(); + continue; + } + if (tokenizer->get_token()==GDTokenizer::TK_PR_VAR) { tokenizer->advance(); //var before the identifier is allowed @@ -2269,6 +2558,10 @@ void GDParser::_parse_class(ClassNode *p_class) { default_values.push_back(on); } + while (tokenizer->get_token()==GDTokenizer::TK_NEWLINE) { + tokenizer->advance(); + } + if (tokenizer->get_token()==GDTokenizer::TK_COMMA) { tokenizer->advance(); continue; @@ -2310,6 +2603,7 @@ void GDParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token()!=GDTokenizer::TK_PARENTHESIS_CLOSE) { //has arguments + parenthesis ++; while(true) { Node *arg = _parse_and_reduce_expression(p_class,_static); @@ -2327,6 +2621,7 @@ void GDParser::_parse_class(ClassNode *p_class) { break; } + parenthesis --; } tokenizer->advance(); @@ -2356,6 +2651,9 @@ void GDParser::_parse_class(ClassNode *p_class) { function->_static=_static; function->line=fnline; + function->rpc_mode=rpc_mode; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; + if (_static) p_class->static_functions.push_back(function); @@ -2387,6 +2685,10 @@ void GDParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_OPEN) { tokenizer->advance(); while(true) { + if (tokenizer->get_token()==GDTokenizer::TK_NEWLINE) { + tokenizer->advance(); + continue; + } if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_CLOSE) { @@ -2402,6 +2704,10 @@ void GDParser::_parse_class(ClassNode *p_class) { sig.arguments.push_back(tokenizer->get_token_identifier()); tokenizer->advance(); + while (tokenizer->get_token()==GDTokenizer::TK_NEWLINE) { + tokenizer->advance(); + } + if (tokenizer->get_token()==GDTokenizer::TK_COMMA) { tokenizer->advance(); } else if (tokenizer->get_token()!=GDTokenizer::TK_PARENTHESIS_CLOSE) { @@ -2433,18 +2739,43 @@ void GDParser::_parse_class(ClassNode *p_class) { return; } current_export.type=type; + current_export.usage|=PROPERTY_USAGE_SCRIPT_VARIABLE; tokenizer->advance(); + + String hint_prefix =""; + + if(type == Variant::ARRAY && tokenizer->get_token()==GDTokenizer::TK_COMMA) { + tokenizer->advance(); + + while(tokenizer->get_token()==GDTokenizer::TK_BUILT_IN_TYPE) { + type = tokenizer->get_token_type(); + + tokenizer->advance(); + + if(type == Variant::ARRAY) { + hint_prefix += itos(Variant::ARRAY)+":"; + if (tokenizer->get_token()==GDTokenizer::TK_COMMA) { + tokenizer->advance(); + } + } else { + hint_prefix += itos(type); + break; + } + } + } + if (tokenizer->get_token()==GDTokenizer::TK_COMMA) { // hint expected next! tokenizer->advance(); - switch(current_export.type) { + + switch(type) { case Variant::INT: { if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier()=="FLAGS") { - current_export.hint=PROPERTY_HINT_ALL_FLAGS; + //current_export.hint=PROPERTY_HINT_ALL_FLAGS; tokenizer->advance(); if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_CLOSE) { @@ -2793,13 +3124,20 @@ void GDParser::_parse_class(ClassNode *p_class) { return; } break; } - + + } + if(current_export.type == Variant::ARRAY && !hint_prefix.empty()) { + if(current_export.hint) { + hint_prefix += "/"+itos(current_export.hint); + } + current_export.hint_string=hint_prefix+":"+current_export.hint_string; + current_export.hint=PROPERTY_HINT_NONE; } } else if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER) { String identifier = tokenizer->get_token_identifier(); - if (!ObjectTypeDB::is_type(identifier,"Resource")) { + if (!ClassDB::is_parent_class(identifier,"Resource")) { current_export=PropertyInfo(); _set_error("Export hint not a type or resource."); @@ -2807,6 +3145,8 @@ void GDParser::_parse_class(ClassNode *p_class) { current_export.type=Variant::OBJECT; current_export.hint=PROPERTY_HINT_RESOURCE_TYPE; + current_export.usage|=PROPERTY_USAGE_SCRIPT_VARIABLE; + current_export.hint_string=identifier; tokenizer->advance(); @@ -2824,25 +3164,101 @@ void GDParser::_parse_class(ClassNode *p_class) { } - if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_ONREADY && tokenizer->get_token()!=GDTokenizer::TK_PR_REMOTE && tokenizer->get_token()!=GDTokenizer::TK_PR_MASTER && tokenizer->get_token()!=GDTokenizer::TK_PR_SLAVE && tokenizer->get_token()!=GDTokenizer::TK_PR_SYNC) { current_export=PropertyInfo(); - _set_error("Expected 'var'."); + _set_error("Expected 'var', 'onready', 'remote', 'master', 'slave' or 'sync'."); return; } - }; //fallthrough to var + continue; + } break; case GDTokenizer::TK_PR_ONREADY: { - if (token==GDTokenizer::TK_PR_ONREADY) { - //may be fallthrough from export, ignore if so - tokenizer->advance(); + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + _set_error("Expected 'var'."); + return; + } + + continue; + } break; + case GDTokenizer::TK_PR_REMOTE: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (current_export.type) { if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { _set_error("Expected 'var'."); return; } + + } else { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + _set_error("Expected 'var' or 'func'."); + return; + } } - }; //fallthrough to var + rpc_mode=ScriptInstance::RPC_MODE_REMOTE; + + continue; + } break; + case GDTokenizer::TK_PR_MASTER: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (current_export.type) { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + _set_error("Expected 'var'."); + return; + } + + } else { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + _set_error("Expected 'var' or 'func'."); + return; + } + } + + rpc_mode=ScriptInstance::RPC_MODE_MASTER; + continue; + } break; + case GDTokenizer::TK_PR_SLAVE: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (current_export.type) { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR) { + _set_error("Expected 'var'."); + return; + } + + } else { + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + _set_error("Expected 'var' or 'func'."); + return; + } + } + + rpc_mode=ScriptInstance::RPC_MODE_SLAVE; + continue; + } break; + case GDTokenizer::TK_PR_SYNC: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (tokenizer->get_token()!=GDTokenizer::TK_PR_VAR && tokenizer->get_token()!=GDTokenizer::TK_PR_FUNCTION) { + if (current_export.type) + _set_error("Expected 'var'."); + else + _set_error("Expected 'var' or 'func'."); + return; + } + + rpc_mode=ScriptInstance::RPC_MODE_SYNC; + continue; + } break; case GDTokenizer::TK_PR_VAR: { //variale declaration and (eventual) initialization @@ -2866,8 +3282,12 @@ void GDParser::_parse_class(ClassNode *p_class) { member.expression=NULL; member._export.name=member.identifier; member.line=tokenizer->get_token_line(); + member.rpc_mode=rpc_mode; + tokenizer->advance(); + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; + if (tokenizer->get_token()==GDTokenizer::TK_OP_ASSIGN) { #ifdef DEBUG_ENABLED @@ -2926,6 +3346,17 @@ void GDParser::_parse_class(ClassNode *p_class) { return; } member._export.type=cn->value.get_type(); + member._export.usage|=PROPERTY_USAGE_SCRIPT_VARIABLE; + if (cn->value.get_type()==Variant::OBJECT) { + Object *obj = cn->value; + Resource *res = obj->cast_to<Resource>(); + if(res==NULL) { + _set_error("Exported constant not a type or resource."); + return; + } + member._export.hint=PROPERTY_HINT_RESOURCE_TYPE; + member._export.hint_string=res->get_class(); + } } } #ifdef TOOLS_ENABLED @@ -3054,7 +3485,124 @@ void GDParser::_parse_class(ClassNode *p_class) { } } break; + case GDTokenizer::TK_PR_ENUM: { + //mutiple constant declarations.. + int last_assign = -1; // Incremented by 1 right before the assingment. + String enum_name; + Dictionary enum_dict; + + tokenizer->advance(); + if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER) { + enum_name=tokenizer->get_token_identifier(); + tokenizer->advance(); + } + if (tokenizer->get_token()!=GDTokenizer::TK_CURLY_BRACKET_OPEN) { + _set_error("Expected '{' in enum declaration"); + return; + } + tokenizer->advance(); + + while(true) { + if(tokenizer->get_token()==GDTokenizer::TK_NEWLINE) { + + tokenizer->advance(); // Ignore newlines + } else if (tokenizer->get_token()==GDTokenizer::TK_CURLY_BRACKET_CLOSE) { + + tokenizer->advance(); + break; // End of enum + } else if (tokenizer->get_token()!=GDTokenizer::TK_IDENTIFIER) { + + if(tokenizer->get_token()==GDTokenizer::TK_EOF) { + _set_error("Unexpected end of file."); + } else { + _set_error(String("Unexpected ") + GDTokenizer::get_token_name(tokenizer->get_token()) + ", expected identifier"); + } + + return; + } else { // tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER + ClassNode::Constant constant; + + constant.identifier=tokenizer->get_token_identifier(); + + tokenizer->advance(); + + if (tokenizer->get_token()==GDTokenizer::TK_OP_ASSIGN) { + tokenizer->advance(); + + Node *subexpr=NULL; + + subexpr = _parse_and_reduce_expression(p_class,true,true); + if (!subexpr) { + if (_recover_from_completion()) { + break; + } + return; + } + + if (subexpr->type!=Node::TYPE_CONSTANT) { + _set_error("Expected constant expression"); + } + + const ConstantNode *subexpr_const = static_cast<const ConstantNode*>(subexpr); + + if(subexpr_const->value.get_type() != Variant::INT) { + _set_error("Expected an int value for enum"); + } + + last_assign = subexpr_const->value; + + constant.expression=subexpr; + + } else { + last_assign = last_assign + 1; + ConstantNode *cn = alloc_node<ConstantNode>(); + cn->value = last_assign; + constant.expression = cn; + } + + if(tokenizer->get_token()==GDTokenizer::TK_COMMA) { + tokenizer->advance(); + } + + if(enum_name != "") { + const ConstantNode *cn = static_cast<const ConstantNode*>(constant.expression); + enum_dict[constant.identifier] = cn->value; + } + + p_class->constant_expressions.push_back(constant); + } + + } + + if(enum_name != "") { + ClassNode::Constant enum_constant; + enum_constant.identifier=enum_name; + ConstantNode *cn = alloc_node<ConstantNode>(); + cn->value = enum_dict; + enum_constant.expression=cn; + p_class->constant_expressions.push_back(enum_constant); + } + + if (!_end_statement()) { + _set_error("Expected end of statement (enum)"); + return; + } + + + + + } break; + + case GDTokenizer::TK_CONSTANT: { + if(tokenizer->get_token_constant().get_type() == Variant::STRING) { + tokenizer->advance(); + // Ignore + } else { + _set_error(String()+"Unexpected constant of type: "+Variant::get_type_name(tokenizer->get_token_constant().get_type())); + return; + } + } break; default: { @@ -3209,6 +3757,7 @@ void GDParser::clear() { current_class=NULL; completion_found=false; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; current_function=NULL; @@ -3272,6 +3821,11 @@ int GDParser::get_completion_argument_index() { return completion_argument; } +int GDParser::get_completion_identifier_is_function() { + + return completion_ident_is_call; +} + GDParser::GDParser() { head=NULL; diff --git a/modules/gdscript/gd_parser.h b/modules/gdscript/gd_parser.h index 6c49c1df5..e8f5f0f98 100644 --- a/modules/gdscript/gd_parser.h +++ b/modules/gdscript/gd_parser.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,6 +33,7 @@ #include "gd_functions.h" #include "map.h" #include "object.h" +#include "script_language.h" class GDParser { public: @@ -88,6 +89,7 @@ public: StringName getter; int line; Node *expression; + ScriptInstance::RPCMode rpc_mode; }; struct Constant { StringName identifier; @@ -119,12 +121,13 @@ public: struct FunctionNode : public Node { bool _static; + ScriptInstance::RPCMode rpc_mode; StringName name; Vector<StringName> arguments; Vector<Node*> default_values; BlockNode *body; - FunctionNode() { type=TYPE_FUNCTION; _static=false; } + FunctionNode() { type=TYPE_FUNCTION; _static=false; rpc_mode=ScriptInstance::RPC_MODE_DISABLED; } }; @@ -206,6 +209,7 @@ public: OP_INDEX_NAMED, //unary operators OP_NEG, + OP_POS, OP_NOT, OP_BIT_INVERT, OP_PREINC, @@ -244,6 +248,9 @@ public: OP_BIT_AND, OP_BIT_OR, OP_BIT_XOR, + //ternary operators + OP_TERNARY_IF, + OP_TERNARY_ELSE, }; Operator op; @@ -369,13 +376,15 @@ public: enum CompletionType { COMPLETION_NONE, COMPLETION_BUILT_IN_TYPE_CONSTANT, + COMPLETION_GET_NODE, COMPLETION_FUNCTION, COMPLETION_IDENTIFIER, COMPLETION_PARENT_FUNCTION, COMPLETION_METHOD, COMPLETION_CALL_ARGUMENTS, COMPLETION_INDEX, - COMPLETION_VIRTUAL_FUNC + COMPLETION_VIRTUAL_FUNC, + COMPLETION_YIELD, }; @@ -425,9 +434,13 @@ private: int completion_line; int completion_argument; bool completion_found; + bool completion_ident_is_call; PropertyInfo current_export; + ScriptInstance::RPCMode rpc_mode; + + void _set_error(const String& p_error, int p_line=-1, int p_column=-1); bool _recover_from_completion(); @@ -435,7 +448,7 @@ private: bool _parse_arguments(Node* p_parent, Vector<Node*>& p_args, bool p_static, bool p_can_codecomplete=false); bool _enter_indent_block(BlockNode *p_block=NULL); bool _parse_newline(); - Node* _parse_expression(Node *p_parent,bool p_static,bool p_allow_assign=false); + Node* _parse_expression(Node *p_parent, bool p_static, bool p_allow_assign=false, bool p_parsing_constant=false); Node* _reduce_expression(Node *p_node,bool p_to_const=false); Node* _parse_and_reduce_expression(Node *p_parent,bool p_static,bool p_reduce_const=false,bool p_allow_assign=false); @@ -468,7 +481,7 @@ public: BlockNode *get_completion_block(); FunctionNode *get_completion_function(); int get_completion_argument_index(); - + int get_completion_identifier_is_function(); void clear(); GDParser(); diff --git a/modules/gdscript/gd_script.cpp b/modules/gdscript/gd_script.cpp index b1919b346..b216ef42b 100644 --- a/modules/gdscript/gd_script.cpp +++ b/modules/gdscript/gd_script.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,1432 +34,6 @@ #include "io/file_access_encrypted.h" #include "os/os.h" - - -Variant *GDFunction::_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self, Variant *p_stack,String& r_error) const{ - - int address = p_address&ADDR_MASK; - - //sequential table (jump table generated by compiler) - switch((p_address&ADDR_TYPE_MASK)>>ADDR_BITS) { - - case ADDR_TYPE_SELF: { - - if (!p_instance) { - r_error="Cannot access self without instance."; - return NULL; - } - return &self; - } break; - case ADDR_TYPE_CLASS: { - - return &p_script->_static_ref; - } break; - case ADDR_TYPE_MEMBER: { - //member indexing is O(1) - if (!p_instance) { - r_error="Cannot access member without instance."; - return NULL; - } - return &p_instance->members[address]; - } break; - case ADDR_TYPE_CLASS_CONSTANT: { - - //todo change to index! - GDScript *o=p_script; - ERR_FAIL_INDEX_V(address,_global_names_count,NULL); - const StringName *sn = &_global_names_ptr[address]; - - while(o) { - GDScript *s=o; - while(s) { - - Map<StringName,Variant>::Element *E=s->constants.find(*sn); - if (E) { - return &E->get(); - } - s=s->_base; - } - o=o->_owner; - } - - - ERR_EXPLAIN("GDCompiler bug.."); - ERR_FAIL_V(NULL); - } break; - case ADDR_TYPE_LOCAL_CONSTANT: { - ERR_FAIL_INDEX_V(address,_constant_count,NULL); - return &_constants_ptr[address]; - } break; - case ADDR_TYPE_STACK: - case ADDR_TYPE_STACK_VARIABLE: { - ERR_FAIL_INDEX_V(address,_stack_size,NULL); - return &p_stack[address]; - } break; - case ADDR_TYPE_GLOBAL: { - - - ERR_FAIL_INDEX_V(address,GDScriptLanguage::get_singleton()->get_global_array_size(),NULL); - - - return &GDScriptLanguage::get_singleton()->get_global_array()[address]; - } break; - case ADDR_TYPE_NIL: { - return &nil; - } break; - } - - ERR_EXPLAIN("Bad Code! (Addressing Mode)"); - ERR_FAIL_V(NULL); - return NULL; -} - - -String GDFunction::_get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const { - - - - String err_text; - - if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { - int errorarg=p_err.argument; - err_text="Invalid type in "+p_where+". Cannot convert argument "+itos(errorarg+1)+" from "+Variant::get_type_name(argptrs[errorarg]->get_type())+" to "+Variant::get_type_name(p_err.expected)+"."; - } else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments."; - } else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments."; - } else if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { - err_text="Invalid call. Nonexistent "+p_where+"."; - } else if (p_err.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) { - err_text="Attempt to call "+p_where+" on a null instance."; - } else { - err_text="Bug, call error: #"+itos(p_err.error); - } - - return err_text; - -} - -static String _get_var_type(const Variant* p_type) { - - String basestr; - - if (p_type->get_type()==Variant::OBJECT) { - Object *bobj = *p_type; - if (!bobj) { - basestr = "null instance"; - } else { -#ifdef DEBUG_ENABLED - if (ObjectDB::instance_validate(bobj)) { - if (bobj->get_script_instance()) - basestr= bobj->get_type()+" ("+bobj->get_script_instance()->get_script()->get_path().get_file()+")"; - else - basestr = bobj->get_type(); - } else { - basestr="previously freed instance"; - } - -#else - basestr="Object"; -#endif - } - - } else { - basestr = Variant::get_type_name(p_type->get_type()); - } - - return basestr; - -} - -Variant GDFunction::call(GDInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError& r_err, CallState *p_state) { - - - if (!_code_ptr) { - - return Variant(); - } - - r_err.error=Variant::CallError::CALL_OK; - - Variant self; - Variant retvalue; - Variant *stack = NULL; - Variant **call_args; - int defarg=0; - -#ifdef DEBUG_ENABLED - - //GDScriptLanguage::get_singleton()->calls++; - -#endif - - uint32_t alloca_size=0; - GDScript *_class; - int ip=0; - int line=_initial_line; - - - - if (p_state) { - //use existing (supplied) state (yielded) - stack=(Variant*)p_state->stack.ptr(); - call_args=(Variant**)&p_state->stack[sizeof(Variant)*p_state->stack_size]; - line=p_state->line; - ip=p_state->ip; - alloca_size=p_state->stack.size(); - _class=p_state->_class; - p_instance=p_state->instance; - defarg=p_state->defarg; - self=p_state->self; - //stack[p_state->result_pos]=p_state->result; //assign stack with result - - } else { - - if (p_argcount!=_argument_count) { - - if (p_argcount>_argument_count) { - - r_err.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument=_argument_count; - - - return Variant(); - } else if (p_argcount < _argument_count - _default_arg_count) { - - r_err.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument=_argument_count - _default_arg_count; - return Variant(); - } else { - - defarg=_argument_count-p_argcount; - } - } - - alloca_size = sizeof(Variant*)*_call_size + sizeof(Variant)*_stack_size; - - if (alloca_size) { - - uint8_t *aptr = (uint8_t*)alloca(alloca_size); - - if (_stack_size) { - - stack=(Variant*)aptr; - for(int i=0;i<p_argcount;i++) - memnew_placement(&stack[i],Variant(*p_args[i])); - for(int i=p_argcount;i<_stack_size;i++) - memnew_placement(&stack[i],Variant); - } else { - stack=NULL; - } - - if (_call_size) { - - call_args = (Variant**)&aptr[sizeof(Variant)*_stack_size]; - } else { - - call_args=NULL; - } - - - } else { - stack=NULL; - call_args=NULL; - } - - if (p_instance) { - if (p_instance->base_ref && static_cast<Reference*>(p_instance->owner)->is_referenced()) { - - self=REF(static_cast<Reference*>(p_instance->owner)); - } else { - self=p_instance->owner; - } - _class=p_instance->script.ptr(); - } else { - _class=_script; - } - } - - String err_text; - -#ifdef DEBUG_ENABLED - - if (ScriptDebugger::get_singleton()) - GDScriptLanguage::get_singleton()->enter_function(p_instance,this,stack,&ip,&line); - -#define CHECK_SPACE(m_space)\ - ERR_BREAK((ip+m_space)>_code_size) - -#define GET_VARIANT_PTR(m_v,m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);\ - if (!m_v)\ - break; - - -#else -#define CHECK_SPACE(m_space) -#define GET_VARIANT_PTR(m_v,m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text); - -#endif - - -#ifdef DEBUG_ENABLED - - uint64_t function_start_time; - uint64_t function_call_time; - - if (GDScriptLanguage::get_singleton()->profiling) { - function_start_time=OS::get_singleton()->get_ticks_usec(); - function_call_time=0; - profile.call_count++; - profile.frame_call_count++; - } -#endif - bool exit_ok=false; - - while(ip<_code_size) { - - - int last_opcode=_code_ptr[ip]; - switch(_code_ptr[ip]) { - - case OPCODE_OPERATOR: { - - CHECK_SPACE(5); - - bool valid; - Variant::Operator op = (Variant::Operator)_code_ptr[ip+1]; - ERR_BREAK(op>=Variant::OP_MAX); - - GET_VARIANT_PTR(a,2); - GET_VARIANT_PTR(b,3); - GET_VARIANT_PTR(dst,4); - -#ifdef DEBUG_ENABLED - Variant ret; - Variant::evaluate(op,*a,*b,ret,valid); -#else - Variant::evaluate(op,*a,*b,*dst,valid); -#endif - - if (!valid) { -#ifdef DEBUG_ENABLED - - if (ret.get_type()==Variant::STRING) { - //return a string when invalid with the error - err_text=ret; - err_text += " in operator '"+Variant::get_operator_name(op)+"'."; - } else { - err_text="Invalid operands '"+Variant::get_type_name(a->get_type())+"' and '"+Variant::get_type_name(b->get_type())+"' in operator '"+Variant::get_operator_name(op)+"'."; - } -#endif - break; - - } -#ifdef DEBUG_ENABLED - *dst=ret; -#endif - - ip+=5; - - } continue; - case OPCODE_EXTENDS_TEST: { - - CHECK_SPACE(4); - - GET_VARIANT_PTR(a,1); - GET_VARIANT_PTR(b,2); - GET_VARIANT_PTR(dst,3); - -#ifdef DEBUG_ENABLED - - if (a->get_type()!=Variant::OBJECT || a->operator Object*()==NULL) { - - err_text="Left operand of 'extends' is not an instance of anything."; - break; - - } - if (b->get_type()!=Variant::OBJECT || b->operator Object*()==NULL) { - - err_text="Right operand of 'extends' is not a class."; - break; - - } -#endif - - - Object *obj_A = *a; - Object *obj_B = *b; - - - GDScript *scr_B = obj_B->cast_to<GDScript>(); - - bool extends_ok=false; - - if (scr_B) { - //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this shoul return false. - - if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language()==GDScriptLanguage::get_singleton()) { - - GDInstance *ins = static_cast<GDInstance*>(obj_A->get_script_instance()); - GDScript *cmp = ins->script.ptr(); - //bool found=false; - while(cmp) { - - if (cmp==scr_B) { - //inherits from script, all ok - extends_ok=true; - break; - - } - - cmp=cmp->_base; - } - - } - - - } else { - - GDNativeClass *nc= obj_B->cast_to<GDNativeClass>(); - - if (!nc) { - - err_text="Right operand of 'extends' is not a class (type: '"+obj_B->get_type()+"')."; - break; - } - - extends_ok=ObjectTypeDB::is_type(obj_A->get_type_name(),nc->get_name()); - } - - *dst=extends_ok; - ip+=4; - - } continue; - case OPCODE_SET: { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst,1); - GET_VARIANT_PTR(index,2); - GET_VARIANT_PTR(value,3); - - bool valid; - dst->set(*index,*value,&valid); - - if (!valid) { - String v = index->operator String(); - if (v!="") { - v="'"+v+"'"; - } else { - v="of type '"+_get_var_type(index)+"'"; - } - err_text="Invalid set index "+v+" (on base: '"+_get_var_type(dst)+"')."; - break; - } - - ip+=4; - } continue; - case OPCODE_GET: { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(src,1); - GET_VARIANT_PTR(index,2); - GET_VARIANT_PTR(dst,3); - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get(*index,&valid); -#else - *dst = src->get(*index,&valid); - -#endif - if (!valid) { - String v = index->operator String(); - if (v!="") { - v="'"+v+"'"; - } else { - v="of type '"+_get_var_type(index)+"'"; - } - err_text="Invalid get index "+v+" (on base: '"+_get_var_type(src)+"')."; - break; - } -#ifdef DEBUG_ENABLED - *dst=ret; -#endif - ip+=4; - } continue; - case OPCODE_SET_NAMED: { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst,1); - GET_VARIANT_PTR(value,3); - - int indexname = _code_ptr[ip+2]; - - ERR_BREAK(indexname<0 || indexname>=_global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; - dst->set_named(*index,*value,&valid); - - if (!valid) { - String err_type; - err_text="Invalid set index '"+String(*index)+"' (on base: '"+_get_var_type(dst)+"')."; - break; - } - - ip+=4; - } continue; - case OPCODE_GET_NAMED: { - - - CHECK_SPACE(3); - - GET_VARIANT_PTR(src,1); - GET_VARIANT_PTR(dst,3); - - int indexname = _code_ptr[ip+2]; - - ERR_BREAK(indexname<0 || indexname>=_global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get_named(*index,&valid); - -#else - *dst = src->get_named(*index,&valid); -#endif - - if (!valid) { - if (src->has_method(*index)) { - err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"'). Did you mean '."+index->operator String()+"()' ?"; - } else { - err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"')."; - } - break; - } -#ifdef DEBUG_ENABLED - *dst=ret; -#endif - ip+=4; - } continue; - case OPCODE_ASSIGN: { - - CHECK_SPACE(3); - GET_VARIANT_PTR(dst,1); - GET_VARIANT_PTR(src,2); - - *dst = *src; - - ip+=3; - - } continue; - case OPCODE_ASSIGN_TRUE: { - - CHECK_SPACE(2); - GET_VARIANT_PTR(dst,1); - - *dst = true; - - ip+=2; - } continue; - case OPCODE_ASSIGN_FALSE: { - - CHECK_SPACE(2); - GET_VARIANT_PTR(dst,1); - - *dst = false; - - ip+=2; - } continue; - case OPCODE_CONSTRUCT: { - - CHECK_SPACE(2); - Variant::Type t=Variant::Type(_code_ptr[ip+1]); - int argc=_code_ptr[ip+2]; - CHECK_SPACE(argc+2); - Variant **argptrs = call_args; - for(int i=0;i<argc;i++) { - GET_VARIANT_PTR(v,3+i); - argptrs[i]=v; - } - - GET_VARIANT_PTR(dst,3+argc); - Variant::CallError err; - *dst = Variant::construct(t,(const Variant**)argptrs,argc,err); - - if (err.error!=Variant::CallError::CALL_OK) { - - err_text=_get_call_error(err,"'"+Variant::get_type_name(t)+"' constructor",(const Variant**)argptrs); - break; - } - - ip+=4+argc; - //construct a basic type - } continue; - case OPCODE_CONSTRUCT_ARRAY: { - - CHECK_SPACE(1); - int argc=_code_ptr[ip+1]; - Array array(true); //arrays are always shared - array.resize(argc); - CHECK_SPACE(argc+2); - - for(int i=0;i<argc;i++) { - GET_VARIANT_PTR(v,2+i); - array[i]=*v; - - } - - GET_VARIANT_PTR(dst,2+argc); - - *dst=array; - - ip+=3+argc; - - } continue; - case OPCODE_CONSTRUCT_DICTIONARY: { - - CHECK_SPACE(1); - int argc=_code_ptr[ip+1]; - Dictionary dict(true); //arrays are always shared - - CHECK_SPACE(argc*2+2); - - for(int i=0;i<argc;i++) { - - GET_VARIANT_PTR(k,2+i*2+0); - GET_VARIANT_PTR(v,2+i*2+1); - dict[*k]=*v; - - } - - GET_VARIANT_PTR(dst,2+argc*2); - - *dst=dict; - - ip+=3+argc*2; - - } continue; - case OPCODE_CALL_RETURN: - case OPCODE_CALL: { - - - CHECK_SPACE(4); - bool call_ret = _code_ptr[ip]==OPCODE_CALL_RETURN; - - int argc=_code_ptr[ip+1]; - GET_VARIANT_PTR(base,2); - int nameg=_code_ptr[ip+3]; - - ERR_BREAK(nameg<0 || nameg>=_global_names_count); - const StringName *methodname = &_global_names_ptr[nameg]; - - ERR_BREAK(argc<0); - ip+=4; - CHECK_SPACE(argc+1); - Variant **argptrs = call_args; - - for(int i=0;i<argc;i++) { - GET_VARIANT_PTR(v,i); - argptrs[i]=v; - } - -#ifdef DEBUG_ENABLED - uint64_t call_time; - - if (GDScriptLanguage::get_singleton()->profiling) { - call_time=OS::get_singleton()->get_ticks_usec(); - } - -#endif - Variant::CallError err; - if (call_ret) { - - GET_VARIANT_PTR(ret,argc); - *ret = base->call(*methodname,(const Variant**)argptrs,argc,err); - } else { - - base->call(*methodname,(const Variant**)argptrs,argc,err); - } -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - function_call_time+=OS::get_singleton()->get_ticks_usec() - call_time; - } -#endif - - if (err.error!=Variant::CallError::CALL_OK) { - - - String methodstr = *methodname; - String basestr = _get_var_type(base); - - if (methodstr=="call") { - if (argc>=1) { - methodstr=String(*argptrs[0])+" (via call)"; - if (err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { - err.argument-=1; - } - } - } if (methodstr=="free") { - - if (err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { - - if (base->is_ref()) { - err_text="Attempted to free a reference."; - break; - } else if (base->get_type()==Variant::OBJECT) { - - err_text="Attempted to free a locked object (calling or emitting)."; - break; - } - } - } - err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs); - break; - } - - //_call_func(NULL,base,*methodname,ip,argc,p_instance,stack); - ip+=argc+1; - - } continue; - case OPCODE_CALL_BUILT_IN: { - - CHECK_SPACE(4); - - GDFunctions::Function func = GDFunctions::Function(_code_ptr[ip+1]); - int argc=_code_ptr[ip+2]; - ERR_BREAK(argc<0); - - ip+=3; - CHECK_SPACE(argc+1); - Variant **argptrs = call_args; - - for(int i=0;i<argc;i++) { - GET_VARIANT_PTR(v,i); - argptrs[i]=v; - } - - GET_VARIANT_PTR(dst,argc); - - Variant::CallError err; - - GDFunctions::call(func,(const Variant**)argptrs,argc,*dst,err); - - if (err.error!=Variant::CallError::CALL_OK) { - - - String methodstr = GDFunctions::get_func_name(func); - err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs); - break; - } - ip+=argc+1; - - } continue; - case OPCODE_CALL_SELF: { - - - } break; - case OPCODE_CALL_SELF_BASE: { - - CHECK_SPACE(2); - int self_fun = _code_ptr[ip+1]; -#ifdef DEBUG_ENABLED - - if (self_fun<0 || self_fun>=_global_names_count) { - - err_text="compiler bug, function name not found"; - break; - } -#endif - const StringName *methodname = &_global_names_ptr[self_fun]; - - int argc=_code_ptr[ip+2]; - - CHECK_SPACE(2+argc+1); - - Variant **argptrs = call_args; - - for(int i=0;i<argc;i++) { - GET_VARIANT_PTR(v,i+3); - argptrs[i]=v; - } - - GET_VARIANT_PTR(dst,argc+3); - - const GDScript *gds = _script; - - - const Map<StringName,GDFunction*>::Element *E=NULL; - while (gds->base.ptr()) { - gds=gds->base.ptr(); - E=gds->member_functions.find(*methodname); - if (E) - break; - } - - Variant::CallError err; - - if (E) { - - *dst=E->get()->call(p_instance,(const Variant**)argptrs,argc,err); - } else if (gds->native.ptr()) { - - if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) { - - MethodBind *mb = ObjectTypeDB::get_method(gds->native->get_name(),*methodname); - if (!mb) { - err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; - } else { - *dst=mb->call(p_instance->owner,(const Variant**)argptrs,argc,err); - } - } else { - err.error=Variant::CallError::CALL_OK; - } - } else { - - if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) { - err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; - } else { - err.error=Variant::CallError::CALL_OK; - } - } - - - if (err.error!=Variant::CallError::CALL_OK) { - - - String methodstr = *methodname; - err_text=_get_call_error(err,"function '"+methodstr+"'",(const Variant**)argptrs); - - break; - } - - ip+=4+argc; - - } continue; - case OPCODE_YIELD: - case OPCODE_YIELD_SIGNAL: { - - int ipofs=1; - if (_code_ptr[ip]==OPCODE_YIELD_SIGNAL) { - CHECK_SPACE(4); - ipofs+=2; - } else { - CHECK_SPACE(2); - - } - - Ref<GDFunctionState> gdfs = memnew( GDFunctionState ); - gdfs->function=this; - - gdfs->state.stack.resize(alloca_size); - //copy variant stack - for(int i=0;i<_stack_size;i++) { - memnew_placement(&gdfs->state.stack[sizeof(Variant)*i],Variant(stack[i])); - } - gdfs->state.stack_size=_stack_size; - gdfs->state.self=self; - gdfs->state.alloca_size=alloca_size; - gdfs->state._class=_class; - gdfs->state.ip=ip+ipofs; - gdfs->state.line=line; - //gdfs->state.result_pos=ip+ipofs-1; - gdfs->state.defarg=defarg; - gdfs->state.instance=p_instance; - gdfs->function=this; - - retvalue=gdfs; - - if (_code_ptr[ip]==OPCODE_YIELD_SIGNAL) { - GET_VARIANT_PTR(argobj,1); - GET_VARIANT_PTR(argname,2); - //do the oneshot connect - - if (argobj->get_type()!=Variant::OBJECT) { - err_text="First argument of yield() not of type object."; - break; - } - if (argname->get_type()!=Variant::STRING) { - err_text="Second argument of yield() not a string (for signal name)."; - break; - } - Object *obj=argobj->operator Object *(); - String signal = argname->operator String(); -#ifdef DEBUG_ENABLED - - if (!obj) { - err_text="First argument of yield() is null."; - break; - } - if (ScriptDebugger::get_singleton()) { - if (!ObjectDB::instance_validate(obj)) { - err_text="First argument of yield() is a previously freed instance."; - break; - } - } - if (signal.length()==0) { - - err_text="Second argument of yield() is an empty string (for signal name)."; - break; - } - -#endif - Error err = obj->connect(signal,gdfs.ptr(),"_signal_callback",varray(gdfs),Object::CONNECT_ONESHOT); - if (err!=OK) { - err_text="Error connecting to signal: "+signal+" during yield()."; - break; - } - - - } - - exit_ok=true; - - } break; - case OPCODE_YIELD_RESUME: { - - CHECK_SPACE(2); - if (!p_state) { - err_text=("Invalid Resume (bug?)"); - break; - } - GET_VARIANT_PTR(result,1); - *result=p_state->result; - ip+=2; - - } continue; - case OPCODE_JUMP: { - - CHECK_SPACE(2); - int to = _code_ptr[ip+1]; - - ERR_BREAK(to<0 || to>_code_size); - ip=to; - - } continue; - case OPCODE_JUMP_IF: { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(test,1); - - bool valid; - bool result = test->booleanize(valid); -#ifdef DEBUG_ENABLED - if (!valid) { - - err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); - break; - } -#endif - if (result) { - int to = _code_ptr[ip+2]; - ERR_BREAK(to<0 || to>_code_size); - ip=to; - continue; - } - ip+=3; - } continue; - case OPCODE_JUMP_IF_NOT: { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(test,1); - - bool valid; - bool result = test->booleanize(valid); -#ifdef DEBUG_ENABLED - if (!valid) { - - err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); - break; - } -#endif - if (!result) { - int to = _code_ptr[ip+2]; - ERR_BREAK(to<0 || to>_code_size); - ip=to; - continue; - } - ip+=3; - } continue; - case OPCODE_JUMP_TO_DEF_ARGUMENT: { - - CHECK_SPACE(2); - ip=_default_arg_ptr[defarg]; - - } continue; - case OPCODE_RETURN: { - - CHECK_SPACE(2); - GET_VARIANT_PTR(r,1); - retvalue=*r; - exit_ok=true; - - } break; - case OPCODE_ITERATE_BEGIN: { - - CHECK_SPACE(8); //space for this an regular iterate - - GET_VARIANT_PTR(counter,1); - GET_VARIANT_PTR(container,2); - - bool valid; - if (!container->iter_init(*counter,valid)) { - if (!valid) { - err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"'."; - break; - } - int jumpto=_code_ptr[ip+3]; - ERR_BREAK(jumpto<0 || jumpto>_code_size); - ip=jumpto; - continue; - } - GET_VARIANT_PTR(iterator,4); - - - *iterator=container->iter_get(*counter,valid); - if (!valid) { - err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"'."; - break; - } - - - ip+=5; //skip regular iterate which is always next - - } continue; - case OPCODE_ITERATE: { - - CHECK_SPACE(4); - - GET_VARIANT_PTR(counter,1); - GET_VARIANT_PTR(container,2); - - bool valid; - if (!container->iter_next(*counter,valid)) { - if (!valid) { - err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"' (type changed since first iteration?)."; - break; - } - int jumpto=_code_ptr[ip+3]; - ERR_BREAK(jumpto<0 || jumpto>_code_size); - ip=jumpto; - continue; - } - GET_VARIANT_PTR(iterator,4); - - *iterator=container->iter_get(*counter,valid); - if (!valid) { - err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"' (but was obtained on first iteration?)."; - break; - } - - ip+=5; //loop again - } continue; - case OPCODE_ASSERT: { - CHECK_SPACE(2); - GET_VARIANT_PTR(test,1); - -#ifdef DEBUG_ENABLED - bool valid; - bool result = test->booleanize(valid); - - - if (!valid) { - - err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type()); - break; - } - - - if (!result) { - - err_text="Assertion failed."; - break; - } - -#endif - - ip+=2; - } continue; - case OPCODE_BREAKPOINT: { -#ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton()) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement",true); - } -#endif - ip+=1; - } continue; - case OPCODE_LINE: { - CHECK_SPACE(2); - - line=_code_ptr[ip+1]; - ip+=2; - - if (ScriptDebugger::get_singleton()) { - // line - bool do_break=false; - - if (ScriptDebugger::get_singleton()->get_lines_left()>0) { - - if (ScriptDebugger::get_singleton()->get_depth()<=0) - ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 ); - if (ScriptDebugger::get_singleton()->get_lines_left()<=0) - do_break=true; - } - - if (ScriptDebugger::get_singleton()->is_breakpoint(line,source)) - do_break=true; - - if (do_break) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint",true); - } - - ScriptDebugger::get_singleton()->line_poll(); - - } - } continue; - case OPCODE_END: { - - exit_ok=true; - break; - - } break; - default: { - - err_text="Illegal opcode "+itos(_code_ptr[ip])+" at address "+itos(ip); - } break; - - } - - if (exit_ok) - break; - //error - // function, file, line, error, explanation - String err_file; - if (p_instance) - err_file=p_instance->script->path; - else if (_class) - err_file=_class->path; - if (err_file=="") - err_file="<built-in>"; - String err_func = name; - if (p_instance && p_instance->script->name!="") - err_func=p_instance->script->name+"."+err_func; - int err_line=line; - if (err_text=="") { - err_text="Internal Script Error! - opcode #"+itos(last_opcode)+" (report please)."; - } - - if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) { - // debugger break did not happen - - _err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,err_text.utf8().get_data(),ERR_HANDLER_SCRIPT); - } - - - break; - } - -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; - profile.total_time+=time_taken; - profile.self_time+=time_taken-function_call_time; - profile.frame_total_time+=time_taken; - profile.frame_self_time+=time_taken-function_call_time; - GDScriptLanguage::get_singleton()->script_frame_time+=time_taken-function_call_time; - - } - -#endif - if (ScriptDebugger::get_singleton()) - GDScriptLanguage::get_singleton()->exit_function(); - - - if (_stack_size) { - //free stack - for(int i=0;i<_stack_size;i++) - stack[i].~Variant(); - } - - return retvalue; - -} - -const int* GDFunction::get_code() const { - - return _code_ptr; -} -int GDFunction::get_code_size() const{ - - return _code_size; -} - -Variant GDFunction::get_constant(int p_idx) const { - - ERR_FAIL_INDEX_V(p_idx,constants.size(),"<errconst>"); - return constants[p_idx]; -} - -StringName GDFunction::get_global_name(int p_idx) const { - - ERR_FAIL_INDEX_V(p_idx,global_names.size(),"<errgname>"); - return global_names[p_idx]; -} - -int GDFunction::get_default_argument_count() const { - - return default_arguments.size(); -} -int GDFunction::get_default_argument_addr(int p_arg) const{ - - ERR_FAIL_INDEX_V(p_arg,default_arguments.size(),-1); - return default_arguments[p_arg]; -} - - -StringName GDFunction::get_name() const { - - return name; -} - -int GDFunction::get_max_stack_size() const { - - return _stack_size; -} - -struct _GDFKC { - - int order; - List<int> pos; -}; - -struct _GDFKCS { - - int order; - StringName id; - int pos; - - bool operator<(const _GDFKCS &p_r) const { - - return order<p_r.order; - } -}; - -void GDFunction::debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const { - - - int oc=0; - Map<StringName,_GDFKC> sdmap; - for( const List<StackDebug>::Element *E=stack_debug.front();E;E=E->next()) { - - const StackDebug &sd=E->get(); - if (sd.line>p_line) - break; - - if (sd.added) { - - if (!sdmap.has(sd.identifier)) { - _GDFKC d; - d.order=oc++; - d.pos.push_back(sd.pos); - sdmap[sd.identifier]=d; - - } else { - sdmap[sd.identifier].pos.push_back(sd.pos); - } - } else { - - - ERR_CONTINUE(!sdmap.has(sd.identifier)); - - sdmap[sd.identifier].pos.pop_back(); - if (sdmap[sd.identifier].pos.empty()) - sdmap.erase(sd.identifier); - } - - } - - - List<_GDFKCS> stackpositions; - for(Map<StringName,_GDFKC>::Element *E=sdmap.front();E;E=E->next() ) { - - _GDFKCS spp; - spp.id=E->key(); - spp.order=E->get().order; - spp.pos=E->get().pos.back()->get(); - stackpositions.push_back(spp); - } - - stackpositions.sort(); - - for(List<_GDFKCS>::Element *E=stackpositions.front();E;E=E->next()) { - - Pair<StringName,int> p; - p.first=E->get().id; - p.second=E->get().pos; - r_stackvars->push_back(p); - } - - -} - -#if 0 -void GDFunction::clear() { - - name=StringName(); - constants.clear(); - _stack_size=0; - code.clear(); - _constants_ptr=NULL; - _constant_count=0; - _global_names_ptr=NULL; - _global_names_count=0; - _code_ptr=NULL; - _code_size=0; - -} -#endif -GDFunction::GDFunction() : function_list(this) { - - _stack_size=0; - _call_size=0; - name="<anonymous>"; -#ifdef DEBUG_ENABLED - _func_cname=NULL; - - if (GDScriptLanguage::get_singleton()->lock) { - GDScriptLanguage::get_singleton()->lock->lock(); - } - GDScriptLanguage::get_singleton()->function_list.add(&function_list); - - if (GDScriptLanguage::get_singleton()->lock) { - GDScriptLanguage::get_singleton()->lock->unlock(); - } - - profile.call_count=0; - profile.self_time=0; - profile.total_time=0; - profile.frame_call_count=0; - profile.frame_self_time=0; - profile.frame_total_time=0; - profile.last_frame_call_count=0; - profile.last_frame_self_time=0; - profile.last_frame_total_time=0; - -#endif -} - -GDFunction::~GDFunction() { -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->lock) { - GDScriptLanguage::get_singleton()->lock->lock(); - } - GDScriptLanguage::get_singleton()->function_list.remove(&function_list); - - if (GDScriptLanguage::get_singleton()->lock) { - GDScriptLanguage::get_singleton()->lock->unlock(); - } -#endif -} - -///////////////////// - - -Variant GDFunctionState::_signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error) { - - Variant arg; - r_error.error=Variant::CallError::CALL_OK; - - ERR_FAIL_COND_V(!function,Variant()); - - if (p_argcount==0) { - r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument=1; - return Variant(); - } else if (p_argcount==1) { - //noooneee - } else if (p_argcount==2) { - arg=*p_args[0]; - } else { - Array extra_args; - for(int i=0;i<p_argcount-1;i++) { - extra_args.push_back(*p_args[i]); - } - arg=extra_args; - } - - Ref<GDFunctionState> self = *p_args[p_argcount-1]; - - if (self.is_null()) { - r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument=p_argcount-1; - r_error.expected=Variant::OBJECT; - return Variant(); - } - - state.result=arg; - Variant ret = function->call(NULL,NULL,0,r_error,&state); - function=NULL; //cleaned up; - state.result=Variant(); - return ret; -} - - -bool GDFunctionState::is_valid() const { - - return function!=NULL; -} - -Variant GDFunctionState::resume(const Variant& p_arg) { - - ERR_FAIL_COND_V(!function,Variant()); - - state.result=p_arg; - Variant::CallError err; - Variant ret = function->call(NULL,NULL,0,err,&state); - function=NULL; //cleaned up; - state.result=Variant(); - return ret; -} - - -void GDFunctionState::_bind_methods() { - - ObjectTypeDB::bind_method(_MD("resume:Variant","arg"),&GDFunctionState::resume,DEFVAL(Variant())); - ObjectTypeDB::bind_method(_MD("is_valid"),&GDFunctionState::is_valid); - ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"_signal_callback",&GDFunctionState::_signal_callback,MethodInfo("_signal_callback")); - -} - -GDFunctionState::GDFunctionState() { - - function=NULL; -} - -GDFunctionState::~GDFunctionState() { - - if (function!=NULL) { - //never called, deinitialize stack - for(int i=0;i<state.stack_size;i++) { - Variant *v=(Variant*)&state.stack[sizeof(Variant)*i]; - v->~Variant(); - } - } -} - /////////////////////////// GDNativeClass::GDNativeClass(const StringName& p_name) { @@ -1476,7 +50,7 @@ GDNativeClass::GDNativeClass(const StringName& p_name) { bool GDNativeClass::_get(const StringName& p_name,Variant &r_ret) const { bool ok; - int v = ObjectTypeDB::get_integer_constant(name, p_name, &ok); + int v = ClassDB::get_integer_constant(name, p_name, &ok); if (ok) { r_ret=v; @@ -1489,7 +63,7 @@ bool GDNativeClass::_get(const StringName& p_name,Variant &r_ret) const { void GDNativeClass::_bind_methods() { - ObjectTypeDB::bind_method(_MD("new"),&GDNativeClass::_new); + ClassDB::bind_method(_MD("new"),&GDNativeClass::_new); } @@ -1512,7 +86,7 @@ Variant GDNativeClass::_new() { Object *GDNativeClass::instance() { - return ObjectTypeDB::instance(name); + return ClassDB::instance(name); } @@ -1527,18 +101,39 @@ GDInstance* GDScript::_create_instance(const Variant** p_args,int p_argcount,Obj instance->members.resize(member_indices.size()); instance->script=Ref<GDScript>(this); instance->owner=p_owner; +#ifdef DEBUG_ENABLED + //needed for hot reloading + for(Map<StringName,MemberInfo>::Element *E=member_indices.front();E;E=E->next()) { + instance->member_indices_cache[E->key()]=E->get().index; + } +#endif instance->owner->set_script_instance(instance); /* STEP 2, INITIALIZE AND CONSRTUCT */ +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->lock(); +#endif + instances.insert(instance->owner); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->unlock(); +#endif + initializer->call(instance,p_args,p_argcount,r_error); if (r_error.error!=Variant::CallError::CALL_OK) { instance->script=Ref<GDScript>(); instance->owner->set_script_instance(NULL); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->lock(); +#endif instances.erase(p_owner); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->unlock(); +#endif + ERR_FAIL_COND_V(r_error.error!=Variant::CallError::CALL_OK, NULL); //error constructing } @@ -1565,6 +160,8 @@ Variant GDScript::_new(const Variant** p_args,int p_argcount,Variant::CallError& _baseptr=_baseptr->_base; } + ERR_FAIL_COND_V(_baseptr->native.is_null(), Variant()); + if (_baseptr->native.ptr()) { owner=_baseptr->native->instance(); } else { @@ -1599,6 +196,15 @@ bool GDScript::can_instance() const { } +Ref<Script> GDScript::get_base_script() const { + + if (_base) { + return Ref<GDScript>( _base ); + } else { + return Ref<Script>(); + } +} + StringName GDScript::get_instance_base_type() const { if (native.is_valid()) @@ -1669,13 +275,94 @@ void GDScript::_update_placeholder(PlaceHolderScriptInstance *p_placeholder) { }*/ #endif + +void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { + + for (const Map<StringName,GDFunction*>::Element *E=member_functions.front();E;E=E->next()) { + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get()->get_argument_count();i++) { + PropertyInfo arg; + arg.type=Variant::NIL; //variant + arg.name=E->get()->get_argument_name(i); + mi.arguments.push_back(arg); + } + + mi.return_val.name="Variant"; + p_list->push_back(mi); + } +} + +void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { + + const GDScript *sptr=this; + List<PropertyInfo> props; + + while(sptr) { + + Vector<_GDScriptMemberSort> msort; + for(Map<StringName,PropertyInfo>::Element *E=sptr->member_info.front();E;E=E->next()) { + + _GDScriptMemberSort ms; + ERR_CONTINUE(!sptr->member_indices.has(E->key())); + ms.index=sptr->member_indices[E->key()].index; + ms.name=E->key(); + msort.push_back(ms); + + } + + msort.sort(); + msort.invert(); + for(int i=0;i<msort.size();i++) { + + props.push_front(sptr->member_info[msort[i].name]); + + } + + sptr = sptr->_base; + } + + for (List<PropertyInfo>::Element *E=props.front();E;E=E->next()) { + p_list->push_back(E->get()); + } + +} + +bool GDScript::has_method(const StringName& p_method) const { + + return member_functions.has(p_method); +} + +MethodInfo GDScript::get_method_info(const StringName& p_method) const { + + const Map<StringName,GDFunction*>::Element *E=member_functions.find(p_method); + if (!E) + return MethodInfo(); + + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get()->get_argument_count();i++) { + PropertyInfo arg; + arg.type=Variant::NIL; //variant + arg.name=E->get()->get_argument_name(i); + mi.arguments.push_back(arg); + } + + mi.return_val.name="Variant"; + return mi; + +} + + bool GDScript::get_property_default_value(const StringName& p_property, Variant &r_value) const { #ifdef TOOLS_ENABLED - //for (const Map<StringName,Variant>::Element *I=member_default_values.front();I;I=I->next()) { - // print_line("\t"+String(String(I->key())+":"+String(I->get()))); - //} + /* + for (const Map<StringName,Variant>::Element *I=member_default_values.front();I;I=I->next()) { + print_line("\t"+String(String(I->key())+":"+String(I->get()))); + } + */ const Map<StringName,Variant>::Element *E=member_default_values_cache.find(p_property); if (E) { r_value=E->get(); @@ -1720,12 +407,12 @@ ScriptInstance* GDScript::instance_create(Object *p_this) { top=top->_base; if (top->native.is_valid()) { - if (!ObjectTypeDB::is_type(p_this->get_type_name(),top->native->get_name())) { + if (!ClassDB::is_parent_class(p_this->get_class_name(),top->native->get_name())) { if (ScriptDebugger::get_singleton()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),0,"Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'"); + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),0,"Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_class()+"'"); } - ERR_EXPLAIN("Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'"); + ERR_EXPLAIN("Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_class()+"'"); ERR_FAIL_V(NULL); } @@ -1737,7 +424,16 @@ ScriptInstance* GDScript::instance_create(Object *p_this) { } bool GDScript::instance_has(const Object *p_this) const { - return instances.has((Object*)p_this); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->lock(); +#endif + bool hasit = instances.has((Object*)p_this); + +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->unlock(); +#endif + + return hasit; } bool GDScript::has_source_code() const { @@ -1926,10 +622,18 @@ void GDScript::_set_subclass_path(Ref<GDScript>& p_sc,const String& p_path) { } } -Error GDScript::reload() { +Error GDScript::reload(bool p_keep_state) { + +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->lock(); +#endif + bool has_instances = instances.size(); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->unlock(); +#endif - ERR_FAIL_COND_V(instances.size(),ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(!p_keep_state && has_instances,ERR_ALREADY_IN_USE); String basedir=path; @@ -1957,7 +661,7 @@ Error GDScript::reload() { bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script(); GDCompiler compiler; - err = compiler.compile(&parser,this); + err = compiler.compile(&parser,this,p_keep_state); if (err) { @@ -2083,9 +787,9 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { void GDScript::_bind_methods() { - ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo("new")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo(Variant::OBJECT,"new")); - ObjectTypeDB::bind_method(_MD("get_as_byte_code"),&GDScript::get_as_byte_code); + ClassDB::bind_method(_MD("get_as_byte_code"),&GDScript::get_as_byte_code); } @@ -2162,7 +866,7 @@ Error GDScript::load_byte_code(const String& p_path) { Error GDScript::load_source_code(const String& p_path) { - DVector<uint8_t> sourcef; + PoolVector<uint8_t> sourcef; Error err; FileAccess *f=FileAccess::open(p_path,FileAccess::READ,&err); if (err) { @@ -2172,7 +876,7 @@ Error GDScript::load_source_code(const String& p_path) { int len = f->get_len(); sourcef.resize(len+1); - DVector<uint8_t>::Write w = sourcef.write(); + PoolVector<uint8_t>::Write w = sourcef.write(); int r = f->get_buffer(w.ptr(),len); f->close(); memdelete(f); @@ -2263,7 +967,7 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { } -GDScript::GDScript() { +GDScript::GDScript() : script_list(this) { _static_ref=this; @@ -2277,12 +981,37 @@ GDScript::GDScript() { source_changed_cache=false; #endif +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->lock(); + } + GDScriptLanguage::get_singleton()->script_list.add(&script_list); + + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->unlock(); + } +#endif } GDScript::~GDScript() { for (Map<StringName,GDFunction*>::Element *E=member_functions.front();E;E=E->next()) { memdelete( E->get() ); } + + for (Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) { + E->get()->_owner=NULL; //bye, you are no longer owned cause I died + } + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->lock(); + } + GDScriptLanguage::get_singleton()->script_list.remove(&script_list); + + if (GDScriptLanguage::get_singleton()->lock) { + GDScriptLanguage::get_singleton()->lock->unlock(); + } +#endif } @@ -2359,11 +1088,16 @@ bool GDInstance::get(const StringName& p_name, Variant &r_ret) const { } { - const Map<StringName,Variant>::Element *E = script->constants.find(p_name); - if (E) { - r_ret=E->get(); - return true; //index found + const GDScript *sl = sptr; + while(sl) { + const Map<StringName,Variant>::Element *E = sl->constants.find(p_name); + if (E) { + r_ret=E->get(); + return true; //index found + + } + sl=sl->_base; } } @@ -2612,6 +1346,8 @@ void GDInstance::call_multilevel_reversed(const StringName& p_method,const Varia } } + + void GDInstance::notification(int p_notification) { //notification is not virutal, it gets called at ALL levels just like in C. @@ -2644,6 +1380,77 @@ ScriptLanguage *GDInstance::get_language() { return GDScriptLanguage::get_singleton(); } +GDInstance::RPCMode GDInstance::get_rpc_mode(const StringName& p_method) const { + + const GDScript *cscript = script.ptr(); + + while(cscript) { + const Map<StringName,GDFunction*>::Element *E=cscript->member_functions.find(p_method); + if (E) { + + if (E->get()->get_rpc_mode()!=RPC_MODE_DISABLED) { + return E->get()->get_rpc_mode(); + } + + } + cscript=cscript->_base; + } + + return RPC_MODE_DISABLED; +} + +GDInstance::RPCMode GDInstance::get_rset_mode(const StringName& p_variable) const { + + const GDScript *cscript = script.ptr(); + + while(cscript) { + const Map<StringName,GDScript::MemberInfo>::Element *E=cscript->member_indices.find(p_variable); + if (E) { + + if (E->get().rpc_mode) { + return E->get().rpc_mode; + } + + } + cscript=cscript->_base; + } + + return RPC_MODE_DISABLED; +} + + + +void GDInstance::reload_members() { + +#ifdef DEBUG_ENABLED + + members.resize(script->member_indices.size()); //resize + + Vector<Variant> new_members; + new_members.resize(script->member_indices.size()); + + //pass the values to the new indices + for(Map<StringName,GDScript::MemberInfo>::Element *E=script->member_indices.front();E;E=E->next()) { + + if (member_indices_cache.has(E->key())) { + Variant value = members[member_indices_cache[E->key()]]; + new_members[E->get().index]=value; + } + + } + + //apply + members=new_members; + + //pass the values to the new indices + member_indices_cache.clear(); + for(Map<StringName,GDScript::MemberInfo>::Element *E=script->member_indices.front();E;E=E->next()) { + + member_indices_cache[E->key()]=E->get().index; + } + +#endif +} GDInstance::GDInstance() { owner=NULL; @@ -2652,7 +1459,15 @@ GDInstance::GDInstance() { GDInstance::~GDInstance() { if (script.is_valid() && owner) { - script->instances.erase(owner); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->lock(); +#endif + + script->instances.erase(owner); +#ifndef NO_THREADS + GDScriptLanguage::singleton->lock->unlock(); +#endif + } } @@ -2706,7 +1521,7 @@ void GDScriptLanguage::init() { //populate native classes List<StringName> class_list; - ObjectTypeDB::get_type_list(&class_list); + ClassDB::get_class_list(&class_list); for(List<StringName>::Element *E=class_list.front();E;E=E->next()) { StringName n = E->get(); @@ -2722,9 +1537,9 @@ void GDScriptLanguage::init() { //populate singletons - List<Globals::Singleton> singletons; - Globals::get_singleton()->get_singletons(&singletons); - for(List<Globals::Singleton>::Element *E=singletons.front();E;E=E->next()) { + List<GlobalConfig::Singleton> singletons; + GlobalConfig::get_singleton()->get_singletons(&singletons); + for(List<GlobalConfig::Singleton>::Element *E=singletons.front();E;E=E->next()) { _add_global(E->get().name,E->get().ptr); } @@ -2867,9 +1682,189 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr,int p_i } +struct GDScriptDepSort { + + //must support sorting so inheritance works properly (parent must be reloaded first) + bool operator()(const Ref<GDScript> &A, const Ref<GDScript>& B) const { + + if (A==B) + return false; //shouldn't happen but.. + const GDScript *I=B->get_base().ptr(); + while(I) { + if (I==A.ptr()) { + // A is a base of B + return true; + } + + I=I->get_base().ptr(); + } + + return false; //not a base + } +}; + +void GDScriptLanguage::reload_all_scripts() { + + + +#ifdef DEBUG_ENABLED + print_line("RELOAD ALL SCRIPTS"); + if (lock) { + lock->lock(); + } + + List<Ref<GDScript> > scripts; + + SelfList<GDScript> *elem=script_list.first(); + while(elem) { + if (elem->self()->get_path().is_resource_file()) { + print_line("FOUND: "+elem->self()->get_path()); + scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident + } + elem=elem->next(); + } + + if (lock) { + lock->unlock(); + } + + //as scripts are going to be reloaded, must proceed without locking here + + scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order + + for(List<Ref<GDScript> >::Element *E=scripts.front();E;E=E->next()) { + + print_line("RELOADING: "+E->get()->get_path()); + E->get()->load_source_code(E->get()->get_path()); + E->get()->reload(true); + } +#endif +} + + +void GDScriptLanguage::reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload) { + + +#ifdef DEBUG_ENABLED + + if (lock) { + lock->lock(); + } + + List<Ref<GDScript> > scripts; + + SelfList<GDScript> *elem=script_list.first(); + while(elem) { + if (elem->self()->get_path().is_resource_file()) { + + scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident + } + elem=elem->next(); + } + + if (lock) { + lock->unlock(); + } + + //when someone asks you why dynamically typed languages are easier to write.... + + Map< Ref<GDScript>, Map<ObjectID,List<Pair<StringName,Variant> > > > to_reload; + + //as scripts are going to be reloaded, must proceed without locking here + + scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order + + for(List<Ref<GDScript> >::Element *E=scripts.front();E;E=E->next()) { + + bool reload = E->get()==p_script || to_reload.has(E->get()->get_base()); + + if (!reload) + continue; + + to_reload.insert(E->get(),Map<ObjectID,List<Pair<StringName,Variant> > >()); + + if (!p_soft_reload) { + + //save state and remove script from instances + Map<ObjectID,List<Pair<StringName,Variant> > >& map = to_reload[E->get()]; + + while(E->get()->instances.front()) { + Object *obj = E->get()->instances.front()->get(); + //save instance info + List<Pair<StringName,Variant> > state; + if (obj->get_script_instance()) { + + obj->get_script_instance()->get_property_state(state); + map[obj->get_instance_ID()]=state; + obj->set_script(RefPtr()); + } + } + + //same thing for placeholders +#ifdef TOOLS_ENABLED + + while(E->get()->placeholders.size()) { + + Object *obj = E->get()->placeholders.front()->get()->get_owner(); + //save instance info + List<Pair<StringName,Variant> > state; + if (obj->get_script_instance()) { + + obj->get_script_instance()->get_property_state(state); + map[obj->get_instance_ID()]=state; + obj->set_script(RefPtr()); + } + } +#endif + + for(Map<ObjectID,List<Pair<StringName,Variant> > >::Element *F=E->get()->pending_reload_state.front();F;F=F->next()) { + map[F->key()]=F->get(); //pending to reload, use this one instead + } + } + } + + for(Map< Ref<GDScript>, Map<ObjectID,List<Pair<StringName,Variant> > > >::Element *E=to_reload.front();E;E=E->next()) { + + Ref<GDScript> scr = E->key(); + scr->reload(p_soft_reload); + + //restore state if saved + for (Map<ObjectID,List<Pair<StringName,Variant> > >::Element *F=E->get().front();F;F=F->next()) { + + Object *obj = ObjectDB::get_instance(F->key()); + if (!obj) + continue; + + if (!p_soft_reload) { + //clear it just in case (may be a pending reload state) + obj->set_script(RefPtr()); + } + obj->set_script(scr.get_ref_ptr()); + if (!obj->get_script_instance()) { + //failed, save reload state for next time if not saved + if (!scr->pending_reload_state.has(obj->get_instance_ID())) { + scr->pending_reload_state[obj->get_instance_ID()]=F->get(); + } + continue; + } + + for (List<Pair<StringName,Variant> >::Element *G=F->get().front();G;G=G->next()) { + obj->get_script_instance()->set(G->get().first,G->get().second); + } + + scr->pending_reload_state.erase(obj->get_instance_ID()); //as it reloaded, remove pending state + } + + //if instance states were saved, set them! + } + + +#endif +} + void GDScriptLanguage::frame() { - // print_line("calls: "+itos(calls)); + //print_line("calls: "+itos(calls)); calls=0; #ifdef DEBUG_ENABLED @@ -2911,6 +1906,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "false", "float", "int", + "bool", "null", "PI", "self", @@ -2943,6 +1939,10 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "pass", "return", "while", + "remote", + "sync", + "master", + "slave", 0}; @@ -2984,7 +1984,7 @@ GDScriptLanguage::GDScriptLanguage() { script_frame_time=0; _debug_call_stack_pos=0; - int dmcs=GLOBAL_DEF("debug/script_max_call_stack",1024); + int dmcs=GLOBAL_DEF("debug/script/max_call_stack",1024); if (ScriptDebugger::get_singleton()) { //debugging enabled! @@ -3070,7 +2070,7 @@ bool ResourceFormatLoaderGDScript::handles_type(const String& p_type) const { String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { - String el = p_path.extension().to_lower(); + String el = p_path.get_extension().to_lower(); if (el=="gd" || el=="gdc" || el=="gde") return "GDScript"; return ""; @@ -3100,6 +2100,11 @@ Error ResourceFormatSaverGDScript::save(const String &p_path,const RES& p_resour } file->close(); memdelete(file); + + if (ScriptServer::is_reload_scripts_on_save_enabled()) { + GDScriptLanguage::get_singleton()->reload_tool_script(p_resource,false); + } + return OK; } diff --git a/modules/gdscript/gd_script.h b/modules/gdscript/gd_script.h index 5f6cd012d..4d3baa5bc 100644 --- a/modules/gdscript/gd_script.h +++ b/modules/gdscript/gd_script.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,210 +32,10 @@ #include "script_language.h" #include "io/resource_loader.h" #include "io/resource_saver.h" -#include "os/thread.h" -#include "pair.h" -#include "self_list.h" - -class GDInstance; -class GDScript; - - - -class GDFunction { -public: - - enum Opcode { - OPCODE_OPERATOR, - OPCODE_EXTENDS_TEST, - OPCODE_SET, - OPCODE_GET, - OPCODE_SET_NAMED, - OPCODE_GET_NAMED, - OPCODE_ASSIGN, - OPCODE_ASSIGN_TRUE, - OPCODE_ASSIGN_FALSE, - OPCODE_CONSTRUCT, //only for basic types!! - OPCODE_CONSTRUCT_ARRAY, - OPCODE_CONSTRUCT_DICTIONARY, - OPCODE_CALL, - OPCODE_CALL_RETURN, - OPCODE_CALL_BUILT_IN, - OPCODE_CALL_SELF, - OPCODE_CALL_SELF_BASE, - OPCODE_YIELD, - OPCODE_YIELD_SIGNAL, - OPCODE_YIELD_RESUME, - OPCODE_JUMP, - OPCODE_JUMP_IF, - OPCODE_JUMP_IF_NOT, - OPCODE_JUMP_TO_DEF_ARGUMENT, - OPCODE_RETURN, - OPCODE_ITERATE_BEGIN, - OPCODE_ITERATE, - OPCODE_ASSERT, - OPCODE_BREAKPOINT, - OPCODE_LINE, - OPCODE_END - }; - - enum Address { - ADDR_BITS=24, - ADDR_MASK=((1<<ADDR_BITS)-1), - ADDR_TYPE_MASK=~ADDR_MASK, - ADDR_TYPE_SELF=0, - ADDR_TYPE_CLASS=1, - ADDR_TYPE_MEMBER=2, - ADDR_TYPE_CLASS_CONSTANT=3, - ADDR_TYPE_LOCAL_CONSTANT=4, - ADDR_TYPE_STACK=5, - ADDR_TYPE_STACK_VARIABLE=6, - ADDR_TYPE_GLOBAL=7, - ADDR_TYPE_NIL=8 - }; - - struct StackDebug { - - int line; - int pos; - bool added; - StringName identifier; - }; - -private: -friend class GDCompiler; - - StringName source; - - mutable Variant nil; - mutable Variant *_constants_ptr; - int _constant_count; - const StringName *_global_names_ptr; - int _global_names_count; - const int *_default_arg_ptr; - int _default_arg_count; - const int *_code_ptr; - int _code_size; - int _argument_count; - int _stack_size; - int _call_size; - int _initial_line; - bool _static; - GDScript *_script; - - StringName name; - Vector<Variant> constants; - Vector<StringName> global_names; - Vector<int> default_arguments; - Vector<int> code; - -#ifdef TOOLS_ENABLED - Vector<StringName> arg_names; -#endif - - List<StackDebug> stack_debug; - - _FORCE_INLINE_ Variant *_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self,Variant *p_stack,String& r_error) const; - _FORCE_INLINE_ String _get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const; - -friend class GDScriptLanguage; - - SelfList<GDFunction> function_list; -#ifdef DEBUG_ENABLED - CharString func_cname; - const char*_func_cname; - - struct Profile { - StringName signature; - uint64_t call_count; - uint64_t self_time; - uint64_t total_time; - uint64_t frame_call_count; - uint64_t frame_self_time; - uint64_t frame_total_time; - uint64_t last_frame_call_count; - uint64_t last_frame_self_time; - uint64_t last_frame_total_time; - } profile; - -#endif - -public: - - - - struct CallState { - - GDInstance *instance; - Vector<uint8_t> stack; - int stack_size; - Variant self; - uint32_t alloca_size; - GDScript *_class; - int ip; - int line; - int defarg; - Variant result; - - }; - - _FORCE_INLINE_ bool is_static() const { return _static; } - - const int* get_code() const; //used for debug - int get_code_size() const; - Variant get_constant(int p_idx) const; - StringName get_global_name(int p_idx) const; - StringName get_name() const; - int get_max_stack_size() const; - int get_default_argument_count() const; - int get_default_argument_addr(int p_idx) const; - GDScript *get_script() const { return _script; } - - void debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const; - - _FORCE_INLINE_ bool is_empty() const { return _code_size==0; } - - int get_argument_count() const { return _argument_count; } - StringName get_argument_name(int p_idx) const { -#ifdef TOOLS_ENABLED - ERR_FAIL_INDEX_V(p_idx,arg_names.size(),StringName()); - return arg_names[p_idx]; -#endif - return StringName(); - - } - Variant get_default_argument(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx,default_arguments.size(),Variant()); - return default_arguments[p_idx]; - } - - Variant call(GDInstance *p_instance,const Variant **p_args, int p_argcount,Variant::CallError& r_err,CallState *p_state=NULL); - - GDFunction(); - ~GDFunction(); -}; - - -class GDFunctionState : public Reference { - - OBJ_TYPE(GDFunctionState,Reference); -friend class GDFunction; - GDFunction *function; - GDFunction::CallState state; - Variant _signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error); -protected: - static void _bind_methods(); -public: - - bool is_valid() const; - Variant resume(const Variant& p_arg=Variant()); - GDFunctionState(); - ~GDFunctionState(); -}; - - +#include "gd_function.h" class GDNativeClass : public Reference { - OBJ_TYPE(GDNativeClass,Reference); + GDCLASS(GDNativeClass,Reference); StringName name; protected: @@ -255,7 +55,7 @@ public: class GDScript : public Script { - OBJ_TYPE(GDScript,Script); + GDCLASS(GDScript,Script); bool tool; bool valid; @@ -264,6 +64,8 @@ class GDScript : public Script { int index; StringName setter; StringName getter; + ScriptInstance::RPCMode rpc_mode; + }; friend class GDInstance; @@ -285,8 +87,11 @@ friend class GDScriptLanguage; Map<StringName,Ref<GDScript> > subclasses; Map<StringName,Vector<StringName> > _signals; + #ifdef TOOLS_ENABLED + Map<StringName,int> member_lines; + Map<StringName,Variant> member_default_values; List<PropertyInfo> members_cache; @@ -307,6 +112,7 @@ friend class GDScriptLanguage; String source; String path; String name; + SelfList<GDScript> script_list; GDInstance* _create_instance(const Variant** p_args,int p_argcount,Object *p_owner,bool p_isref,Variant::CallError &r_error); @@ -319,7 +125,11 @@ friend class GDScriptLanguage; virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); #endif +#ifdef DEBUG_ENABLED + + Map<ObjectID,List<Pair<StringName,Variant> > > pending_reload_state; +#endif bool _update_exports(); @@ -329,7 +139,7 @@ protected: void _get_property_list(List<PropertyInfo> *p_properties) const; Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error); -// void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); + //void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); static void _bind_methods(); public: @@ -356,6 +166,8 @@ public: Variant _new(const Variant** p_args,int p_argcount,Variant::CallError& r_error); virtual bool can_instance() const; + virtual Ref<Script> get_base_script() const; + virtual StringName get_instance_base_type() const; // this may not work in all scripts, will return empty if so virtual ScriptInstance* instance_create(Object *p_this); virtual bool instance_has(const Object *p_this) const; @@ -365,7 +177,7 @@ public: virtual void set_source_code(const String& p_code); virtual void update_exports(); - virtual Error reload(); + virtual Error reload(bool p_keep_state=false); virtual String get_node_type() const; void set_script_path(const String& p_path) { path=p_path; } //because subclasses need a path too... @@ -376,8 +188,25 @@ public: bool get_property_default_value(const StringName& p_property,Variant& r_value) const; + virtual void get_script_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName& p_method) const; + virtual MethodInfo get_method_info(const StringName& p_method) const; + + virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + + virtual ScriptLanguage *get_language() const; + virtual int get_member_line(const StringName& p_member) const { +#ifdef TOOLS_ENABLED + if (member_lines.has(p_member)) + return member_lines[p_member]; + else +#endif + return -1; + + } + GDScript(); ~GDScript(); }; @@ -386,9 +215,13 @@ class GDInstance : public ScriptInstance { friend class GDScript; friend class GDFunction; friend class GDFunctions; +friend class GDCompiler; Object *owner; Ref<GDScript> script; +#ifdef DEBUG_ENABLED + Map<StringName,int> member_indices_cache; //used only for hot script reloading +#endif Vector<Variant> members; bool base_ref; @@ -397,6 +230,8 @@ friend class GDFunctions; public: + _FORCE_INLINE_ Object* get_owner() { return owner; } + virtual bool set(const StringName& p_name, const Variant& p_value); virtual bool get(const StringName& p_name, Variant &r_ret) const; virtual void get_property_list(List<PropertyInfo> *p_properties) const; @@ -409,7 +244,7 @@ public: virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount); virtual void call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount); - Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; } + Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; } virtual void notification(int p_notification); @@ -419,6 +254,11 @@ public: void set_path(const String& p_path); + void reload_members(); + + virtual RPCMode get_rpc_mode(const StringName& p_method) const; + virtual RPCMode get_rset_mode(const StringName& p_variable) const; + GDInstance(); ~GDInstance(); @@ -434,28 +274,36 @@ class GDScriptLanguage : public ScriptLanguage { Map<StringName,int> globals; - struct CallLevel { + struct CallLevel { - Variant *stack; - GDFunction *function; - GDInstance *instance; - int *ip; - int *line; + Variant *stack; + GDFunction *function; + GDInstance *instance; + int *ip; + int *line; - }; + }; - int _debug_parse_err_line; - String _debug_parse_err_file; - String _debug_error; - int _debug_call_stack_pos; - int _debug_max_call_stack; - CallLevel *_call_stack; + int _debug_parse_err_line; + String _debug_parse_err_file; + String _debug_error; + int _debug_call_stack_pos; + int _debug_max_call_stack; + CallLevel *_call_stack; void _add_global(const StringName& p_name,const Variant& p_value); +friend class GDInstance; Mutex *lock; + + + + +friend class GDScript; + + SelfList<GDScript>::List script_list; friend class GDFunction; SelfList<GDFunction>::List function_list; @@ -466,54 +314,54 @@ public: int calls; - bool debug_break(const String& p_error,bool p_allow_continue=true); - bool debug_break_parse(const String& p_file, int p_line,const String& p_error); + bool debug_break(const String& p_error,bool p_allow_continue=true); + bool debug_break_parse(const String& p_file, int p_line,const String& p_error); - _FORCE_INLINE_ void enter_function(GDInstance *p_instance,GDFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { + _FORCE_INLINE_ void enter_function(GDInstance *p_instance,GDFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) { - if (Thread::get_main_ID()!=Thread::get_caller_ID()) - return; //no support for other threads than main for now + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now - if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) - ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); - if (_debug_call_stack_pos >= _debug_max_call_stack) { - //stack overflow - _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; - ScriptDebugger::get_singleton()->debug(this); - return; - } + if (_debug_call_stack_pos >= _debug_max_call_stack) { + //stack overflow + _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; + ScriptDebugger::get_singleton()->debug(this); + return; + } - _call_stack[_debug_call_stack_pos].stack=p_stack; - _call_stack[_debug_call_stack_pos].instance=p_instance; - _call_stack[_debug_call_stack_pos].function=p_function; - _call_stack[_debug_call_stack_pos].ip=p_ip; - _call_stack[_debug_call_stack_pos].line=p_line; - _debug_call_stack_pos++; - } + _call_stack[_debug_call_stack_pos].stack=p_stack; + _call_stack[_debug_call_stack_pos].instance=p_instance; + _call_stack[_debug_call_stack_pos].function=p_function; + _call_stack[_debug_call_stack_pos].ip=p_ip; + _call_stack[_debug_call_stack_pos].line=p_line; + _debug_call_stack_pos++; + } - _FORCE_INLINE_ void exit_function() { + _FORCE_INLINE_ void exit_function() { - if (Thread::get_main_ID()!=Thread::get_caller_ID()) - return; //no support for other threads than main for now + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now - if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) - ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); - if (_debug_call_stack_pos==0) { + if (_debug_call_stack_pos==0) { - _debug_error="Stack Underflow (Engine Bug)"; - ScriptDebugger::get_singleton()->debug(this); - return; - } + _debug_error="Stack Underflow (Engine Bug)"; + ScriptDebugger::get_singleton()->debug(this); + return; + } - _debug_call_stack_pos--; - } + _debug_call_stack_pos--; + } virtual Vector<StackInfo> debug_get_current_stack_info() { - if (Thread::get_main_ID()!=Thread::get_caller_ID()) - return Vector<StackInfo>(); + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return Vector<StackInfo>(); Vector<StackInfo> csi; csi.resize(_debug_call_stack_pos); @@ -555,13 +403,16 @@ public: virtual void get_reserved_words(List<String> *p_words) const; virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; - virtual String get_template(const String& p_class_name, const String& p_base_class_name) const; + virtual Ref<Script> get_template(const String& p_class_name, const String& p_base_class_name) const; virtual bool validate(const String& p_script,int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path="",List<String> *r_functions=NULL) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual int find_function(const String& p_function,const String& p_code) const; - virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const; + virtual String make_function(const String& p_class,const String& p_name,const PoolStringArray& p_args) const; virtual Error complete_code(const String& p_code, const String& p_base_path, Object*p_owner,List<String>* r_options,String& r_call_hint); +#ifdef TOOLS_ENABLED + virtual Error lookup_code(const String& p_code, const String& p_symbol, const String& p_base_path, Object*p_owner, LookupResult& r_result); +#endif virtual void auto_indent_code(String& p_code,int p_from_line,int p_to_line) const; virtual void add_global_constant(const StringName& p_variable,const Variant& p_value); @@ -578,6 +429,9 @@ public: virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1); + virtual void reload_all_scripts(); + virtual void reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload); + virtual void frame(); virtual void get_public_functions(List<MethodInfo> *p_functions) const; diff --git a/modules/gdscript/gd_tokenizer.cpp b/modules/gdscript/gd_tokenizer.cpp index 56eacfd20..5dabf50ba 100644 --- a/modules/gdscript/gd_tokenizer.cpp +++ b/modules/gdscript/gd_tokenizer.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -79,8 +79,8 @@ const char* GDTokenizer::token_names[TK_MAX]={ "for", "do", "while", -"switch", -"case", +"switch (reserved)", +"case (reserved)", "break", "continue", "pass", @@ -95,11 +95,16 @@ const char* GDTokenizer::token_names[TK_MAX]={ "setget", "const", "var", +"enum", "preload", "assert", "yield", "signal", "breakpoint", +"rpc", +"sync", +"master", +"slave", "'['", "']'", "'{'", @@ -277,7 +282,7 @@ void GDTokenizerText::_advance() { case '\n': { line++; INCPOS(1); - column=0; + column=1; int i=0; while(GETCHAR(i)==' ' || GETCHAR(i)=='\t') { i++; @@ -298,7 +303,7 @@ void GDTokenizerText::_advance() { } } INCPOS(1); - column=0; + column=1; line++; int i=0; while(GETCHAR(i)==' ' || GETCHAR(i)=='\t') { @@ -330,7 +335,7 @@ void GDTokenizerText::_advance() { break; } else if (_code[pos]=='\n') { new_line++; - new_col=0; + new_col=1; } else { new_col++; } @@ -353,7 +358,7 @@ void GDTokenizerText::_advance() { } } INCPOS(1); - column=0; + column=1; line++; continue; @@ -455,6 +460,9 @@ void GDTokenizerText::_advance() { case ':': _make_token(TK_COLON); //for methods maybe but now useless. break; + case '$': + _make_token(TK_DOLLAR); //for the get_node() shortener + break; case '^': { if (GETCHAR(1)=='=') { _make_token(TK_OP_ASSIGN_BIT_XOR); @@ -505,9 +513,11 @@ void GDTokenizerText::_advance() { if (GETCHAR(1)=='=') { _make_token(TK_OP_ASSIGN_ADD); INCPOS(1); - //} else if (GETCHAR(1)=='+') { - // _make_token(TK_OP_PLUS_PLUS); - // INCPOS(1); + /* + } else if (GETCHAR(1)=='+') { + _make_token(TK_OP_PLUS_PLUS); + INCPOS(1); + */ } else { _make_token(TK_OP_ADD); } @@ -518,9 +528,11 @@ void GDTokenizerText::_advance() { if (GETCHAR(1)=='=') { _make_token(TK_OP_ASSIGN_SUB); INCPOS(1); - //} else if (GETCHAR(1)=='-') { - // _make_token(TK_OP_MINUS_MINUS); - // INCPOS(1); + /* + } else if (GETCHAR(1)=='-') { + _make_token(TK_OP_MINUS_MINUS); + INCPOS(1); + */ } else { _make_token(TK_OP_SUB); } @@ -649,7 +661,7 @@ void GDTokenizerText::_advance() { } else { if (CharType(GETCHAR(i))=='\n') { line++; - column=0; + column=1; } str+=CharType(GETCHAR(i)); @@ -723,14 +735,14 @@ void GDTokenizerText::_advance() { INCPOS(str.length()); if (hexa_found) { - int val = str.hex_to_int(); + int64_t val = str.hex_to_int64(); _make_constant(val); - } else if (period_found) { - real_t val = str.to_double(); + } else if (period_found || exponent_found) { + double val = str.to_double(); //print_line("*%*%*%*% to convert: "+str+" result: "+rtos(val)); _make_constant(val); } else { - int val = str.to_int(); + int64_t val = str.to_int64(); _make_constant(val); } @@ -780,13 +792,12 @@ void GDTokenizerText::_advance() { {Variant::STRING,"String"}, {Variant::VECTOR2,"Vector2"}, {Variant::RECT2,"Rect2"}, - {Variant::MATRIX32,"Matrix32"}, + {Variant::TRANSFORM2D,"Transform2D"}, {Variant::VECTOR3,"Vector3"}, - {Variant::_AABB,"AABB"}, - {Variant::_AABB,"Rect3"}, + {Variant::RECT3,"Rect3"}, {Variant::PLANE,"Plane"}, {Variant::QUAT,"Quat"}, - {Variant::MATRIX3,"Matrix3"}, + {Variant::BASIS,"Basis"}, {Variant::TRANSFORM,"Transform"}, {Variant::COLOR,"Color"}, {Variant::IMAGE,"Image"}, @@ -796,13 +807,13 @@ void GDTokenizerText::_advance() { {Variant::NODE_PATH,"NodePath"}, {Variant::DICTIONARY,"Dictionary"}, {Variant::ARRAY,"Array"}, - {Variant::RAW_ARRAY,"RawArray"}, - {Variant::INT_ARRAY,"IntArray"}, - {Variant::REAL_ARRAY,"FloatArray"}, - {Variant::STRING_ARRAY,"StringArray"}, - {Variant::VECTOR2_ARRAY,"Vector2Array"}, - {Variant::VECTOR3_ARRAY,"Vector3Array"}, - {Variant::COLOR_ARRAY,"ColorArray"}, + {Variant::POOL_BYTE_ARRAY,"PoolByteArray"}, + {Variant::POOL_INT_ARRAY,"PoolIntArray"}, + {Variant::POOL_REAL_ARRAY,"PoolFloatArray"}, + {Variant::POOL_STRING_ARRAY,"PoolStringArray"}, + {Variant::POOL_VECTOR2_ARRAY,"PoolVector2Array"}, + {Variant::POOL_VECTOR3_ARRAY,"PoolVector3Array"}, + {Variant::POOL_COLOR_ARRAY,"PoolColorArray"}, {Variant::VARIANT_MAX,NULL}, }; @@ -865,7 +876,12 @@ void GDTokenizerText::_advance() { {TK_PR_YIELD,"yield"}, {TK_PR_SIGNAL,"signal"}, {TK_PR_BREAKPOINT,"breakpoint"}, + {TK_PR_REMOTE,"remote"}, + {TK_PR_MASTER,"master"}, + {TK_PR_SLAVE,"slave"}, + {TK_PR_SYNC,"sync"}, {TK_PR_CONST,"const"}, + {TK_PR_ENUM,"enum"}, //controlflow {TK_CF_IF,"if"}, {TK_CF_ELIF,"elif"}, @@ -874,6 +890,7 @@ void GDTokenizerText::_advance() { {TK_CF_WHILE,"while"}, {TK_CF_DO,"do"}, {TK_CF_SWITCH,"switch"}, + {TK_CF_CASE,"case"}, {TK_CF_BREAK,"break"}, {TK_CF_CONTINUE,"continue"}, {TK_CF_RETURN,"return"}, @@ -932,7 +949,7 @@ void GDTokenizerText::set_code(const String& p_code) { } code_pos=0; line=1; //it is stand-ar-ized that lines begin in 1 in code.. - column=0; + column=1; //the same holds for columns tk_rb_pos=0; error_flag=false; last_error=""; @@ -1046,7 +1063,7 @@ void GDTokenizerText::advance(int p_amount) { ////////////////////////////////////////////////////////////////////////////////////////////////////// -#define BYTECODE_VERSION 10 +#define BYTECODE_VERSION 12 Error GDTokenizerBuffer::set_code_buffer(const Vector<uint8_t> & p_buffer) { @@ -1155,7 +1172,6 @@ Vector<uint8_t> GDTokenizerBuffer::parse_code_string(const String& p_code) { GDTokenizerText tt; tt.set_code(p_code); int line=-1; - int col=0; while(true) { @@ -1322,7 +1338,7 @@ StringName GDTokenizerBuffer::get_token_identifier(int p_offset) const{ ERR_FAIL_INDEX_V(offset,tokens.size(),StringName()); uint32_t identifier = tokens[offset]>>TOKEN_BITS; - ERR_FAIL_INDEX_V(identifier,identifiers.size(),StringName()); + ERR_FAIL_INDEX_V(identifier,(uint32_t)identifiers.size(),StringName()); return identifiers[identifier]; } @@ -1381,7 +1397,7 @@ const Variant& GDTokenizerBuffer::get_token_constant(int p_offset) const{ int offset = token+p_offset; ERR_FAIL_INDEX_V(offset,tokens.size(),nil); uint32_t constant = tokens[offset]>>TOKEN_BITS; - ERR_FAIL_INDEX_V(constant,constants.size(),nil); + ERR_FAIL_INDEX_V(constant,(uint32_t)constants.size(),nil); return constants[constant]; } diff --git a/modules/gdscript/gd_tokenizer.h b/modules/gdscript/gd_tokenizer.h index aaff57309..18e5547d3 100644 --- a/modules/gdscript/gd_tokenizer.h +++ b/modules/gdscript/gd_tokenizer.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -102,11 +102,16 @@ public: TK_PR_SETGET, TK_PR_CONST, TK_PR_VAR, + TK_PR_ENUM, TK_PR_PRELOAD, TK_PR_ASSERT, TK_PR_YIELD, TK_PR_SIGNAL, TK_PR_BREAKPOINT, + TK_PR_REMOTE, + TK_PR_SYNC, + TK_PR_MASTER, + TK_PR_SLAVE, TK_BRACKET_OPEN, TK_BRACKET_CLOSE, TK_CURLY_BRACKET_OPEN, @@ -118,6 +123,7 @@ public: TK_PERIOD, TK_QUESTION_MARK, TK_COLON, + TK_DOLLAR, TK_NEWLINE, TK_CONST_PI, TK_ERROR, diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 2aea494f3..051d1d85c 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -1,14 +1,31 @@ -/*************************************************/ -/* register_script_types.cpp */ -/*************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/*************************************************/ -/* Source code within this file is: */ -/* (c) 2007-2016 Juan Linietsky, Ariel Manzur */ -/* All Rights Reserved. */ -/*************************************************/ - +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ #include "register_types.h" #include "gd_script.h" @@ -31,7 +48,7 @@ ResourceFormatSaverGDScript *resource_saver_gd=NULL; class EditorExportGDScript : public EditorExportPlugin { - OBJ_TYPE(EditorExportGDScript,EditorExportPlugin); + GDCLASS(EditorExportGDScript,EditorExportPlugin); public: @@ -83,7 +100,7 @@ public: if (err==OK) { fae->store_buffer(file.ptr(),file.size()); - p_path=p_path.basename()+".gde"; + p_path=p_path.get_basename()+".gde"; } memdelete(fae); @@ -94,7 +111,7 @@ public: } else { - p_path=p_path.basename()+".gdc"; + p_path=p_path.get_basename()+".gdc"; return file; } } @@ -121,8 +138,8 @@ static void register_editor_plugin() { void register_gdscript_types() { - ObjectTypeDB::register_type<GDScript>(); - ObjectTypeDB::register_virtual_type<GDFunctionState>(); + ClassDB::register_class<GDScript>(); + ClassDB::register_virtual_class<GDFunctionState>(); script_language_gd=memnew( GDScriptLanguage ); //script_language_gd->init(); @@ -141,7 +158,7 @@ void register_gdscript_types() { void unregister_gdscript_types() { - + ScriptServer::unregister_language(script_language_gd); if (script_language_gd) memdelete( script_language_gd ); diff --git a/modules/gdscript/register_types.h b/modules/gdscript/register_types.h index aed11cd1d..5778dfcad 100644 --- a/modules/gdscript/register_types.h +++ b/modules/gdscript/register_types.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/gridmap/SCsub b/modules/gridmap/SCsub index 211a04346..088240676 100644 --- a/modules/gridmap/SCsub +++ b/modules/gridmap/SCsub @@ -1,3 +1,7 @@ +#!/usr/bin/env python + Import('env') -env.add_source_files(env.modules_sources,"*.cpp") +env.add_source_files(env.modules_sources, "*.cpp") + +Export('env') diff --git a/modules/gridmap/config.py b/modules/gridmap/config.py index ea7e83378..1ab13c4ae 100644 --- a/modules/gridmap/config.py +++ b/modules/gridmap/config.py @@ -1,11 +1,9 @@ def can_build(platform): - return True + # FIXME: Disabled temporary for gles3 implementation + return False def configure(env): - pass - - - + pass diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 503e723de..271db4bab 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -61,9 +61,9 @@ bool GridMap::_set(const StringName& p_name, const Variant& p_value) { } else if (name=="theme/bake") { set_bake(p_value); /* } else if (name=="cells") { - DVector<int> cells = p_value; + PoolVector<int> cells = p_value; int amount=cells.size(); - DVector<int>::Read r = cells.read(); + PoolVector<int>::Read r = cells.read(); ERR_FAIL_COND_V(amount&1,false); // not even cell_map.clear();; for(int i=0;i<amount/3;i++) { @@ -86,9 +86,9 @@ bool GridMap::_set(const StringName& p_name, const Variant& p_value) { baked=d["baked"]; if (d.has("cells")) { - DVector<int> cells = d["cells"]; + PoolVector<int> cells = d["cells"]; int amount=cells.size(); - DVector<int>::Read r = cells.read(); + PoolVector<int>::Read r = cells.read(); ERR_FAIL_COND_V(amount%3,false); // not even cell_map.clear();; for(int i=0;i<amount/3;i++) { @@ -183,10 +183,10 @@ bool GridMap::_get(const StringName& p_name,Variant &r_ret) const { Dictionary d; - DVector<int> cells; + PoolVector<int> cells; cells.resize(cell_map.size()*3); { - DVector<int>::Write w = cells.write(); + PoolVector<int>::Write w = cells.write(); int i=0; for (Map<IndexKey,Cell>::Element *E=cell_map.front();E;E=E->next(),i++) { @@ -252,7 +252,7 @@ void GridMap::_get_property_list( List<PropertyInfo> *p_list) const { for(const Map<int,Area*>::Element *E=area_map.front();E;E=E->next()) { String base="areas/"+itos(E->key())+"/"; - p_list->push_back( PropertyInfo( Variant::_AABB, base+"bounds", PROPERTY_HINT_NONE,"",PROPERTY_USAGE_STORAGE) ); + p_list->push_back( PropertyInfo( Variant::RECT3, base+"bounds", PROPERTY_HINT_NONE,"",PROPERTY_USAGE_STORAGE) ); p_list->push_back( PropertyInfo( Variant::STRING, base+"name", PROPERTY_HINT_NONE,"",PROPERTY_USAGE_STORAGE) ); p_list->push_back( PropertyInfo( Variant::REAL, base+"disable_distance", PROPERTY_HINT_NONE,"",PROPERTY_USAGE_STORAGE) ); p_list->push_back( PropertyInfo( Variant::COLOR, base+"disable_color", PROPERTY_HINT_NONE,"",PROPERTY_USAGE_STORAGE) ); @@ -665,7 +665,7 @@ void GridMap::_octant_update(const OctantKey &p_key) { VS::get_singleton()->mesh_clear(g.collision_debug); } - DVector<Vector3> col_debug; + PoolVector<Vector3> col_debug; /* * foreach item in this octant, @@ -678,8 +678,8 @@ void GridMap::_octant_update(const OctantKey &p_key) { ii.multimesh->set_instance_count(ii.cells.size()); - AABB aabb; - AABB mesh_aabb = ii.mesh.is_null()?AABB():ii.mesh->get_aabb(); + Rect3 aabb; + Rect3 mesh_aabb = ii.mesh.is_null()?Rect3():ii.mesh->get_aabb(); Vector3 ofs(cell_size*0.5*int(center_x),cell_size*0.5*int(center_y),cell_size*0.5*int(center_z)); @@ -731,7 +731,7 @@ void GridMap::_octant_update(const OctantKey &p_key) { ii.shape->add_vertices_to_array(col_debug,xform); } - // print_line("PHIS x: "+xform); + //print_line("PHIS x: "+xform); } // add the item's navmesh at given xform to GridMap's Navigation ancestor @@ -796,7 +796,7 @@ void GridMap::_octant_exit_world(const OctantKey &p_key) { for(Map<int,Octant::ItemInstances>::Element *E=g.items.front();E;E=E->next()) { VS::get_singleton()->instance_set_scenario(E->get().multimesh_instance,RID()); - // VS::get_singleton()->instance_set_transform(E->get().multimesh_instance,get_global_transform()); + //VS::get_singleton()->instance_set_transform(E->get().multimesh_instance,get_global_transform()); VS::get_singleton()->instance_set_room(E->get().multimesh_instance,RID()); } @@ -863,15 +863,15 @@ void GridMap::_octant_bake(const OctantKey &p_key, const Ref<TriangleMesh>& p_tm if (ii.mesh->surface_get_primitive_type(i)!=Mesh::PRIMITIVE_TRIANGLES) continue; Array a = ii.mesh->surface_get_arrays(i); - DVector<Vector3> av=a[VS::ARRAY_VERTEX]; + PoolVector<Vector3> av=a[VS::ARRAY_VERTEX]; int avs = av.size(); - DVector<Vector3>::Read vr = av.read(); + PoolVector<Vector3>::Read vr = av.read(); - DVector<int> ai=a[VS::ARRAY_INDEX]; + PoolVector<int> ai=a[VS::ARRAY_INDEX]; int ais=ai.size(); if (ais) { - DVector<int>::Read ir=ai.read(); + PoolVector<int>::Read ir=ai.read(); for(int j=0;j<ais;j++) { p_prebake->push_back(xform.xform(vr[ir[j]])); @@ -912,7 +912,6 @@ void GridMap::_octant_bake(const OctantKey &p_key, const Ref<TriangleMesh>& p_tm int lc = p_lights.size(); const BakeLight* bl = p_lights.ptr(); float ofs = cell_size*0.02; - float att = 0.2; for(;V;V=V->next()) { @@ -921,7 +920,7 @@ void GridMap::_octant_bake(const OctantKey &p_key, const Ref<TriangleMesh>& p_tm Vector3 vertex = v.vertex + octant_ofs; //print_line("V GET: "+vertex); - Vector3 normal = tm->get_area_normal( AABB( Vector3(-ofs,-ofs,-ofs)+vertex,Vector3(ofs,ofs,ofs)*2.0)); + Vector3 normal = tm->get_area_normal( Rect3( Vector3(-ofs,-ofs,-ofs)+vertex,Vector3(ofs,ofs,ofs)*2.0)); if (normal==Vector3()) { print_line("couldn't find for vertex: "+vertex); } @@ -961,9 +960,9 @@ void GridMap::_octant_bake(const OctantKey &p_key, const Ref<TriangleMesh>& p_tm st->add_to_format(VS::ARRAY_FORMAT_COLOR); if (m.is_valid()) { - Ref<FixedMaterial> fm = m; + Ref<FixedSpatialMaterial> fm = m; if (fm.is_valid()) - fm->set_fixed_flag(FixedMaterial::FLAG_USE_COLOR_ARRAY,true); + fm->set_fixed_flag(FixedSpatialMaterial::FLAG_USE_COLOR_ARRAY,true); } } } @@ -1002,8 +1001,8 @@ void GridMap::_notification(int p_what) { _update_area_instances(); for(Map<OctantKey,Octant*>::Element *E=octant_map.front();E;E=E->next()) { -// IndexKey ik; -// ik.key = E->key().indexkey; + //IndexKey ik; + //ik.key = E->key().indexkey; _octant_enter_world(E->key()); _octant_update(E->key()); } @@ -1186,60 +1185,60 @@ void GridMap::_update_dirty_map_callback() { void GridMap::_bind_methods() { - ObjectTypeDB::bind_method(_MD("set_theme","theme:MeshLibrary"),&GridMap::set_theme); - ObjectTypeDB::bind_method(_MD("get_theme:MeshLibrary"),&GridMap::get_theme); + ClassDB::bind_method(_MD("set_theme","theme:MeshLibrary"),&GridMap::set_theme); + ClassDB::bind_method(_MD("get_theme:MeshLibrary"),&GridMap::get_theme); - ObjectTypeDB::bind_method(_MD("set_bake","enable"),&GridMap::set_bake); - ObjectTypeDB::bind_method(_MD("is_baking_enabled"),&GridMap::is_baking_enabled); + ClassDB::bind_method(_MD("set_bake","enable"),&GridMap::set_bake); + ClassDB::bind_method(_MD("is_baking_enabled"),&GridMap::is_baking_enabled); - ObjectTypeDB::bind_method(_MD("set_cell_size","size"),&GridMap::set_cell_size); - ObjectTypeDB::bind_method(_MD("get_cell_size"),&GridMap::get_cell_size); + ClassDB::bind_method(_MD("set_cell_size","size"),&GridMap::set_cell_size); + ClassDB::bind_method(_MD("get_cell_size"),&GridMap::get_cell_size); - ObjectTypeDB::bind_method(_MD("set_octant_size","size"),&GridMap::set_octant_size); - ObjectTypeDB::bind_method(_MD("get_octant_size"),&GridMap::get_octant_size); + ClassDB::bind_method(_MD("set_octant_size","size"),&GridMap::set_octant_size); + ClassDB::bind_method(_MD("get_octant_size"),&GridMap::get_octant_size); - ObjectTypeDB::bind_method(_MD("set_cell_item","x","y","z","item","orientation"),&GridMap::set_cell_item,DEFVAL(0)); - ObjectTypeDB::bind_method(_MD("get_cell_item","x","y","z"),&GridMap::get_cell_item); - ObjectTypeDB::bind_method(_MD("get_cell_item_orientation","x","y","z"),&GridMap::get_cell_item_orientation); + ClassDB::bind_method(_MD("set_cell_item","x","y","z","item","orientation"),&GridMap::set_cell_item,DEFVAL(0)); + ClassDB::bind_method(_MD("get_cell_item","x","y","z"),&GridMap::get_cell_item); + ClassDB::bind_method(_MD("get_cell_item_orientation","x","y","z"),&GridMap::get_cell_item_orientation); -// ObjectTypeDB::bind_method(_MD("_recreate_octants"),&GridMap::_recreate_octants); - ObjectTypeDB::bind_method(_MD("_update_dirty_map_callback"),&GridMap::_update_dirty_map_callback); - ObjectTypeDB::bind_method(_MD("resource_changed","resource"),&GridMap::resource_changed); + //ClassDB::bind_method(_MD("_recreate_octants"),&GridMap::_recreate_octants); + ClassDB::bind_method(_MD("_update_dirty_map_callback"),&GridMap::_update_dirty_map_callback); + ClassDB::bind_method(_MD("resource_changed","resource"),&GridMap::resource_changed); - ObjectTypeDB::bind_method(_MD("set_center_x","enable"),&GridMap::set_center_x); - ObjectTypeDB::bind_method(_MD("get_center_x"),&GridMap::get_center_x); - ObjectTypeDB::bind_method(_MD("set_center_y","enable"),&GridMap::set_center_y); - ObjectTypeDB::bind_method(_MD("get_center_y"),&GridMap::get_center_y); - ObjectTypeDB::bind_method(_MD("set_center_z","enable"),&GridMap::set_center_z); - ObjectTypeDB::bind_method(_MD("get_center_z"),&GridMap::get_center_z); + ClassDB::bind_method(_MD("set_center_x","enable"),&GridMap::set_center_x); + ClassDB::bind_method(_MD("get_center_x"),&GridMap::get_center_x); + ClassDB::bind_method(_MD("set_center_y","enable"),&GridMap::set_center_y); + ClassDB::bind_method(_MD("get_center_y"),&GridMap::get_center_y); + ClassDB::bind_method(_MD("set_center_z","enable"),&GridMap::set_center_z); + ClassDB::bind_method(_MD("get_center_z"),&GridMap::get_center_z); - ObjectTypeDB::bind_method(_MD("set_clip","enabled","clipabove","floor","axis"),&GridMap::set_clip,DEFVAL(true),DEFVAL(0),DEFVAL(Vector3::AXIS_X)); + ClassDB::bind_method(_MD("set_clip","enabled","clipabove","floor","axis"),&GridMap::set_clip,DEFVAL(true),DEFVAL(0),DEFVAL(Vector3::AXIS_X)); - ObjectTypeDB::bind_method(_MD("create_area","id","area"),&GridMap::create_area); - ObjectTypeDB::bind_method(_MD("area_get_bounds","area","bounds"),&GridMap::area_get_bounds); - ObjectTypeDB::bind_method(_MD("area_set_exterior_portal","area","enable"),&GridMap::area_set_exterior_portal); - ObjectTypeDB::bind_method(_MD("area_set_name","area","name"),&GridMap::area_set_name); - ObjectTypeDB::bind_method(_MD("area_get_name","area"),&GridMap::area_get_name); - ObjectTypeDB::bind_method(_MD("area_is_exterior_portal","area"),&GridMap::area_is_exterior_portal); - ObjectTypeDB::bind_method(_MD("area_set_portal_disable_distance","area","distance"),&GridMap::area_set_portal_disable_distance); - ObjectTypeDB::bind_method(_MD("area_get_portal_disable_distance","area"),&GridMap::area_get_portal_disable_distance); - ObjectTypeDB::bind_method(_MD("area_set_portal_disable_color","area","color"),&GridMap::area_set_portal_disable_color); - ObjectTypeDB::bind_method(_MD("area_get_portal_disable_color","area"),&GridMap::area_get_portal_disable_color); - ObjectTypeDB::bind_method(_MD("erase_area","area"),&GridMap::erase_area); - ObjectTypeDB::bind_method(_MD("get_unused_area_id","area"),&GridMap::get_unused_area_id); - ObjectTypeDB::bind_method(_MD("bake_geometry"),&GridMap::bake_geometry); + ClassDB::bind_method(_MD("create_area","id","area"),&GridMap::create_area); + ClassDB::bind_method(_MD("area_get_bounds","area","bounds"),&GridMap::area_get_bounds); + ClassDB::bind_method(_MD("area_set_exterior_portal","area","enable"),&GridMap::area_set_exterior_portal); + ClassDB::bind_method(_MD("area_set_name","area","name"),&GridMap::area_set_name); + ClassDB::bind_method(_MD("area_get_name","area"),&GridMap::area_get_name); + ClassDB::bind_method(_MD("area_is_exterior_portal","area"),&GridMap::area_is_exterior_portal); + ClassDB::bind_method(_MD("area_set_portal_disable_distance","area","distance"),&GridMap::area_set_portal_disable_distance); + ClassDB::bind_method(_MD("area_get_portal_disable_distance","area"),&GridMap::area_get_portal_disable_distance); + ClassDB::bind_method(_MD("area_set_portal_disable_color","area","color"),&GridMap::area_set_portal_disable_color); + ClassDB::bind_method(_MD("area_get_portal_disable_color","area"),&GridMap::area_get_portal_disable_color); + ClassDB::bind_method(_MD("erase_area","area"),&GridMap::erase_area); + ClassDB::bind_method(_MD("get_unused_area_id","area"),&GridMap::get_unused_area_id); + ClassDB::bind_method(_MD("bake_geometry"),&GridMap::bake_geometry); - ObjectTypeDB::bind_method(_MD("_baked_light_changed"),&GridMap::_baked_light_changed); - ObjectTypeDB::bind_method(_MD("set_use_baked_light","use"),&GridMap::set_use_baked_light); - ObjectTypeDB::bind_method(_MD("is_using_baked_light","use"),&GridMap::is_using_baked_light); + ClassDB::bind_method(_MD("_baked_light_changed"),&GridMap::_baked_light_changed); + ClassDB::bind_method(_MD("set_use_baked_light","use"),&GridMap::set_use_baked_light); + ClassDB::bind_method(_MD("is_using_baked_light","use"),&GridMap::is_using_baked_light); - ObjectTypeDB::bind_method(_MD("_get_baked_light_meshes"),&GridMap::_get_baked_light_meshes); + ClassDB::bind_method(_MD("_get_baked_light_meshes"),&GridMap::_get_baked_light_meshes); - ObjectTypeDB::set_method_flags("GridMap","bake_geometry",METHOD_FLAGS_DEFAULT|METHOD_FLAG_EDITOR); + ClassDB::set_method_flags("GridMap","bake_geometry",METHOD_FLAGS_DEFAULT|METHOD_FLAG_EDITOR); - ObjectTypeDB::bind_method(_MD("clear"),&GridMap::clear); + ClassDB::bind_method(_MD("clear"),&GridMap::clear); BIND_CONSTANT( INVALID_CELL_ITEM ); @@ -1436,7 +1435,7 @@ void GridMap::_update_area_instances() { } -Error GridMap::create_area(int p_id,const AABB& p_bounds) { +Error GridMap::create_area(int p_id,const Rect3& p_bounds) { ERR_FAIL_COND_V(area_map.has(p_id),ERR_ALREADY_EXISTS); ERR_EXPLAIN("ID 0 is taken as global area, start from 1"); @@ -1485,12 +1484,12 @@ Error GridMap::create_area(int p_id,const AABB& p_bounds) { return OK; } -AABB GridMap::area_get_bounds(int p_area) const { +Rect3 GridMap::area_get_bounds(int p_area) const { - ERR_FAIL_COND_V(!area_map.has(p_area),AABB()); + ERR_FAIL_COND_V(!area_map.has(p_area),Rect3()); const Area *a = area_map[p_area]; - AABB aabb; + Rect3 aabb; aabb.pos=Vector3(a->from.x,a->from.y,a->from.z); aabb.size=Vector3(a->to.x,a->to.y,a->to.z)-aabb.pos; @@ -1668,7 +1667,7 @@ void GridMap::bake_geometry() { } - DVector<Vector3> vv; + PoolVector<Vector3> vv; vv.fill_with(vertices); //print_line("TOTAL VERTICES: "+itos(vv.size())); tmesh = Ref<TriangleMesh>( memnew( TriangleMesh )); @@ -1707,10 +1706,12 @@ void GridMap::bake_geometry() { void GridMap::_baked_light_changed() { -// if (!baked_light_instance) -// VS::get_singleton()->instance_geometry_set_baked_light(get_instance(),RID()); -// else -// VS::get_singleton()->instance_geometry_set_baked_light(get_instance(),baked_light_instance->get_baked_light_instance()); + /* + if (!baked_light_instance) + VS::get_singleton()->instance_geometry_set_baked_light(get_instance(),RID()); + else + VS::get_singleton()->instance_geometry_set_baked_light(get_instance(),baked_light_instance->get_baked_light_instance()); + */ for(Map<OctantKey,Octant*>::Element *E=octant_map.front();E;E=E->next()) { for(Map<int,Octant::ItemInstances>::Element *F=E->get()->items.front();F;F=F->next()) { @@ -1809,6 +1810,7 @@ bool GridMap::is_using_baked_light() const{ } + GridMap::GridMap() { cell_size=2; @@ -1830,7 +1832,8 @@ GridMap::GridMap() { baked_light_instance=NULL; use_baked_light=false; - + navigation = NULL; + set_notify_transform(true); } diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 0116ea094..04d140cdc 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -44,7 +44,7 @@ class BakedLightInstance; class GridMap : public Spatial { - OBJ_TYPE( GridMap, Spatial ); + GDCLASS( GridMap, Spatial ); enum { MAP_DIRTY_TRANSFORMS=1, @@ -268,8 +268,8 @@ public: void set_clip(bool p_enabled, bool p_clip_above=true, int p_floor=0, Vector3::Axis p_axis=Vector3::AXIS_X); - Error create_area(int p_id,const AABB& p_area); - AABB area_get_bounds(int p_area) const; + Error create_area(int p_id,const Rect3& p_area); + Rect3 area_get_bounds(int p_area) const; void area_set_exterior_portal(int p_area,bool p_enable); void area_set_name(int p_area,const String& p_name); String area_get_name(int p_area) const; diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 87afe3d5a..109f6338d 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -99,70 +99,70 @@ void GridMapEditor::_menu_option(int p_option) { } break; case MENU_OPTION_CURSOR_ROTATE_Y: { - Matrix3 r; + Basis r; if (input_action==INPUT_DUPLICATE) { r.set_orthogonal_index(selection.duplicate_rot); - r.rotate(Vector3(0,1,0),Math_PI/2.0); + r.rotate(Vector3(0,1,0),-Math_PI/2.0); selection.duplicate_rot=r.get_orthogonal_index(); _update_duplicate_indicator(); break; } r.set_orthogonal_index(cursor_rot); - r.rotate(Vector3(0,1,0),Math_PI/2.0); + r.rotate(Vector3(0,1,0),-Math_PI/2.0); cursor_rot=r.get_orthogonal_index(); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_ROTATE_X: { - Matrix3 r; + Basis r; if (input_action==INPUT_DUPLICATE) { r.set_orthogonal_index(selection.duplicate_rot); - r.rotate(Vector3(1,0,0),Math_PI/2.0); + r.rotate(Vector3(1,0,0),-Math_PI/2.0); selection.duplicate_rot=r.get_orthogonal_index(); _update_duplicate_indicator(); break; } r.set_orthogonal_index(cursor_rot); - r.rotate(Vector3(1,0,0),Math_PI/2.0); + r.rotate(Vector3(1,0,0),-Math_PI/2.0); cursor_rot=r.get_orthogonal_index(); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_ROTATE_Z: { - Matrix3 r; + Basis r; if (input_action==INPUT_DUPLICATE) { r.set_orthogonal_index(selection.duplicate_rot); - r.rotate(Vector3(0,0,1),Math_PI/2.0); + r.rotate(Vector3(0,0,1),-Math_PI/2.0); selection.duplicate_rot=r.get_orthogonal_index(); _update_duplicate_indicator(); break; } r.set_orthogonal_index(cursor_rot); - r.rotate(Vector3(0,0,1),Math_PI/2.0); + r.rotate(Vector3(0,0,1),-Math_PI/2.0); cursor_rot=r.get_orthogonal_index(); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_Y: { - Matrix3 r; + Basis r; r.set_orthogonal_index(cursor_rot); - r.rotate(Vector3(0,1,0),-Math_PI/2.0); + r.rotate(Vector3(0,1,0),Math_PI/2.0); cursor_rot=r.get_orthogonal_index(); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_X: { - Matrix3 r; + Basis r; r.set_orthogonal_index(cursor_rot); - r.rotate(Vector3(1,0,0),-Math_PI/2.0); + r.rotate(Vector3(1,0,0),Math_PI/2.0); cursor_rot=r.get_orthogonal_index(); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_Z: { - Matrix3 r; + Basis r; r.set_orthogonal_index(cursor_rot); - r.rotate(Vector3(0,0,1),-Math_PI/2.0); + r.rotate(Vector3(0,0,1),Math_PI/2.0); cursor_rot=r.get_orthogonal_index(); _update_cursor_transform(); } break; @@ -191,7 +191,7 @@ void GridMapEditor::_menu_option(int p_option) { if (!selection.active) break; int area = node->get_unused_area_id(); - Error err = node->create_area(area,AABB(selection.begin,selection.end-selection.begin+Vector3(1,1,1))); + Error err = node->create_area(area,Rect3(selection.begin,selection.end-selection.begin+Vector3(1,1,1))); if (err!=OK) { @@ -212,6 +212,18 @@ void GridMapEditor::_menu_option(int p_option) { _update_areas_display(); update_areas(); } break; + case MENU_OPTION_SELECTION_DUPLICATE: + if (!(selection.active && input_action==INPUT_NONE)) + return; + if (last_mouseover==Vector3(-1,-1,-1)) //nono mouseovering anythin + break; + + input_action=INPUT_DUPLICATE; + selection.click=last_mouseover; + selection.current=last_mouseover; + selection.duplicate_rot=0; + _update_duplicate_indicator(); + break; case MENU_OPTION_SELECTION_CLEAR: { if (!selection.active) return; @@ -306,7 +318,7 @@ bool GridMapEditor::do_input_action(Camera* p_camera,const Point2& p_point,bool p.d=edit_floor[edit_axis]*node->get_cell_size(); Vector3 inters; - if (!p.intersects_segment(from, from + normal * settings_pick_distance->get_val(), &inters)) + if (!p.intersects_segment(from, from + normal * settings_pick_distance->get_value(), &inters)) return false; @@ -346,7 +358,7 @@ bool GridMapEditor::do_input_action(Camera* p_camera,const Point2& p_point,bool } last_mouseover=Vector3(cell[0],cell[1],cell[2]); - VS::get_singleton()->instance_set_transform(grid_instance[edit_axis],Transform(Matrix3(),grid_ofs)); + VS::get_singleton()->instance_set_transform(grid_instance[edit_axis],Transform(Basis(),grid_ofs)); if (cursor_instance.is_valid()) { @@ -377,7 +389,9 @@ bool GridMapEditor::do_input_action(Camera* p_camera,const Point2& p_point,bool int item=node->get_cell_item(cell[0],cell[1],cell[2]); if (item>=0) { selected_pallete=item; + theme_pallete->set_current(item); update_pallete(); + _update_cursor_instance(); } return true; } if (input_action==INPUT_PAINT) { @@ -445,7 +459,7 @@ void GridMapEditor::_update_duplicate_indicator() { Transform xf; xf.scale(Vector3(1,1,1)*(Vector3(1,1,1)+(selection.end-selection.begin))*node->get_cell_size()); xf.origin=(selection.begin+(selection.current-selection.click))*node->get_cell_size(); - Matrix3 rot; + Basis rot; rot.set_orthogonal_index(selection.duplicate_rot); xf.basis = rot * xf.basis; @@ -467,7 +481,7 @@ void GridMapEditor::_duplicate_paste() { List< __Item > items; - Matrix3 rot; + Basis rot; rot.set_orthogonal_index(selection.duplicate_rot); for(int i=selection.begin.x;i<=selection.end.x;i++) { @@ -484,7 +498,7 @@ void GridMapEditor::_duplicate_paste() { Vector3 rel=Vector3(i,j,k)-selection.begin; rel = rot.xform(rel); - Matrix3 orm; + Basis orm; orm.set_orthogonal_index(orientation); orm = rot * orm; @@ -530,39 +544,16 @@ bool GridMapEditor::forward_spatial_input_event(Camera* p_camera,const InputEven if (edit_mode->get_selected()==0) { // regular click switch (p_event.type) { - case InputEvent::KEY: { - - if (p_event.key.pressed && p_event.key.scancode==KEY_D && p_event.key.mod.shift && selection.active && input_action==INPUT_NONE) { - - if (last_mouseover==Vector3(-1,-1,-1)) //nono mouseovering anythin - return false; - - input_action=INPUT_DUPLICATE; - selection.click=last_mouseover; - selection.current=last_mouseover; - selection.duplicate_rot=0; - _update_duplicate_indicator(); - - - } - - if (p_event.key.pressed && p_event.key.scancode==KEY_DELETE && selection.active) { - - _delete_selection(); - return true; - } - - } break; case InputEvent::MOUSE_BUTTON: { if (p_event.mouse_button.button_index==BUTTON_WHEEL_UP && (p_event.mouse_button.mod.command || p_event.mouse_button.mod.shift)) { if (p_event.mouse_button.pressed) - floor->set_val( floor->get_val() +1); + floor->set_value( floor->get_value() +1); return true; //eaten } else if (p_event.mouse_button.button_index==BUTTON_WHEEL_DOWN && (p_event.mouse_button.mod.command || p_event.mouse_button.mod.shift)) { if (p_event.mouse_button.pressed) - floor->set_val( floor->get_val() -1); + floor->set_value( floor->get_value() -1); return true; } @@ -673,7 +664,7 @@ bool GridMapEditor::forward_spatial_input_event(Camera* p_camera,const InputEven for(List<int>::Element *E=areas.front();E;E=E->next()) { int area = E->get(); - AABB aabb = node->area_get_bounds(area); + Rect3 aabb = node->area_get_bounds(area); aabb.pos*=node->get_cell_size(); aabb.size*=node->get_cell_size(); @@ -740,8 +731,8 @@ void GridMapEditor::update_pallete() { theme_pallete->set_icon_mode(ItemList::ICON_MODE_LEFT); } - float min_size = EDITOR_DEF("grid_map/preview_size",64); - theme_pallete->set_min_icon_size(Size2(min_size, min_size)); + float min_size = EDITOR_DEF("editors/grid_map/preview_size",64); + theme_pallete->set_fixed_icon_size(Size2(min_size, min_size)); theme_pallete->set_fixed_column_width(min_size*3/2); theme_pallete->set_max_text_lines(2); @@ -851,6 +842,11 @@ void GridMapEditor::edit(GridMap *p_gridmap) { VS *vs = VS::get_singleton(); last_mouseover=Vector3(-1,-1,-1); + input_action=INPUT_NONE; + selection.active=false; + _update_selection_transform(); + _update_duplicate_indicator(); + spatial_editor = editor->get_editor_plugin_screen()->cast_to<SpatialEditorPlugin>(); if (!node) { @@ -976,7 +972,7 @@ void GridMapEditor::update_grid() { grid_ofs[edit_axis]=edit_floor[edit_axis]*node->get_cell_size(); edit_grid_xform.origin=grid_ofs; - edit_grid_xform.basis=Matrix3(); + edit_grid_xform.basis=Basis(); for(int i=0;i<3;i++) { @@ -985,7 +981,7 @@ void GridMapEditor::update_grid() { } updating=true; - floor->set_val(edit_floor[edit_axis]); + floor->set_value(edit_floor[edit_axis]); updating=false; } @@ -1130,7 +1126,7 @@ void GridMapEditor::_update_areas_display() { RID mesh = VisualServer::get_singleton()->mesh_create(); - DVector<Plane> planes; + PoolVector<Plane> planes; for(int i=0;i<3;i++) { Vector3 axis; @@ -1146,7 +1142,7 @@ void GridMapEditor::_update_areas_display() { ad.mesh=mesh; ad.instance = VisualServer::get_singleton()->instance_create2(mesh,node->get_world()->get_scenario()); Transform xform; - AABB aabb = node->area_get_bounds(area); + Rect3 aabb = node->area_get_bounds(area); xform.origin=aabb.pos * node->get_cell_size(); xform.basis.scale(aabb.size * node->get_cell_size()); VisualServer::get_singleton()->instance_set_transform(ad.instance,global_xf * xform); @@ -1185,15 +1181,15 @@ void GridMapEditor::_floor_changed(float p_value) { void GridMapEditor::_bind_methods() { - ObjectTypeDB::bind_method("_menu_option",&GridMapEditor::_menu_option); - ObjectTypeDB::bind_method("_configure",&GridMapEditor::_configure); - ObjectTypeDB::bind_method("_item_selected_cbk",&GridMapEditor::_item_selected_cbk); - ObjectTypeDB::bind_method("_edit_mode_changed",&GridMapEditor::_edit_mode_changed); - ObjectTypeDB::bind_method("_area_renamed",&GridMapEditor::_area_renamed); - ObjectTypeDB::bind_method("_area_selected",&GridMapEditor::_area_selected); - ObjectTypeDB::bind_method("_floor_changed",&GridMapEditor::_floor_changed); + ClassDB::bind_method("_menu_option",&GridMapEditor::_menu_option); + ClassDB::bind_method("_configure",&GridMapEditor::_configure); + ClassDB::bind_method("_item_selected_cbk",&GridMapEditor::_item_selected_cbk); + ClassDB::bind_method("_edit_mode_changed",&GridMapEditor::_edit_mode_changed); + ClassDB::bind_method("_area_renamed",&GridMapEditor::_area_renamed); + ClassDB::bind_method("_area_selected",&GridMapEditor::_area_selected); + ClassDB::bind_method("_floor_changed",&GridMapEditor::_floor_changed); - ObjectTypeDB::bind_method(_MD("_set_display_mode","mode"), &GridMapEditor::_set_display_mode); + ClassDB::bind_method(_MD("_set_display_mode","mode"), &GridMapEditor::_set_display_mode); } @@ -1205,7 +1201,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { editor=p_editor; undo_redo=p_editor->get_undo_redo(); - int mw = EDITOR_DEF("grid_map/palette_min_width",230); + int mw = EDITOR_DEF("editors/grid_map/palette_min_width",230); Control *ec = memnew( Control); ec->set_custom_minimum_size(Size2(mw,0)); add_child(ec); @@ -1247,7 +1243,8 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { options->get_popup()->add_item("Create Exterior Connector",MENU_OPTION_SELECTION_MAKE_EXTERIOR_CONNECTOR); options->get_popup()->add_item("Erase Area",MENU_OPTION_REMOVE_AREA); options->get_popup()->add_separator(); - options->get_popup()->add_item("Selection -> Clear",MENU_OPTION_SELECTION_CLEAR); + options->get_popup()->add_item("Selection -> Duplicate",MENU_OPTION_SELECTION_DUPLICATE,KEY_MASK_SHIFT+KEY_INSERT); + options->get_popup()->add_item("Selection -> Clear",MENU_OPTION_SELECTION_CLEAR,KEY_MASK_SHIFT+KEY_DELETE); //options->get_popup()->add_separator(); //options->get_popup()->add_item("Configure",MENU_OPTION_CONFIGURE); @@ -1260,17 +1257,16 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { settings_vbc = memnew(VBoxContainer); settings_vbc->set_custom_minimum_size(Size2(200, 0)); settings_dialog->add_child(settings_vbc); - settings_dialog->set_child_rect(settings_vbc); settings_pick_distance = memnew(SpinBox); settings_pick_distance->set_max(10000.0f); settings_pick_distance->set_min(500.0f); settings_pick_distance->set_step(1.0f); - settings_pick_distance->set_val(EDITOR_DEF("gridmap_editor/pick_distance", 5000.0)); + settings_pick_distance->set_value(EDITOR_DEF("editors/grid_map/pick_distance", 5000.0)); settings_vbc->add_margin_child("Pick Distance:", settings_pick_distance); clip_mode=CLIP_DISABLED; - options->get_popup()->connect("item_pressed", this,"_menu_option"); + options->get_popup()->connect("id_pressed", this,"_menu_option"); HBoxContainer *hb = memnew( HBoxContainer ); add_child(hb); @@ -1299,6 +1295,8 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { hb->add_child(mode_list); mode_list->connect("pressed", this, "_set_display_mode", varray(DISPLAY_LIST)); + EDITOR_DEF("editors/grid_map/preview_size",64) + display_mode = DISPLAY_THUMBNAIL; selected_area=-1; @@ -1344,8 +1342,8 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { //selection mesh create - DVector<Vector3> lines; - DVector<Vector3> triangles; + PoolVector<Vector3> lines; + PoolVector<Vector3> triangles; for (int i=0;i<6;i++) { @@ -1379,7 +1377,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { for(int i=0;i<12;i++) { - AABB base(Vector3(0,0,0),Vector3(1,1,1)); + Rect3 base(Vector3(0,0,0),Vector3(1,1,1)); Vector3 a,b; base.get_edge(i,a,b); lines.push_back(a); @@ -1483,7 +1481,7 @@ void GridMapEditorPlugin::edit(Object *p_object) { bool GridMapEditorPlugin::handles(Object *p_object) const { - return p_object->is_type("GridMap"); + return p_object->is_class("GridMap"); } void GridMapEditorPlugin::make_visible(bool p_visible) { diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index fc43866ef..2c0ff99dc 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -40,7 +40,7 @@ class SpatialEditorPlugin; class GridMapEditor : public VBoxContainer { - OBJ_TYPE(GridMapEditor, VBoxContainer ); + GDCLASS(GridMapEditor, VBoxContainer ); enum { @@ -167,6 +167,7 @@ class GridMapEditor : public VBoxContainer { MENU_OPTION_DUPLICATE_SELECTS, MENU_OPTION_SELECTION_MAKE_AREA, MENU_OPTION_SELECTION_MAKE_EXTERIOR_CONNECTOR, + MENU_OPTION_SELECTION_DUPLICATE, MENU_OPTION_SELECTION_CLEAR, MENU_OPTION_REMOVE_AREA, MENU_OPTION_GRIDMAP_SETTINGS @@ -237,7 +238,7 @@ public: class GridMapEditorPlugin : public EditorPlugin { - OBJ_TYPE( GridMapEditorPlugin, EditorPlugin ); + GDCLASS( GridMapEditorPlugin, EditorPlugin ); GridMapEditor *gridmap_editor; EditorNode *editor; diff --git a/modules/gridmap/register_types.cpp b/modules/gridmap/register_types.cpp index 9dcc04b22..b4a0d3b0b 100644 --- a/modules/gridmap/register_types.cpp +++ b/modules/gridmap/register_types.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -36,7 +36,7 @@ void register_gridmap_types() { #ifndef _3D_DISABLED - ObjectTypeDB::register_type<GridMap>(); + ClassDB::register_class<GridMap>(); #ifdef TOOLS_ENABLED EditorPlugins::add_by_type<GridMapEditorPlugin>(); #endif diff --git a/modules/gridmap/register_types.h b/modules/gridmap/register_types.h index 7b5c10f9e..cc7c13961 100644 --- a/modules/gridmap/register_types.h +++ b/modules/gridmap/register_types.h @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/modules/ik/SCsub b/modules/ik/SCsub deleted file mode 100644 index 211a04346..000000000 --- a/modules/ik/SCsub +++ /dev/null @@ -1,3 +0,0 @@ -Import('env') - -env.add_source_files(env.modules_sources,"*.cpp") diff --git a/modules/ik/ik.cpp b/modules/ik/ik.cpp deleted file mode 100644 index 6c383fdb5..000000000 --- a/modules/ik/ik.cpp +++ /dev/null @@ -1,326 +0,0 @@ -/*************************************************************************/ -/* ik.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* http://www.godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ -/* This file is Copyright (c) 2016 Sergey Lapin <slapinid@gmail.com> */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "ik.h" - -bool InverseKinematics::_get(const StringName& p_name,Variant &r_ret) const -{ - - if (String(p_name)=="ik_bone") { - - r_ret=get_bone_name(); - return true; - } - - return false; -} - -bool InverseKinematics::_set(const StringName& p_name, const Variant& p_value) -{ - - if (String(p_name)=="ik_bone") { - - set_bone_name(p_value); - changed = true; - return true; - } - - return false; -} - -void InverseKinematics::_get_property_list( List<PropertyInfo>* p_list ) const -{ - - Skeleton *parent=NULL; - if(get_parent()) - parent=get_parent()->cast_to<Skeleton>(); - - if (parent) { - - String names; - for(int i=0;i<parent->get_bone_count();i++) { - if(i>0) - names+=","; - names+=parent->get_bone_name(i); - } - - p_list->push_back(PropertyInfo(Variant::STRING,"ik_bone",PROPERTY_HINT_ENUM,names)); - } else { - - p_list->push_back(PropertyInfo(Variant::STRING,"ik_bone")); - - } - -} - -void InverseKinematics::_check_bind() -{ - - if (get_parent() && get_parent()->cast_to<Skeleton>()) { - Skeleton *sk = get_parent()->cast_to<Skeleton>(); - int idx = sk->find_bone(ik_bone); - if (idx!=-1) { - ik_bone_no = idx; - bound=true; - } - skel = sk; - } -} - -void InverseKinematics::_check_unbind() -{ - - if (bound) { - - if (get_parent() && get_parent()->cast_to<Skeleton>()) { - Skeleton *sk = get_parent()->cast_to<Skeleton>(); - int idx = sk->find_bone(ik_bone); - if (idx!=-1) - ik_bone_no = idx; - else - ik_bone_no = 0; - skel = sk; - - } - bound=false; - } -} - - -void InverseKinematics::set_bone_name(const String& p_name) -{ - - if (is_inside_tree()) - _check_unbind(); - - ik_bone=p_name; - - if (is_inside_tree()) - _check_bind(); - changed = true; -} - -String InverseKinematics::get_bone_name() const -{ - - return ik_bone; -} - -void InverseKinematics::set_iterations(int itn) -{ - - if (is_inside_tree()) - _check_unbind(); - - iterations=itn; - - if (is_inside_tree()) - _check_bind(); - changed = true; -} - -int InverseKinematics::get_iterations() const -{ - - return iterations; -} - -void InverseKinematics::set_chain_size(int cs) -{ - if (is_inside_tree()) - _check_unbind(); - - chain_size=cs; - chain.clear(); - if (bound) - update_parameters(); - - if (is_inside_tree()) - _check_bind(); - changed = true; -} - -int InverseKinematics::get_chain_size() const -{ - - return chain_size; -} - -void InverseKinematics::set_precision(float p) -{ - - if (is_inside_tree()) - _check_unbind(); - - precision=p; - - if (is_inside_tree()) - _check_bind(); - changed = true; -} - -float InverseKinematics::get_precision() const -{ - - return precision; -} - -void InverseKinematics::set_speed(float p) -{ - - if (is_inside_tree()) - _check_unbind(); - - speed=p; - - if (is_inside_tree()) - _check_bind(); - changed = true; -} - -float InverseKinematics::get_speed() const -{ - - return speed; -} - -void InverseKinematics::update_parameters() -{ - tail_bone = -1; - for (int i = 0; i < skel->get_bone_count(); i++) - if (skel->get_bone_parent(i) == ik_bone_no) - tail_bone = i; - int cur_bone = ik_bone_no; - int its = chain_size; - while (its > 0 && cur_bone >= 0) { - chain.push_back(cur_bone); - cur_bone = skel->get_bone_parent(cur_bone); - its--; - } -} - -void InverseKinematics::_notification(int p_what) -{ - - switch(p_what) { - - case NOTIFICATION_ENTER_TREE: { - - _check_bind(); - if (bound) { - update_parameters(); - changed = false; - set_process(true); - } - } break; - case NOTIFICATION_PROCESS: { - float delta = get_process_delta_time(); - Spatial *sksp = skel->cast_to<Spatial>(); - if (!bound) - break; - if (!sksp) - break; - if (changed) { - update_parameters(); - changed = false; - } - Vector3 to = get_translation(); - for (int hump = 0; hump < iterations; hump++) { - int depth = 0; - float olderr = 1000.0; - float psign = 1.0; - bool reached = false; - - for (List<int>::Element *b = chain.front(); b; b = b->next()) { - int cur_bone = b->get(); - Vector3 d = skel->get_bone_global_pose(tail_bone).origin; - Vector3 rg = to; - float err = d.distance_squared_to(rg); - if (err < precision) { - if (!reached && err < precision) - reached = true; - break; - } else - if (reached) - reached = false; - if (err > olderr) - psign = -psign; - Transform mod = skel->get_bone_global_pose(cur_bone); - Quat q1 = Quat(mod.basis).normalized(); - Transform mod2 = mod.looking_at(to, Vector3(0.0, 1.0, 0.0)); - Quat q2 = Quat(mod2.basis).normalized(); - if (psign < 0.0) - q2 = q2.inverse(); - Quat q = q1.slerp(q2, speed / (1.0 + 500.0 * depth)).normalized(); - Transform fin = Transform(q); - fin.origin = mod.origin; - skel->set_bone_global_pose(cur_bone, fin); - depth++; - } - if (reached) - break; - - } - - } break; - case NOTIFICATION_EXIT_TREE: { - set_process(false); - - _check_unbind(); - } break; - } -} -void InverseKinematics::_bind_methods() { - ObjectTypeDB::bind_method(_MD("set_bone_name","ik_bone"),&InverseKinematics::set_bone_name); - ObjectTypeDB::bind_method(_MD("get_bone_name"),&InverseKinematics::get_bone_name); - ObjectTypeDB::bind_method(_MD("set_iterations","iterations"),&InverseKinematics::set_iterations); - ObjectTypeDB::bind_method(_MD("get_iterations"),&InverseKinematics::get_iterations); - ObjectTypeDB::bind_method(_MD("set_chain_size","chain_size"),&InverseKinematics::set_chain_size); - ObjectTypeDB::bind_method(_MD("get_chain_size"),&InverseKinematics::get_chain_size); - ObjectTypeDB::bind_method(_MD("set_precision","precision"),&InverseKinematics::set_precision); - ObjectTypeDB::bind_method(_MD("get_precision"),&InverseKinematics::get_precision); - ObjectTypeDB::bind_method(_MD("set_speed","speed"),&InverseKinematics::set_speed); - ObjectTypeDB::bind_method(_MD("get_speed"),&InverseKinematics::get_speed); - ADD_PROPERTY(PropertyInfo(Variant::INT, "iterations"), _SCS("set_iterations"), _SCS("get_iterations")); - ADD_PROPERTY(PropertyInfo(Variant::INT, "chain_size"), _SCS("set_chain_size"), _SCS("get_chain_size")); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "precision"), _SCS("set_precision"), _SCS("get_precision")); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed"), _SCS("set_speed"), _SCS("get_speed")); -} - -InverseKinematics::InverseKinematics() -{ - bound=false; - chain_size = 2; - iterations = 100; - precision = 0.001; - speed = 0.2; - -} - diff --git a/modules/jpg/SCsub b/modules/jpg/SCsub new file mode 100644 index 000000000..e72dc6a1c --- /dev/null +++ b/modules/jpg/SCsub @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_jpg = env_modules.Clone() + +# Thirdparty source files +# Not unbundled for now as they are not commonly available as shared library +thirdparty_dir = "#thirdparty/jpeg-compressor/" +thirdparty_sources = [ + "jpgd.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_jpg.add_source_files(env.modules_sources, thirdparty_sources) +env_jpg.Append(CPPPATH=[thirdparty_dir]) + +# Godot's own source files +env_jpg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/jpg/config.py b/modules/jpg/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/jpg/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp new file mode 100644 index 000000000..1152d42e1 --- /dev/null +++ b/modules/jpg/image_loader_jpegd.cpp @@ -0,0 +1,144 @@ +/*************************************************************************/ +/* image_loader_jpegd.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "image_loader_jpegd.h" + +#include "print_string.h" +#include "os/os.h" + +#include <jpgd.h> +#include <string.h> + + +Error jpeg_load_image_from_buffer(Image *p_image,const uint8_t* p_buffer, int p_buffer_len) { + + jpgd::jpeg_decoder_mem_stream mem_stream(p_buffer,p_buffer_len); + + jpgd::jpeg_decoder decoder(&mem_stream); + + if (decoder.get_error_code() != jpgd::JPGD_SUCCESS) { + return ERR_CANT_OPEN; + } + + const int image_width = decoder.get_width(); + const int image_height = decoder.get_height(); + int comps = decoder.get_num_components(); + if (comps==3) + comps=4; //weird + + if (decoder.begin_decoding() != jpgd::JPGD_SUCCESS) + return ERR_FILE_CORRUPT; + + const int dst_bpl = image_width * comps; + + PoolVector<uint8_t> data; + + data.resize(dst_bpl * image_height); + + PoolVector<uint8_t>::Write dw = data.write(); + + jpgd::uint8 *pImage_data = (jpgd::uint8*)dw.ptr(); + + for (int y = 0; y < image_height; y++) + { + const jpgd::uint8* pScan_line; + jpgd::uint scan_line_len; + if (decoder.decode((const void**)&pScan_line, &scan_line_len) != jpgd::JPGD_SUCCESS) + { + return ERR_FILE_CORRUPT; + } + + jpgd::uint8 *pDst = pImage_data + y * dst_bpl; + memcpy(pDst, pScan_line, dst_bpl); + + + } + + + //all good + + Image::Format fmt; + if (comps==1) + fmt=Image::FORMAT_L8; + else + fmt=Image::FORMAT_RGBA8; + + dw = PoolVector<uint8_t>::Write(); + p_image->create(image_width,image_height,0,fmt,data); + + return OK; + +} + + +Error ImageLoaderJPG::load_image(Image *p_image,FileAccess *f) { + + + PoolVector<uint8_t> src_image; + int src_image_len = f->get_len(); + ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT); + src_image.resize(src_image_len); + + PoolVector<uint8_t>::Write w = src_image.write(); + + f->get_buffer(&w[0],src_image_len); + + f->close(); + + + Error err = jpeg_load_image_from_buffer(p_image,w.ptr(),src_image_len); + + w = PoolVector<uint8_t>::Write(); + + return err; + +} + +void ImageLoaderJPG::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("jpg"); + p_extensions->push_back("jpeg"); +} + + +static Image _jpegd_mem_loader_func(const uint8_t* p_png,int p_size) { + + Image img; + Error err = jpeg_load_image_from_buffer(&img,p_png,p_size); + if (err) + ERR_PRINT("Couldn't initialize ImageLoaderJPG with the given resource."); + + return img; +} + +ImageLoaderJPG::ImageLoaderJPG() { + + Image::_jpg_mem_loader_func=_jpegd_mem_loader_func; +} + + diff --git a/modules/jpg/image_loader_jpegd.h b/modules/jpg/image_loader_jpegd.h new file mode 100644 index 000000000..aeb219aa5 --- /dev/null +++ b/modules/jpg/image_loader_jpegd.h @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* image_loader_jpegd.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef IMAGE_LOADER_JPG_H +#define IMAGE_LOADER_JPG_H + +#include "io/image_loader.h" + +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ +class ImageLoaderJPG : public ImageFormatLoader { + + +public: + + virtual Error load_image(Image *p_image,FileAccess *f); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + ImageLoaderJPG(); +}; + + + +#endif diff --git a/modules/jpg/register_types.cpp b/modules/jpg/register_types.cpp new file mode 100644 index 000000000..bcd467f5f --- /dev/null +++ b/modules/jpg/register_types.cpp @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "image_loader_jpegd.h" + +static ImageLoaderJPG *image_loader_jpg = NULL; + +void register_jpg_types() { + + image_loader_jpg = memnew( ImageLoaderJPG ); + ImageLoader::add_image_format_loader(image_loader_jpg); +} + +void unregister_jpg_types() { + + memdelete( image_loader_jpg ); +} diff --git a/modules/jpg/register_types.h b/modules/jpg/register_types.h new file mode 100644 index 000000000..62132bf46 --- /dev/null +++ b/modules/jpg/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_jpg_types(); +void unregister_jpg_types(); diff --git a/modules/ogg/SCsub b/modules/ogg/SCsub new file mode 100644 index 000000000..5eabaf6f2 --- /dev/null +++ b/modules/ogg/SCsub @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_ogg = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_libogg'] != 'no'): + thirdparty_dir = "#thirdparty/libogg/" + thirdparty_sources = [ + "bitwise.c", + "framing.c", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_ogg.add_source_files(env.modules_sources, thirdparty_sources) + env_ogg.Append(CPPPATH=[thirdparty_dir]) + +# Godot source files +env_ogg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/ogg/config.py b/modules/ogg/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/ogg/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/ogg/register_types.cpp b/modules/ogg/register_types.cpp new file mode 100644 index 000000000..ed796ec09 --- /dev/null +++ b/modules/ogg/register_types.cpp @@ -0,0 +1,35 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +// Dummy module as libogg is needed by other modules (vorbis, theora, opus, ...) + +void register_ogg_types() {} + +void unregister_ogg_types() {} diff --git a/modules/ogg/register_types.h b/modules/ogg/register_types.h new file mode 100644 index 000000000..cc67b4d2f --- /dev/null +++ b/modules/ogg/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_ogg_types(); +void unregister_ogg_types(); diff --git a/modules/openssl/SCsub b/modules/openssl/SCsub new file mode 100644 index 000000000..0e811ce8c --- /dev/null +++ b/modules/openssl/SCsub @@ -0,0 +1,687 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_openssl = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_openssl'] != 'no'): + thirdparty_dir = "#thirdparty/openssl/" + + thirdparty_sources = [ + "ssl/t1_lib.c", + "ssl/t1_ext.c", + "ssl/s3_srvr.c", + "ssl/t1_enc.c", + "ssl/t1_meth.c", + "ssl/s23_clnt.c", + "ssl/ssl_asn1.c", + "ssl/tls_srp.c", + "ssl/kssl.c", + "ssl/d1_both.c", + "ssl/t1_clnt.c", + "ssl/bio_ssl.c", + "ssl/d1_srtp.c", + "ssl/t1_reneg.c", + "ssl/ssl_cert.c", + "ssl/s3_lib.c", + "ssl/d1_srvr.c", + "ssl/s23_meth.c", + "ssl/ssl_stat.c", + "ssl/ssl_err.c", + "ssl/ssl_algs.c", + "ssl/s3_cbc.c", + "ssl/d1_clnt.c", + "ssl/s3_pkt.c", + "ssl/d1_meth.c", + "ssl/s3_both.c", + "ssl/s2_enc.c", + "ssl/s3_meth.c", + "ssl/s3_enc.c", + "ssl/s23_pkt.c", + "ssl/s2_pkt.c", + "ssl/d1_pkt.c", + "ssl/ssl_rsa.c", + "ssl/s23_srvr.c", + "ssl/s2_meth.c", + "ssl/s3_clnt.c", + "ssl/s23_lib.c", + "ssl/t1_srvr.c", + "ssl/ssl_lib.c", + "ssl/ssl_txt.c", + "ssl/s2_srvr.c", + "ssl/ssl_sess.c", + "ssl/s2_clnt.c", + "ssl/d1_lib.c", + "ssl/s2_lib.c", + "ssl/ssl_err2.c", + "ssl/ssl_ciph.c", + "crypto/dsa/dsa_lib.c", + "crypto/dsa/dsa_pmeth.c", + "crypto/dsa/dsa_ossl.c", + "crypto/dsa/dsa_gen.c", + "crypto/dsa/dsa_asn1.c", + "crypto/dsa/dsa_prn.c", + "crypto/dsa/dsa_sign.c", + "crypto/dsa/dsa_key.c", + "crypto/dsa/dsa_vrf.c", + "crypto/dsa/dsa_err.c", + "crypto/dsa/dsa_ameth.c", + "crypto/dsa/dsa_depr.c", + "crypto/x509/x509_lu.c", + "crypto/x509/x509cset.c", + "crypto/x509/x509_set.c", + "crypto/x509/x509_d2.c", + "crypto/x509/x509_txt.c", + "crypto/x509/x509rset.c", + "crypto/x509/by_dir.c", + "crypto/x509/x509_vpm.c", + "crypto/x509/x509_vfy.c", + "crypto/x509/x509_trs.c", + "crypto/x509/by_file.c", + "crypto/x509/x509_obj.c", + "crypto/x509/x509spki.c", + "crypto/x509/x509_v3.c", + "crypto/x509/x509_req.c", + "crypto/x509/x509_att.c", + "crypto/x509/x_all.c", + "crypto/x509/x509_ext.c", + "crypto/x509/x509type.c", + "crypto/x509/x509_def.c", + "crypto/x509/x509_err.c", + "crypto/x509/x509name.c", + "crypto/x509/x509_r2x.c", + "crypto/x509/x509_cmp.c", + "crypto/asn1/x_pkey.c", + "crypto/asn1/a_gentm.c", + "crypto/asn1/x_sig.c", + "crypto/asn1/t_req.c", + "crypto/asn1/t_pkey.c", + "crypto/asn1/p8_pkey.c", + "crypto/asn1/a_i2d_fp.c", + "crypto/asn1/x_val.c", + "crypto/asn1/f_string.c", + "crypto/asn1/p5_pbe.c", + "crypto/asn1/bio_ndef.c", + "crypto/asn1/a_bool.c", + "crypto/asn1/asn1_gen.c", + "crypto/asn1/x_algor.c", + "crypto/asn1/bio_asn1.c", + "crypto/asn1/asn_mime.c", + "crypto/asn1/t_x509.c", + "crypto/asn1/a_strex.c", + "crypto/asn1/x_nx509.c", + "crypto/asn1/asn1_err.c", + "crypto/asn1/x_crl.c", + "crypto/asn1/a_print.c", + "crypto/asn1/a_type.c", + "crypto/asn1/tasn_new.c", + "crypto/asn1/n_pkey.c", + "crypto/asn1/x_bignum.c", + "crypto/asn1/asn_pack.c", + "crypto/asn1/evp_asn1.c", + "crypto/asn1/t_bitst.c", + "crypto/asn1/x_req.c", + "crypto/asn1/a_time.c", + "crypto/asn1/x_name.c", + "crypto/asn1/x_pubkey.c", + "crypto/asn1/tasn_typ.c", + "crypto/asn1/asn_moid.c", + "crypto/asn1/a_utctm.c", + "crypto/asn1/asn1_lib.c", + "crypto/asn1/x_x509a.c", + "crypto/asn1/a_set.c", + "crypto/asn1/t_crl.c", + "crypto/asn1/p5_pbev2.c", + "crypto/asn1/tasn_enc.c", + "crypto/asn1/a_mbstr.c", + "crypto/asn1/tasn_dec.c", + "crypto/asn1/x_x509.c", + "crypto/asn1/a_octet.c", + "crypto/asn1/x_long.c", + "crypto/asn1/a_bytes.c", + "crypto/asn1/t_x509a.c", + "crypto/asn1/a_enum.c", + "crypto/asn1/a_int.c", + "crypto/asn1/tasn_prn.c", + "crypto/asn1/i2d_pr.c", + "crypto/asn1/a_utf8.c", + "crypto/asn1/t_spki.c", + "crypto/asn1/a_digest.c", + "crypto/asn1/a_dup.c", + "crypto/asn1/i2d_pu.c", + "crypto/asn1/a_verify.c", + "crypto/asn1/f_enum.c", + "crypto/asn1/a_sign.c", + "crypto/asn1/d2i_pr.c", + "crypto/asn1/asn1_par.c", + "crypto/asn1/x_spki.c", + "crypto/asn1/a_d2i_fp.c", + "crypto/asn1/f_int.c", + "crypto/asn1/x_exten.c", + "crypto/asn1/tasn_utl.c", + "crypto/asn1/nsseq.c", + "crypto/asn1/a_bitstr.c", + "crypto/asn1/x_info.c", + "crypto/asn1/a_strnid.c", + "crypto/asn1/a_object.c", + "crypto/asn1/tasn_fre.c", + "crypto/asn1/d2i_pu.c", + "crypto/asn1/ameth_lib.c", + "crypto/asn1/x_attrib.c", + "crypto/evp/m_sha.c", + "crypto/evp/e_camellia.c", + "crypto/evp/e_aes.c", + "crypto/evp/bio_b64.c", + "crypto/evp/m_sigver.c", + "crypto/evp/m_wp.c", + "crypto/evp/m_sha1.c", + "crypto/evp/p_seal.c", + "crypto/evp/c_alld.c", + "crypto/evp/p5_crpt.c", + "crypto/evp/e_rc4.c", + "crypto/evp/m_ecdsa.c", + "crypto/evp/bio_enc.c", + "crypto/evp/e_des3.c", + "crypto/evp/m_null.c", + "crypto/evp/bio_ok.c", + "crypto/evp/pmeth_gn.c", + "crypto/evp/e_rc5.c", + "crypto/evp/e_rc2.c", + "crypto/evp/p_dec.c", + "crypto/evp/p_verify.c", + "crypto/evp/e_rc4_hmac_md5.c", + "crypto/evp/pmeth_lib.c", + "crypto/evp/m_ripemd.c", + "crypto/evp/m_md5.c", + "crypto/evp/e_bf.c", + "crypto/evp/p_enc.c", + "crypto/evp/m_dss.c", + "crypto/evp/bio_md.c", + "crypto/evp/evp_pbe.c", + "crypto/evp/e_seed.c", + "crypto/evp/e_cast.c", + "crypto/evp/p_open.c", + "crypto/evp/p5_crpt2.c", + "crypto/evp/m_dss1.c", + "crypto/evp/names.c", + "crypto/evp/evp_acnf.c", + "crypto/evp/e_des.c", + "crypto/evp/evp_cnf.c", + "crypto/evp/evp_lib.c", + "crypto/evp/digest.c", + "crypto/evp/evp_err.c", + "crypto/evp/evp_enc.c", + "crypto/evp/e_old.c", + "crypto/evp/c_all.c", + "crypto/evp/m_md2.c", + "crypto/evp/e_xcbc_d.c", + "crypto/evp/pmeth_fn.c", + "crypto/evp/p_lib.c", + "crypto/evp/evp_key.c", + "crypto/evp/encode.c", + "crypto/evp/e_aes_cbc_hmac_sha1.c", + "crypto/evp/e_aes_cbc_hmac_sha256.c", + "crypto/evp/m_mdc2.c", + "crypto/evp/e_null.c", + "crypto/evp/p_sign.c", + "crypto/evp/e_idea.c", + "crypto/evp/c_allc.c", + "crypto/evp/evp_pkey.c", + "crypto/evp/m_md4.c", + "crypto/ex_data.c", + "crypto/pkcs12/p12_p8e.c", + "crypto/pkcs12/p12_crt.c", + "crypto/pkcs12/p12_utl.c", + "crypto/pkcs12/p12_attr.c", + "crypto/pkcs12/p12_npas.c", + "crypto/pkcs12/p12_decr.c", + "crypto/pkcs12/p12_init.c", + "crypto/pkcs12/p12_kiss.c", + "crypto/pkcs12/p12_add.c", + "crypto/pkcs12/p12_p8d.c", + "crypto/pkcs12/p12_mutl.c", + "crypto/pkcs12/p12_crpt.c", + "crypto/pkcs12/pk12err.c", + "crypto/pkcs12/p12_asn.c", + "crypto/pkcs12/p12_key.c", + "crypto/ecdh/ech_key.c", + "crypto/ecdh/ech_ossl.c", + "crypto/ecdh/ech_lib.c", + "crypto/ecdh/ech_err.c", + "crypto/ecdh/ech_kdf.c", + "crypto/o_str.c", + "crypto/conf/conf_api.c", + "crypto/conf/conf_err.c", + "crypto/conf/conf_def.c", + "crypto/conf/conf_lib.c", + "crypto/conf/conf_mall.c", + "crypto/conf/conf_sap.c", + "crypto/conf/conf_mod.c", + "crypto/ebcdic.c", + "crypto/ecdsa/ecs_lib.c", + "crypto/ecdsa/ecs_asn1.c", + "crypto/ecdsa/ecs_ossl.c", + "crypto/ecdsa/ecs_vrf.c", + "crypto/ecdsa/ecs_sign.c", + "crypto/ecdsa/ecs_err.c", + "crypto/dso/dso_win32.c", + "crypto/dso/dso_lib.c", + "crypto/dso/dso_dlfcn.c", + "crypto/dso/dso_dl.c", + "crypto/dso/dso_beos.c", + "crypto/dso/dso_null.c", + "crypto/dso/dso_vms.c", + "crypto/dso/dso_err.c", + "crypto/dso/dso_openssl.c", + "crypto/cryptlib.c", + "crypto/md5/md5_one.c", + "crypto/md5/md5_dgst.c", + "crypto/pkcs7/pkcs7err.c", + "crypto/pkcs7/pk7_smime.c", + "crypto/pkcs7/bio_pk7.c", + "crypto/pkcs7/pk7_mime.c", + "crypto/pkcs7/pk7_lib.c", + "crypto/pkcs7/pk7_asn1.c", + "crypto/pkcs7/pk7_doit.c", + "crypto/pkcs7/pk7_attr.c", + "crypto/md4/md4_one.c", + "crypto/md4/md4_dgst.c", + "crypto/o_dir.c", + "crypto/buffer/buf_err.c", + "crypto/buffer/buf_str.c", + "crypto/buffer/buffer.c", + "crypto/cms/cms_lib.c", + "crypto/cms/cms_io.c", + "crypto/cms/cms_err.c", + "crypto/cms/cms_dd.c", + "crypto/cms/cms_smime.c", + "crypto/cms/cms_att.c", + "crypto/cms/cms_pwri.c", + "crypto/cms/cms_cd.c", + "crypto/cms/cms_sd.c", + "crypto/cms/cms_asn1.c", + "crypto/cms/cms_env.c", + "crypto/cms/cms_enc.c", + "crypto/cms/cms_ess.c", + "crypto/cms/cms_kari.c", + "crypto/mem_dbg.c", + "crypto/uid.c", + "crypto/stack/stack.c", + "crypto/ec/ec_ameth.c", + "crypto/ec/ec_err.c", + "crypto/ec/ec_lib.c", + "crypto/ec/ec_curve.c", + "crypto/ec/ec_oct.c", + "crypto/ec/ec_asn1.c", + "crypto/ec/ecp_oct.c", + "crypto/ec/ec_print.c", + "crypto/ec/ec2_smpl.c", + "crypto/ec/ecp_nistp224.c", + "crypto/ec/ec2_oct.c", + "crypto/ec/eck_prn.c", + "crypto/ec/ec_key.c", + "crypto/ec/ecp_nist.c", + "crypto/ec/ec_check.c", + "crypto/ec/ecp_smpl.c", + "crypto/ec/ec2_mult.c", + "crypto/ec/ecp_mont.c", + "crypto/ec/ecp_nistp521.c", + "crypto/ec/ec_mult.c", + "crypto/ec/ecp_nistputil.c", + "crypto/ec/ec_pmeth.c", + "crypto/ec/ec_cvt.c", + "crypto/ec/ecp_nistp256.c", + "crypto/krb5/krb5_asn.c", + "crypto/hmac/hmac.c", + "crypto/hmac/hm_ameth.c", + "crypto/hmac/hm_pmeth.c", + "crypto/comp/c_rle.c", + "crypto/comp/c_zlib.c", + "crypto/comp/comp_lib.c", + "crypto/comp/comp_err.c", + "crypto/des/fcrypt.c", + "crypto/des/str2key.c", + "crypto/des/cbc_cksm.c", + "crypto/des/des_enc.c", + "crypto/des/ofb_enc.c", + "crypto/des/read2pwd.c", + "crypto/des/ecb3_enc.c", + "crypto/des/rand_key.c", + "crypto/des/cfb64ede.c", + "crypto/des/rpc_enc.c", + "crypto/des/ofb64ede.c", + "crypto/des/qud_cksm.c", + "crypto/des/enc_writ.c", + "crypto/des/set_key.c", + "crypto/des/xcbc_enc.c", + "crypto/des/fcrypt_b.c", + "crypto/des/ede_cbcm_enc.c", + "crypto/des/des_old2.c", + "crypto/des/cfb_enc.c", + "crypto/des/ecb_enc.c", + "crypto/des/enc_read.c", + "crypto/des/des_old.c", + "crypto/des/ofb64enc.c", + "crypto/des/pcbc_enc.c", + "crypto/des/cbc_enc.c", + "crypto/des/cfb64enc.c", + "crypto/lhash/lh_stats.c", + "crypto/lhash/lhash.c", + "crypto/x509v3/v3_genn.c", + "crypto/x509v3/pcy_cache.c", + "crypto/x509v3/v3_sxnet.c", + "crypto/x509v3/v3_scts.c", + "crypto/x509v3/v3err.c", + "crypto/x509v3/v3_conf.c", + "crypto/x509v3/v3_utl.c", + "crypto/x509v3/v3_akeya.c", + "crypto/x509v3/v3_lib.c", + "crypto/x509v3/pcy_lib.c", + "crypto/x509v3/v3_cpols.c", + "crypto/x509v3/v3_ia5.c", + "crypto/x509v3/v3_bitst.c", + "crypto/x509v3/v3_skey.c", + "crypto/x509v3/v3_info.c", + "crypto/x509v3/v3_asid.c", + "crypto/x509v3/pcy_tree.c", + "crypto/x509v3/v3_pcons.c", + "crypto/x509v3/v3_bcons.c", + "crypto/x509v3/v3_pku.c", + "crypto/x509v3/v3_ocsp.c", + "crypto/x509v3/pcy_map.c", + "crypto/x509v3/v3_ncons.c", + "crypto/x509v3/v3_purp.c", + "crypto/x509v3/v3_enum.c", + "crypto/x509v3/v3_pmaps.c", + "crypto/x509v3/pcy_node.c", + "crypto/x509v3/v3_pcia.c", + "crypto/x509v3/v3_crld.c", + "crypto/x509v3/v3_pci.c", + "crypto/x509v3/v3_akey.c", + "crypto/x509v3/v3_addr.c", + "crypto/x509v3/v3_int.c", + "crypto/x509v3/v3_alt.c", + "crypto/x509v3/v3_extku.c", + "crypto/x509v3/v3_prn.c", + "crypto/x509v3/pcy_data.c", + "crypto/aes/aes_ofb.c", + "crypto/aes/aes_ctr.c", + "crypto/aes/aes_ecb.c", + "crypto/aes/aes_cfb.c", + "crypto/aes/aes_wrap.c", + "crypto/aes/aes_ige.c", + "crypto/aes/aes_misc.c", + "crypto/pqueue/pqueue.c", + "crypto/sha/sha_one.c", + "crypto/sha/sha_dgst.c", + "crypto/sha/sha512.c", + "crypto/sha/sha1_one.c", + "crypto/sha/sha1dgst.c", + "crypto/sha/sha256.c", + "crypto/whrlpool/wp_dgst.c", + "crypto/objects/obj_xref.c", + "crypto/objects/o_names.c", + "crypto/objects/obj_err.c", + "crypto/objects/obj_dat.c", + "crypto/objects/obj_lib.c", + "crypto/mem.c", + "crypto/fips_ers.c", + "crypto/o_fips.c", + "crypto/engine/eng_rdrand.c", + "crypto/engine/eng_err.c", + "crypto/engine/tb_ecdsa.c", + "crypto/engine/tb_rsa.c", + "crypto/engine/tb_cipher.c", + "crypto/engine/tb_dsa.c", + "crypto/engine/eng_lib.c", + "crypto/engine/tb_asnmth.c", + "crypto/engine/tb_ecdh.c", + "crypto/engine/tb_dh.c", + "crypto/engine/tb_store.c", + "crypto/engine/eng_init.c", + "crypto/engine/eng_cnf.c", + "crypto/engine/eng_all.c", + "crypto/engine/tb_digest.c", + "crypto/engine/tb_pkmeth.c", + "crypto/engine/eng_table.c", + "crypto/engine/eng_ctrl.c", + "crypto/engine/eng_list.c", + "crypto/engine/eng_cryptodev.c", + "crypto/engine/eng_pkey.c", + "crypto/engine/tb_rand.c", + "crypto/engine/eng_openssl.c", + "crypto/engine/eng_fat.c", + "crypto/engine/eng_dyn.c", + "crypto/ts/ts_rsp_verify.c", + "crypto/ts/ts_req_print.c", + "crypto/ts/ts_verify_ctx.c", + "crypto/ts/ts_req_utils.c", + "crypto/ts/ts_err.c", + "crypto/ts/ts_rsp_print.c", + "crypto/ts/ts_rsp_utils.c", + "crypto/ts/ts_lib.c", + "crypto/ts/ts_conf.c", + "crypto/ts/ts_asn1.c", + "crypto/ts/ts_rsp_sign.c", + "crypto/ocsp/ocsp_ext.c", + "crypto/ocsp/ocsp_cl.c", + "crypto/ocsp/ocsp_ht.c", + "crypto/ocsp/ocsp_lib.c", + "crypto/ocsp/ocsp_srv.c", + "crypto/ocsp/ocsp_vfy.c", + "crypto/ocsp/ocsp_err.c", + "crypto/ocsp/ocsp_prn.c", + "crypto/ocsp/ocsp_asn.c", + "crypto/bf/bf_cfb64.c", + "crypto/bf/bf_ecb.c", + "crypto/bf/bf_enc.c", + "crypto/bf/bf_skey.c", + "crypto/bf/bf_ofb64.c", + "crypto/idea/i_skey.c", + "crypto/idea/i_ofb64.c", + "crypto/idea/i_cbc.c", + "crypto/idea/i_ecb.c", + "crypto/idea/i_cfb64.c", + "crypto/cmac/cm_ameth.c", + "crypto/cmac/cmac.c", + "crypto/cmac/cm_pmeth.c", + "crypto/dh/dh_lib.c", + "crypto/dh/dh_key.c", + "crypto/dh/dh_asn1.c", + "crypto/dh/dh_depr.c", + "crypto/dh/dh_pmeth.c", + "crypto/dh/dh_prn.c", + "crypto/dh/dh_gen.c", + "crypto/dh/dh_ameth.c", + "crypto/dh/dh_check.c", + "crypto/dh/dh_err.c", + "crypto/dh/dh_kdf.c", + "crypto/dh/dh_rfc5114.c", + "crypto/modes/ccm128.c", + "crypto/modes/ofb128.c", + "crypto/modes/cts128.c", + "crypto/modes/ctr128.c", + "crypto/modes/gcm128.c", + "crypto/modes/cbc128.c", + "crypto/modes/cfb128.c", + "crypto/modes/xts128.c", + "crypto/modes/wrap128.c", + "crypto/camellia/cmll_cfb.c", + "crypto/camellia/cmll_ecb.c", + "crypto/camellia/cmll_utl.c", + "crypto/camellia/cmll_misc.c", + "crypto/camellia/cmll_ofb.c", + "crypto/camellia/cmll_ctr.c", + "crypto/seed/seed_ecb.c", + "crypto/seed/seed_cbc.c", + "crypto/seed/seed.c", + "crypto/seed/seed_ofb.c", + "crypto/seed/seed_cfb.c", + "crypto/txt_db/txt_db.c", + "crypto/cpt_err.c", + "crypto/pem/pem_pk8.c", + "crypto/pem/pem_lib.c", + "crypto/pem/pem_sign.c", + "crypto/pem/pem_all.c", + "crypto/pem/pem_info.c", + "crypto/pem/pem_pkey.c", + "crypto/pem/pem_seal.c", + "crypto/pem/pem_err.c", + "crypto/pem/pem_xaux.c", + "crypto/pem/pvkfmt.c", + "crypto/pem/pem_x509.c", + "crypto/pem/pem_oth.c", + "crypto/rand/rand_lib.c", + "crypto/rand/randfile.c", + "crypto/rand/rand_os2.c", + "crypto/rand/rand_unix.c", + "crypto/rand/rand_nw.c", + "crypto/rand/md_rand.c", + "crypto/rand/rand_err.c", + "crypto/rand/rand_win.c", + "crypto/rand/rand_egd.c", + "crypto/cversion.c", + "crypto/cast/c_ecb.c", + "crypto/cast/c_skey.c", + "crypto/cast/c_ofb64.c", + "crypto/cast/c_enc.c", + "crypto/cast/c_cfb64.c", + "crypto/o_time.c", + "crypto/mdc2/mdc2dgst.c", + "crypto/mdc2/mdc2_one.c", + "crypto/rc4/rc4_utl.c", + "crypto/ui/ui_compat.c", + "crypto/ui/ui_util.c", + "crypto/ui/ui_lib.c", + "crypto/ui/ui_err.c", + "crypto/ui/ui_openssl.c", + "crypto/bio/bf_buff.c", + "crypto/bio/bss_null.c", + "crypto/bio/bss_acpt.c", + "crypto/bio/bss_conn.c", + "crypto/bio/bss_fd.c", + "crypto/bio/bf_null.c", + "crypto/bio/bio_err.c", + "crypto/bio/bss_sock.c", + "crypto/bio/bss_mem.c", + "crypto/bio/b_dump.c", + "crypto/bio/b_print.c", + "crypto/bio/b_sock.c", + "crypto/bio/bss_dgram.c", + "crypto/bio/bf_nbio.c", + "crypto/bio/bio_lib.c", + "crypto/bio/bss_file.c", + "crypto/bio/bss_bio.c", + "crypto/bio/bss_log.c", + "crypto/bio/bio_cb.c", + "crypto/o_init.c", + "crypto/rc2/rc2_skey.c", + "crypto/rc2/rc2_cbc.c", + "crypto/rc2/rc2cfb64.c", + "crypto/rc2/rc2_ecb.c", + "crypto/rc2/rc2ofb64.c", + "crypto/bn/bn_x931p.c", + "crypto/bn/bn_blind.c", + "crypto/bn/bn_gf2m.c", + "crypto/bn/bn_const.c", + "crypto/bn/bn_sqr.c", + "crypto/bn/bn_nist.c", + "crypto/bn/bn_rand.c", + "crypto/bn/bn_err.c", + "crypto/bn/bn_div.c", + "crypto/bn/bn_kron.c", + "crypto/bn/bn_ctx.c", + "crypto/bn/bn_shift.c", + "crypto/bn/bn_mod.c", + "crypto/bn/bn_exp2.c", + "crypto/bn/bn_word.c", + "crypto/bn/bn_add.c", + "crypto/bn/bn_exp.c", + "crypto/bn/bn_mont.c", + "crypto/bn/bn_print.c", + "crypto/bn/bn_mul.c", + "crypto/bn/bn_prime.c", + "crypto/bn/bn_depr.c", + "crypto/bn/bn_gcd.c", + "crypto/bn/bn_mpi.c", + "crypto/bn/bn_sqrt.c", + "crypto/bn/bn_recp.c", + "crypto/bn/bn_lib.c", + "crypto/ripemd/rmd_dgst.c", + "crypto/ripemd/rmd_one.c", + "crypto/rsa/rsa_x931.c", + "crypto/rsa/rsa_depr.c", + "crypto/rsa/rsa_saos.c", + "crypto/rsa/rsa_crpt.c", + "crypto/rsa/rsa_pss.c", + "crypto/rsa/rsa_oaep.c", + "crypto/rsa/rsa_null.c", + "crypto/rsa/rsa_gen.c", + "crypto/rsa/rsa_prn.c", + "crypto/rsa/rsa_pmeth.c", + "crypto/rsa/rsa_asn1.c", + "crypto/rsa/rsa_ssl.c", + "crypto/rsa/rsa_ameth.c", + "crypto/rsa/rsa_pk1.c", + "crypto/rsa/rsa_err.c", + "crypto/rsa/rsa_lib.c", + "crypto/rsa/rsa_none.c", + "crypto/rsa/rsa_chk.c", + "crypto/rsa/rsa_eay.c", + "crypto/rsa/rsa_sign.c", + "crypto/srp/srp_lib.c", + "crypto/srp/srp_vfy.c", + "crypto/err/err.c", + "crypto/err/err_prn.c", + "crypto/err/err_all.c", + "crypto/mem_clr.c", + "crypto/rc4/rc4_skey.c", + "crypto/rc4/rc4_enc.c", + "crypto/camellia/camellia.c", + "crypto/camellia/cmll_cbc.c", + #"crypto/aes/aes_x86core.c", + "crypto/aes/aes_core.c", + "crypto/aes/aes_cbc.c", + "crypto/whrlpool/wp_block.c", + "crypto/bn/bn_asm.c", + ] + + if "platform" in env and env["platform"] == "uwp": + thirdparty_sources += ['uwp.cpp'] + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_openssl.add_source_files(env.modules_sources, thirdparty_sources) + + # FIXME: Clone the environment to make a env_openssl and not pollute the modules env + thirdparty_include_paths = [ + "", + "crypto", + "crypto/asn1", + "crypto/evp", + "crypto/modes", + "openssl", + ] + env_openssl.Append(CPPPATH=[thirdparty_dir + "/" + dir for dir in thirdparty_include_paths]) + + env_openssl.Append(CPPFLAGS=["-DOPENSSL_NO_ASM", "-DOPENSSL_THREADS", "-DL_ENDIAN"]) + + # Workaround for compilation error with GCC/Clang when -Werror is too greedy (GH-4517) + import os + import methods + if not (os.name == "nt" and os.getenv("VCINSTALLDIR")): # not Windows and not MSVC + env_openssl.Append(CFLAGS=["-Wno-error=implicit-function-declaration"]) + + +# Module sources +env_openssl.add_source_files(env.modules_sources, "*.cpp") +env_openssl.add_source_files(env.modules_sources, "*.c") + +# platform/uwp need to know openssl is available, pass to main env +if "platform" in env and env["platform"] == "uwp": + env.Append(CPPPATH=[thirdparty_dir]) + env.Append(CPPFLAGS=['-DOPENSSL_ENABLED']) + +Export('env') diff --git a/modules/openssl/config.py b/modules/openssl/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/openssl/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/openssl/curl_hostcheck.c b/modules/openssl/curl_hostcheck.c new file mode 100644 index 000000000..feef23261 --- /dev/null +++ b/modules/openssl/curl_hostcheck.c @@ -0,0 +1,217 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* This file is an amalgamation of hostcheck.c and most of rawstr.c + from cURL. The contents of the COPYING file mentioned above are: + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2013, Daniel Stenberg, <daniel@haxx.se>. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. +*/ + +#include "curl_hostcheck.h" +#include <string.h> + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +static char Curl_raw_toupper(char in) +{ + switch (in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +/* + * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * some further explanation to why this function is necessary. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ + +static int Curl_raw_equal(const char *first, const char *second) +{ + while(*first && *second) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if the + loop above is skipped because one of the strings reached zero, we must not + return this as a successful match */ + return (Curl_raw_toupper(*first) == Curl_raw_toupper(*second)); +} + +static int Curl_raw_nequal(const char *first, const char *second, size_t max) +{ + while(*first && *second && max) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) { + break; + } + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); +} + +/* + * Match a hostname against a wildcard pattern. + * E.g. + * "foo.host.com" matches "*.host.com". + * + * We use the matching rule described in RFC6125, section 6.4.3. + * http://tools.ietf.org/html/rfc6125#section-6.4.3 + */ + +static int hostmatch(const char *hostname, const char *pattern) +{ + const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; + int wildcard_enabled; + size_t prefixlen, suffixlen; + pattern_wildcard = strchr(pattern, '*'); + if(pattern_wildcard == NULL) + return Curl_raw_equal(pattern, hostname) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; + + /* We require at least 2 dots in pattern to avoid too wide wildcard + match. */ + wildcard_enabled = 1; + pattern_label_end = strchr(pattern, '.'); + if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL || + pattern_wildcard > pattern_label_end || + Curl_raw_nequal(pattern, "xn--", 4)) { + wildcard_enabled = 0; + } + if(!wildcard_enabled) + return Curl_raw_equal(pattern, hostname) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; + + hostname_label_end = strchr(hostname, '.'); + if(hostname_label_end == NULL || + !Curl_raw_equal(pattern_label_end, hostname_label_end)) + return CURL_HOST_NOMATCH; + + /* The wildcard must match at least one character, so the left-most + label of the hostname is at least as large as the left-most label + of the pattern. */ + if(hostname_label_end - hostname < pattern_label_end - pattern) + return CURL_HOST_NOMATCH; + + prefixlen = pattern_wildcard - pattern; + suffixlen = pattern_label_end - (pattern_wildcard+1); + return Curl_raw_nequal(pattern, hostname, prefixlen) && + Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen, + suffixlen) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; +} + +int Tool_Curl_cert_hostcheck(const char *match_pattern, const char *hostname) +{ + if(!match_pattern || !*match_pattern || + !hostname || !*hostname) /* sanity check */ + return 0; + + if(Curl_raw_equal(hostname, match_pattern)) /* trivial case */ + return 1; + + if(hostmatch(hostname,match_pattern) == CURL_HOST_MATCH) + return 1; + return 0; +} diff --git a/modules/openssl/curl_hostcheck.h b/modules/openssl/curl_hostcheck.h new file mode 100644 index 000000000..1b7fbe81e --- /dev/null +++ b/modules/openssl/curl_hostcheck.h @@ -0,0 +1,39 @@ +#ifndef HEADER_TOOL_CURL_HOSTCHECK_H +#define HEADER_TOOL_CURL_HOSTCHECK_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#define CURL_HOST_NOMATCH 0 +#define CURL_HOST_MATCH 1 +int Tool_Curl_cert_hostcheck(const char *match_pattern, const char *hostname); + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_CURL_HOSTCHECK_H */ + diff --git a/modules/openssl/register_types.cpp b/modules/openssl/register_types.cpp new file mode 100644 index 000000000..6cc9fa366 --- /dev/null +++ b/modules/openssl/register_types.cpp @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "stream_peer_openssl.h" + +void register_openssl_types() { + + ClassDB::register_class<StreamPeerOpenSSL>(); + StreamPeerOpenSSL::initialize_ssl(); +} + +void unregister_openssl_types() { + + StreamPeerOpenSSL::finalize_ssl(); +} diff --git a/modules/openssl/register_types.h b/modules/openssl/register_types.h new file mode 100644 index 000000000..3bcee59bf --- /dev/null +++ b/modules/openssl/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_openssl_types(); +void unregister_openssl_types(); diff --git a/modules/openssl/stream_peer_openssl.cpp b/modules/openssl/stream_peer_openssl.cpp new file mode 100644 index 000000000..43a1f610d --- /dev/null +++ b/modules/openssl/stream_peer_openssl.cpp @@ -0,0 +1,646 @@ +/*************************************************************************/ +/* stream_peer_openssl.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "stream_peer_openssl.h" +//hostname matching code from curl + + +//#include <openssl/applink.c> // To prevent crashing (see the OpenSSL FAQ) + +bool StreamPeerOpenSSL::_match_host_name(const char *name, const char *hostname) { + + return Tool_Curl_cert_hostcheck(name,hostname)==CURL_HOST_MATCH; + //print_line("MATCH: "+String(name)+" vs "+String(hostname)); + //return true; +} + +Error StreamPeerOpenSSL::_match_common_name(const char *hostname, const X509 *server_cert) { + + int common_name_loc = -1; + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + char *common_name_str = NULL; + + // Find the position of the CN field in the Subject field of the certificate + common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1); + + ERR_FAIL_COND_V(common_name_loc < 0, ERR_INVALID_PARAMETER ); + + // Extract the CN field + common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc); + + ERR_FAIL_COND_V(common_name_entry == NULL, ERR_INVALID_PARAMETER ); + + // Convert the CN field to a C string + common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + + ERR_FAIL_COND_V(common_name_asn1 == NULL, ERR_INVALID_PARAMETER ); + + common_name_str = (char *) ASN1_STRING_data(common_name_asn1); + + // Make sure there isn't an embedded NUL character in the CN + bool malformed_certificate = (size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str); + + ERR_FAIL_COND_V(malformed_certificate, ERR_INVALID_PARAMETER ); + + + // Compare expected hostname with the CN + + + return _match_host_name(common_name_str,hostname)?OK:FAILED; + +} + + +/** +* Tries to find a match for hostname in the certificate's Subject Alternative Name extension. +* +*/ + +Error StreamPeerOpenSSL::_match_subject_alternative_name(const char *hostname, const X509 *server_cert) { + + Error result = FAILED; + int i; + int san_names_nb = -1; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + // Try to extract the names within the SAN extension from the certificate + san_names = (STACK_OF(GENERAL_NAME) *)X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL); + if (san_names == NULL) { + return ERR_FILE_NOT_FOUND; + } + san_names_nb = sk_GENERAL_NAME_num(san_names); + + // Check each name within the extension + for (i=0; i<san_names_nb; i++) { + const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i); + + if (current_name->type == GEN_DNS) { + // Current name is a DNS name, let's check it + char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); + + // Make sure there isn't an embedded NUL character in the DNS name + if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { + result = ERR_INVALID_PARAMETER; + break; + } + else { // Compare expected hostname with the DNS name + if (_match_host_name(dns_name, hostname)) { + result = OK; + break; + } + } + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + return result; +} + +/* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */ +int StreamPeerOpenSSL::_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg) { + + /* This is the function that OpenSSL would call if we hadn't called + * SSL_CTX_set_cert_verify_callback(). Therefore, we are "wrapping" + * the default functionality, rather than replacing it. */ + + bool base_cert_valid = X509_verify_cert(x509_ctx); + if (!base_cert_valid) { + print_line("Cause: "+String(X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx)))); + ERR_print_errors_fp(stdout); + } + X509 *server_cert = X509_STORE_CTX_get_current_cert(x509_ctx); + + ERR_FAIL_COND_V(!server_cert,0); + + char cert_str[256]; + X509_NAME_oneline(X509_get_subject_name (server_cert), + cert_str, sizeof (cert_str)); + + print_line("CERT STR: "+String(cert_str)); + print_line("VALID: "+itos(base_cert_valid)); + + if (!base_cert_valid) + return 0; + + StreamPeerOpenSSL *ssl = (StreamPeerOpenSSL *)arg; + + if (ssl->validate_hostname) { + + Error err = _match_subject_alternative_name(ssl->hostname.utf8().get_data(),server_cert); + + if (err==ERR_FILE_NOT_FOUND) { + + err = _match_common_name(ssl->hostname.utf8().get_data(),server_cert); + } + + if (err!=OK) { + + ssl->status=STATUS_ERROR_HOSTNAME_MISMATCH; + return 0; + } + } + + return 1; + +} + + + +int StreamPeerOpenSSL::_bio_create( BIO *b ) { + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + return 1; +} + +int StreamPeerOpenSSL::_bio_destroy( BIO *b ) +{ + if ( b == NULL ) + return 0; + + b->ptr = NULL; /* sb_tls_remove() will free it */ + b->init = 0; + b->flags = 0; + return 1; +} + +int StreamPeerOpenSSL::_bio_read( BIO *b, char *buf, int len ) { + + + if ( buf == NULL || len <= 0 ) return 0; + + StreamPeerOpenSSL *sp = (StreamPeerOpenSSL *)b->ptr; + + ERR_FAIL_COND_V( sp == NULL, 0); + + BIO_clear_retry_flags( b ); + if (sp->use_blocking) { + + Error err = sp->base->get_data((uint8_t*)buf,len); + if (err!=OK) { + return -1; + } + + return len; + } else { + + int got; + Error err = sp->base->get_partial_data((uint8_t*)buf,len,got); + if (err!=OK) { + return -1; + } + if (got==0) { + BIO_set_retry_read( b ); + } + return got; + } + + //unreachable + return 0; +} + +int StreamPeerOpenSSL::_bio_write( BIO *b, const char *buf, int len ) { + + if ( buf == NULL || len <= 0 ) return 0; + + StreamPeerOpenSSL *sp = (StreamPeerOpenSSL *)b->ptr; + + ERR_FAIL_COND_V( sp == NULL, 0); + + BIO_clear_retry_flags( b ); + if (sp->use_blocking) { + + Error err = sp->base->put_data((const uint8_t*)buf,len); + if (err!=OK) { + return -1; + } + + return len; + } else { + + int sent; + Error err = sp->base->put_partial_data((const uint8_t*)buf,len,sent); + if (err!=OK) { + return -1; + } + if (sent==0) { + BIO_set_retry_write( b ); + } + return sent; + + } + + //unreachable + return 0; +} + +long StreamPeerOpenSSL::_bio_ctrl( BIO *b, int cmd, long num, void *ptr ) +{ + if ( cmd == BIO_CTRL_FLUSH ) { + /* The OpenSSL library needs this */ + return 1; + } + return 0; +} + +int StreamPeerOpenSSL::_bio_gets( BIO *b, char *buf, int len ) +{ + return -1; +} + +int StreamPeerOpenSSL::_bio_puts( BIO *b, const char *str ) +{ + return _bio_write( b, str, strlen( str ) ); +} + +BIO_METHOD StreamPeerOpenSSL::_bio_method = { + /* it's a source/sink BIO */ + ( 100 | 0x400 ), + "streampeer glue", + _bio_write, + _bio_read, + _bio_puts, + _bio_gets, + _bio_ctrl, + _bio_create, + _bio_destroy +}; + +Error StreamPeerOpenSSL::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String& p_for_hostname) { + + if (connected) + disconnect_from_stream(); + + + hostname=p_for_hostname; + status=STATUS_DISCONNECTED; + + // Set up a SSL_CTX object, which will tell our BIO object how to do its work + ctx = SSL_CTX_new(SSLv23_client_method()); + base=p_base; + validate_certs=p_validate_certs; + validate_hostname=p_for_hostname!=""; + + + + + if (p_validate_certs) { + + + if (certs.size()) { + //yay for undocumented OpenSSL functions + + X509_STORE *store = SSL_CTX_get_cert_store(ctx); + for(int i=0;i<certs.size();i++) { + + X509_STORE_add_cert(store,certs[i]); + + } +#if 0 + const unsigned char *in=(const unsigned char *)certs.ptr(); + X509 *Cert = d2i_X509(NULL, &in, certs.size()-1); + if (!Cert) { + print_line(String(ERR_error_string(ERR_get_error(),NULL))); + } + ERR_FAIL_COND_V(!Cert,ERR_PARSE_ERROR); + + X509_STORE *store = SSL_CTX_get_cert_store(ctx); + X509_STORE_add_cert(store,Cert); + + //char *str = X509_NAME_oneline(X509_get_subject_name(Cert),0,0); + //printf ("subject: %s\n", str); /* [1] */ +#endif + } + + //used for testing + //int res = SSL_CTX_load_verify_locations(ctx,"/etc/ssl/certs/ca-certificates.crt",NULL); + //print_line("verify locations res: "+itos(res)); + + + /* Ask OpenSSL to verify the server certificate. Note that this + * does NOT include verifying that the hostname is correct. + * So, by itself, this means anyone with any legitimate + * CA-issued certificate for any website, can impersonate any + * other website in the world. This is not good. See "The + * Most Dangerous Code in the World" article at + * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html + */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + /* This is how we solve the problem mentioned in the previous + * comment. We "wrap" OpenSSL's validation routine in our + * own routine, which also validates the hostname by calling + * the code provided by iSECPartners. Note that even though + * the "Everything You've Always Wanted to Know About + * Certificate Validation With OpenSSL (But Were Afraid to + * Ask)" paper from iSECPartners says very explicitly not to + * call SSL_CTX_set_cert_verify_callback (at the bottom of + * page 2), what we're doing here is safe because our + * cert_verify_callback() calls X509_verify_cert(), which is + * OpenSSL's built-in routine which would have been called if + * we hadn't set the callback. Therefore, we're just + * "wrapping" OpenSSL's routine, not replacing it. */ + SSL_CTX_set_cert_verify_callback (ctx, _cert_verify_callback,this); + + //Let the verify_callback catch the verify_depth error so that we get an appropriate error in the logfile. (??) + SSL_CTX_set_verify_depth(ctx,max_cert_chain_depth + 1); + + } + + + + + + ssl = SSL_new( ctx ); + bio = BIO_new( &_bio_method ); + bio->ptr = this; + SSL_set_bio( ssl, bio, bio ); + + if (p_for_hostname!=String()) { + SSL_set_tlsext_host_name(ssl,p_for_hostname.utf8().get_data()); + } + + use_blocking=true; // let handshake use blocking + // Set the SSL to automatically retry on failure. + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + + // Same as before, try to connect. + int result = SSL_connect( ssl ); + + print_line("CONNECTION RESULT: "+itos(result)); + if (result<1) { + ERR_print_errors_fp(stdout); + _print_error(result); + } + + X509 * peer = SSL_get_peer_certificate(ssl); + + if (peer) { + bool cert_ok = SSL_get_verify_result(ssl) == X509_V_OK; + print_line("cert_ok: "+itos(cert_ok)); + + } else if (validate_certs){ + status=STATUS_ERROR_NO_CERTIFICATE; + } + + connected=true; + status=STATUS_CONNECTED; + + return OK; +} + +Error StreamPeerOpenSSL::accept_stream(Ref<StreamPeer> p_base) { + + + return ERR_UNAVAILABLE; +} + +void StreamPeerOpenSSL::_print_error(int err) { + + err = SSL_get_error(ssl,err); + switch(err) { + case SSL_ERROR_NONE: ERR_PRINT("NO ERROR: The TLS/SSL I/O operation completed"); break; + case SSL_ERROR_ZERO_RETURN: ERR_PRINT("The TLS/SSL connection has been closed."); + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + ERR_PRINT("The operation did not complete."); break; + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + ERR_PRINT("The connect/accept operation did not complete"); break; + case SSL_ERROR_WANT_X509_LOOKUP: + ERR_PRINT("The operation did not complete because an application callback set by SSL_CTX_set_client_cert_cb() has asked to be called again."); break; + case SSL_ERROR_SYSCALL: + ERR_PRINT("Some I/O error occurred. The OpenSSL error queue may contain more information on the error."); break; + case SSL_ERROR_SSL: + ERR_PRINT("A failure in the SSL library occurred, usually a protocol error."); break; + + } +} + +Error StreamPeerOpenSSL::put_data(const uint8_t* p_data,int p_bytes) { + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + + while(p_bytes>0) { + int ret = SSL_write(ssl,p_data,p_bytes); + if (ret<=0) { + _print_error(ret); + disconnect_from_stream(); + return ERR_CONNECTION_ERROR; + } + p_data+=ret; + p_bytes-=ret; + } + + return OK; + +} + +Error StreamPeerOpenSSL::put_partial_data(const uint8_t* p_data,int p_bytes, int &r_sent){ + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + if (p_bytes==0) + return OK; + + Error err = put_data(p_data,p_bytes); + if (err!=OK) + return err; + + r_sent=p_bytes; + return OK; + +} + +Error StreamPeerOpenSSL::get_data(uint8_t* p_buffer, int p_bytes){ + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + + while(p_bytes>0) { + + int ret = SSL_read(ssl,p_buffer,p_bytes); + if (ret<=0) { + _print_error(ret); + disconnect_from_stream(); + return ERR_CONNECTION_ERROR; + } + p_buffer+=ret; + p_bytes-=ret; + } + + return OK; +} + +Error StreamPeerOpenSSL::get_partial_data(uint8_t* p_buffer, int p_bytes,int &r_received){ + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + if (p_bytes==0) { + r_received=0; + return OK; + } + + Error err = get_data(p_buffer,p_bytes); + if (err!=OK) + return err; + r_received=p_bytes; + return OK; +} + +int StreamPeerOpenSSL::get_available_bytes() const { + + ERR_FAIL_COND_V(!connected,0); + + return SSL_pending(ssl); + +} +StreamPeerOpenSSL::StreamPeerOpenSSL() { + + ctx=NULL; + ssl=NULL; + bio=NULL; + connected=false; + use_blocking=true; //might be improved int the future, but for now it always blocks + max_cert_chain_depth=9; + flags=0; +} + +void StreamPeerOpenSSL::disconnect_from_stream() { + + if (!connected) + return; + SSL_shutdown( ssl ); + SSL_free( ssl ); + SSL_CTX_free(ctx); + base=Ref<StreamPeer>(); + connected=false; + validate_certs=false; + validate_hostname=false; + status=STATUS_DISCONNECTED; + + +} + +StreamPeerOpenSSL::Status StreamPeerOpenSSL::get_status() const { + + return status; +} + + +StreamPeerOpenSSL::~StreamPeerOpenSSL() { + disconnect_from_stream(); +} + +StreamPeerSSL* StreamPeerOpenSSL::_create_func() { + + return memnew( StreamPeerOpenSSL ); +} + + +Vector<X509*> StreamPeerOpenSSL::certs; + + +void StreamPeerOpenSSL::_load_certs(const PoolByteArray& p_array) { + + PoolByteArray::Read r = p_array.read(); + BIO* mem = BIO_new(BIO_s_mem()); + BIO_puts(mem,(const char*)r.ptr()); + while(true) { + X509*cert = PEM_read_bio_X509(mem, NULL, 0, NULL); + if (!cert) + break; + certs.push_back(cert); + } + BIO_free(mem); +} + +void StreamPeerOpenSSL::initialize_ssl() { + + available=true; + + load_certs_func=_load_certs; + + _create=_create_func; + CRYPTO_malloc_init(); // Initialize malloc, free, etc for OpenSSL's use + SSL_library_init(); // Initialize OpenSSL's SSL libraries + SSL_load_error_strings(); // Load SSL error strings + ERR_load_BIO_strings(); // Load BIO error strings + OpenSSL_add_all_algorithms(); // Load all available encryption algorithms + String certs_path =GLOBAL_DEF("network/ssl/certificates",""); + GlobalConfig::get_singleton()->set_custom_property_info("network/ssl/certificates",PropertyInfo(Variant::STRING,"network/ssl/certificates",PROPERTY_HINT_FILE,"*.crt")); + if (certs_path!="") { + + + + FileAccess *f=FileAccess::open(certs_path,FileAccess::READ); + if (f) { + PoolByteArray arr; + int flen = f->get_len(); + arr.resize(flen+1); + { + PoolByteArray::Write w = arr.write(); + f->get_buffer(w.ptr(),flen); + w[flen]=0; //end f string + } + + memdelete(f); + + _load_certs(arr); + print_line("Loaded certs from '"+certs_path+"': "+itos(certs.size())); + } + } + String config_path =GLOBAL_DEF("network/ssl/config",""); + GlobalConfig::get_singleton()->set_custom_property_info("network/ssl/config",PropertyInfo(Variant::STRING,"network/ssl/config",PROPERTY_HINT_FILE,"*.cnf")); + if (config_path!="") { + + Vector<uint8_t> data = FileAccess::get_file_as_array(config_path); + if (data.size()) { + data.push_back(0); + BIO* mem = BIO_new(BIO_s_mem()); + BIO_puts(mem,(const char*) data.ptr()); + + while(true) { + X509*cert = PEM_read_bio_X509(mem, NULL, 0, NULL); + if (!cert) + break; + certs.push_back(cert); + } + BIO_free(mem); + } + print_line("Loaded certs from '"+certs_path+"': "+itos(certs.size())); + + } + +} + +void StreamPeerOpenSSL::finalize_ssl(){ + + for(int i=0;i<certs.size();i++) { + X509_free(certs[i]); + } + certs.clear(); +} diff --git a/modules/openssl/stream_peer_openssl.h b/modules/openssl/stream_peer_openssl.h new file mode 100644 index 000000000..3d6875698 --- /dev/null +++ b/modules/openssl/stream_peer_openssl.h @@ -0,0 +1,109 @@ +/*************************************************************************/ +/* stream_peer_openssl.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef STREAM_PEER_OPEN_SSL_H +#define STREAM_PEER_OPEN_SSL_H + +#include <stdio.h> // If you don't know what this is for stop reading now. +#include "io/stream_peer_ssl.h" +#include "globals.h" +#include "os/file_access.h" +#include "curl_hostcheck.h" + +#include <openssl/bio.h> // BIO objects for I/O +#include <openssl/ssl.h> // SSL and SSL_CTX for SSL connections +#include <openssl/err.h> // Error reporting +#include <openssl/x509v3.h> + +class StreamPeerOpenSSL : public StreamPeerSSL { +private: + static int _bio_create( BIO *b ); + static int _bio_destroy( BIO *b ); + static int _bio_read( BIO *b, char *buf, int len ); + static int _bio_write( BIO *b, const char *buf, int len ); + static long _bio_ctrl( BIO *b, int cmd, long num, void *ptr ); + static int _bio_gets( BIO *b, char *buf, int len ); + static int _bio_puts( BIO *b, const char *str ); + + static BIO_METHOD _bio_method; + + static bool _match_host_name(const char *name, const char *hostname); + static Error _match_common_name(const char *hostname, const X509 *server_cert); + static Error _match_subject_alternative_name(const char *hostname, const X509 *server_cert); + + + static int _cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg); + + + Status status; + String hostname; + int max_cert_chain_depth; + SSL_CTX* ctx; + SSL* ssl; + BIO* bio; + bool connected; + int flags; + bool use_blocking; + bool validate_certs; + bool validate_hostname; + + Ref<StreamPeer> base; + + static StreamPeerSSL* _create_func(); + void _print_error(int err); + + static Vector<X509*> certs; + + static void _load_certs(const PoolByteArray& p_array); +protected: + static void _bind_methods(); +public: + + + virtual Error accept_stream(Ref<StreamPeer> p_base); + virtual Error connect_to_stream(Ref<StreamPeer> p_base,bool p_validate_certs=false,const String& p_for_hostname=String()); + virtual Status get_status() const; + + virtual void disconnect_from_stream(); + + virtual Error put_data(const uint8_t* p_data,int p_bytes); + virtual Error put_partial_data(const uint8_t* p_data,int p_bytes, int &r_sent); + + virtual Error get_data(uint8_t* p_buffer, int p_bytes); + virtual Error get_partial_data(uint8_t* p_buffer, int p_bytes,int &r_received); + + virtual int get_available_bytes() const; + + static void initialize_ssl(); + static void finalize_ssl(); + + StreamPeerOpenSSL(); + ~StreamPeerOpenSSL(); +}; + +#endif // STREAM_PEER_SSL_H diff --git a/modules/opus/SCsub b/modules/opus/SCsub new file mode 100644 index 000000000..4d3053c7e --- /dev/null +++ b/modules/opus/SCsub @@ -0,0 +1,216 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_opus = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_opus'] != 'no'): + thirdparty_dir = "#thirdparty/opus/" + + thirdparty_sources = [ + "silk/tables_other.c", + "silk/sum_sqr_shift.c", + "silk/PLC.c", + "silk/dec_API.c", + "silk/decode_pulses.c", + "silk/inner_prod_aligned.c", + "silk/init_encoder.c", + "silk/interpolate.c", + "silk/stereo_encode_pred.c", + "silk/decode_frame.c", + "silk/NLSF_del_dec_quant.c", + "silk/VAD.c", + "silk/resampler_private_AR2.c", + "silk/NLSF_unpack.c", + "silk/resampler_down2.c", + "silk/sort.c", + "silk/resampler_private_IIR_FIR.c", + "silk/resampler_down2_3.c", + "silk/resampler_private_up2_HQ.c", + "silk/tables_gain.c", + "silk/stereo_find_predictor.c", + "silk/stereo_quant_pred.c", + "silk/NLSF_stabilize.c", + "silk/ana_filt_bank_1.c", + "silk/check_control_input.c", + "silk/bwexpander.c", + "silk/A2NLSF.c", + "silk/LPC_inv_pred_gain.c", + "silk/log2lin.c", + "silk/process_NLSFs.c", + "silk/sigm_Q15.c", + "silk/VQ_WMat_EC.c", + "silk/quant_LTP_gains.c", + "silk/resampler_private_down_FIR.c", + "silk/NLSF_decode.c", + "silk/control_codec.c", + "silk/NLSF_VQ_weights_laroia.c", + "silk/decode_pitch.c", + "silk/stereo_decode_pred.c", + "silk/tables_pulses_per_block.c", + "silk/init_decoder.c", + "silk/table_LSF_cos.c", + "silk/decode_core.c", + "silk/code_signs.c", + "silk/enc_API.c", + "silk/tables_LTP.c", + "silk/pitch_est_tables.c", + "silk/biquad_alt.c", + "silk/encode_indices.c", + "silk/tables_NLSF_CB_WB.c", + "silk/debug.c", + "silk/decode_parameters.c", + "silk/tables_pitch_lag.c", + "silk/NLSF2A.c", + "silk/resampler.c", + "silk/decode_indices.c", + "silk/NLSF_VQ.c", + "silk/bwexpander_32.c", + "silk/tables_NLSF_CB_NB_MB.c", + "silk/encode_pulses.c", + "silk/NSQ_del_dec.c", + "silk/control_SNR.c", + "silk/shell_coder.c", + "silk/NLSF_encode.c", + "silk/stereo_MS_to_LR.c", + "silk/stereo_LR_to_MS.c", + "silk/HP_variable_cutoff.c", + "silk/LPC_analysis_filter.c", + "silk/CNG.c", + "silk/decoder_set_fs.c", + "silk/resampler_rom.c", + "silk/control_audio_bandwidth.c", + "silk/lin2log.c", + "silk/LP_variable_cutoff.c", + "silk/NSQ.c", + "silk/gain_quant.c", + "celt/laplace.c", + "celt/vq.c", + "celt/quant_bands.c", + "celt/kiss_fft.c", + "celt/entcode.c", + "celt/entenc.c", + "celt/celt_lpc.c", + "celt/pitch.c", + "celt/rate.c", + "celt/mathops.c", + #"celt/arm/armcpu.c", + #"celt/arm/celt_neon_intr.c", + #"celt/arm/celt_ne10_mdct.c", + #"celt/arm/celt_ne10_fft.c", + #"celt/arm/arm_celt_map.c", + "celt/celt_encoder.c", + "celt/celt.c", + "celt/bands.c", + "celt/cwrs.c", + "celt/entdec.c", + "celt/celt_decoder.c", + "celt/mdct.c", + "celt/modes.c", + "repacketizer.c", + "mlp_data.c", + "opus_multistream.c", + "opusfile.c", + "opus_encoder.c", + "analysis.c", + "mlp.c", + "info.c", + "stream.c", + "opus_decoder.c", + "internal.c", + "wincerts.c", + "opus.c", + "opus_multistream_encoder.c", + "http.c", + "opus_multistream_decoder.c" + ] + + opus_sources_silk = [] + + if("opus_fixed_point" in env and env.opus_fixed_point == "yes"): + env_opus.Append(CFLAGS=["-DFIXED_POINT"]) + opus_sources_silk = [ + "silk/fixed/schur64_FIX.c", + "silk/fixed/residual_energy16_FIX.c", + "silk/fixed/encode_frame_FIX.c", + "silk/fixed/regularize_correlations_FIX.c", + "silk/fixed/apply_sine_window_FIX.c", + "silk/fixed/solve_LS_FIX.c", + "silk/fixed/schur_FIX.c", + "silk/fixed/pitch_analysis_core_FIX.c", + "silk/fixed/noise_shape_analysis_FIX.c", + "silk/fixed/find_LTP_FIX.c", + "silk/fixed/vector_ops_FIX.c", + "silk/fixed/autocorr_FIX.c", + "silk/fixed/warped_autocorrelation_FIX.c", + "silk/fixed/find_pitch_lags_FIX.c", + "silk/fixed/k2a_Q16_FIX.c", + "silk/fixed/LTP_scale_ctrl_FIX.c", + "silk/fixed/corrMatrix_FIX.c", + "silk/fixed/prefilter_FIX.c", + "silk/fixed/find_LPC_FIX.c", + "silk/fixed/residual_energy_FIX.c", + "silk/fixed/process_gains_FIX.c", + "silk/fixed/LTP_analysis_filter_FIX.c", + "silk/fixed/k2a_FIX.c", + "silk/fixed/burg_modified_FIX.c", + "silk/fixed/find_pred_coefs_FIX.c" + ] + else: + opus_sources_silk = [ + "silk/float/LTP_scale_ctrl_FLP.c", + "silk/float/regularize_correlations_FLP.c", + "silk/float/corrMatrix_FLP.c", + "silk/float/LPC_analysis_filter_FLP.c", + "silk/float/levinsondurbin_FLP.c", + "silk/float/schur_FLP.c", + "silk/float/scale_vector_FLP.c", + "silk/float/apply_sine_window_FLP.c", + "silk/float/pitch_analysis_core_FLP.c", + "silk/float/wrappers_FLP.c", + "silk/float/bwexpander_FLP.c", + "silk/float/warped_autocorrelation_FLP.c", + "silk/float/solve_LS_FLP.c", + "silk/float/find_LPC_FLP.c", + "silk/float/autocorrelation_FLP.c", + "silk/float/find_pred_coefs_FLP.c", + "silk/float/find_pitch_lags_FLP.c", + "silk/float/burg_modified_FLP.c", + "silk/float/find_LTP_FLP.c", + "silk/float/energy_FLP.c", + "silk/float/sort_FLP.c", + "silk/float/LPC_inv_pred_gain_FLP.c", + "silk/float/k2a_FLP.c", + "silk/float/noise_shape_analysis_FLP.c", + "silk/float/inner_product_FLP.c", + "silk/float/process_gains_FLP.c", + "silk/float/encode_frame_FLP.c", + "silk/float/scale_copy_vector_FLP.c", + "silk/float/residual_energy_FLP.c", + "silk/float/LTP_analysis_filter_FLP.c", + "silk/float/prefilter_FLP.c" + ] + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources + opus_sources_silk] + + env_opus.add_source_files(env.modules_sources, thirdparty_sources) + env_opus.Append(CFLAGS=["-DHAVE_CONFIG_H"]) + + thirdparty_include_paths = [ + "", + "celt", + "opus", + "silk", + "silk/fixed", + "silk/float", + ] + env_opus.Append(CPPPATH=[thirdparty_dir + "/" + dir for dir in thirdparty_include_paths]) + + # also requires libogg + if (env['builtin_libogg'] != 'no'): + env_opus.Append(CPPPATH=["#thirdparty/libogg"]) + +# Module files +env_opus.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/opus/audio_stream_opus.cpp b/modules/opus/audio_stream_opus.cpp new file mode 100644 index 000000000..4a6b2e224 --- /dev/null +++ b/modules/opus/audio_stream_opus.cpp @@ -0,0 +1,376 @@ +/*************************************************************************/ +/* audio_stream_opus.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Author: George Marques <george@gmarqu.es> */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "audio_stream_opus.h" + +const float AudioStreamPlaybackOpus::osrate=48000.0f; + +int AudioStreamPlaybackOpus::_op_read_func(void *_stream, unsigned char *_ptr, int _nbytes) { + FileAccess *fa=(FileAccess*)_stream; + + if(fa->eof_reached()) + return 0; + + uint8_t *dst = (uint8_t*)_ptr; + + int read = fa->get_buffer(dst, _nbytes); + + return read; +} + +int AudioStreamPlaybackOpus::_op_seek_func(void *_stream, opus_int64 _offset, int _whence){ + +#ifdef SEEK_SET + FileAccess *fa=(FileAccess*)_stream; + + switch (_whence) { + case SEEK_SET: { + fa->seek(_offset); + } break; + case SEEK_CUR: { + fa->seek(fa->get_pos()+_offset); + } break; + case SEEK_END: { + fa->seek_end(_offset); + } break; + default: { + ERR_PRINT("BUG, wtf was whence set to?\n"); + } + } + int ret=fa->eof_reached()?-1:0; + return ret; +#else + return -1; // no seeking +#endif +} + +int AudioStreamPlaybackOpus::_op_close_func(void *_stream) { + if (!_stream) + return 0; + FileAccess *fa=(FileAccess*)_stream; + if (fa->is_open()) + fa->close(); + return 0; +} + +opus_int64 AudioStreamPlaybackOpus::_op_tell_func(void *_stream) { + FileAccess *_fa = (FileAccess*)_stream; + return (opus_int64)_fa->get_pos(); +} + +void AudioStreamPlaybackOpus::_clear_stream() { + if(!stream_loaded) + return; + + op_free(opus_file); + _close_file(); + + stream_loaded=false; + stream_channels=1; + playing=false; +} + +void AudioStreamPlaybackOpus::_close_file() { + if (f) { + memdelete(f); + f=NULL; + } +} + +Error AudioStreamPlaybackOpus::_load_stream() { + + ERR_FAIL_COND_V(!stream_valid,ERR_UNCONFIGURED); + + _clear_stream(); + if (file=="") + return ERR_INVALID_DATA; + + Error err; + f=FileAccess::open(file,FileAccess::READ,&err); + + if (err) { + ERR_FAIL_COND_V( err, err ); + } + + int _err = 0; + + opus_file = op_open_callbacks(f,&_op_callbacks,NULL,0,&_err); + + switch (_err) { + case OP_EREAD: { // - Can't read the file. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CANT_READ ); + } break; + case OP_EVERSION: // - Unrecognized version number. + case OP_ENOTFORMAT: // - Stream is not Opus data. + case OP_EIMPL : { // - Stream used non-implemented feature. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_UNRECOGNIZED ); + } break; + case OP_EBADLINK: // - Failed to find old data after seeking. + case OP_EBADTIMESTAMP: // - Timestamp failed the validity checks. + case OP_EBADHEADER: { // - Invalid or mising Opus bitstream header. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CORRUPT ); + } break; + case OP_EFAULT: { // - Internal logic fault; indicates a bug or heap/stack corruption. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_BUG ); + } break; + } + repeats=0; + stream_loaded=true; + + + return OK; +} + +AudioStreamPlaybackOpus::AudioStreamPlaybackOpus() { + loops=false; + playing=false; + f = NULL; + stream_loaded=false; + stream_valid=false; + repeats=0; + paused=true; + stream_channels=0; + current_section=0; + length=0; + loop_restart_time=0; + pre_skip=0; + + _op_callbacks.read = _op_read_func; + _op_callbacks.seek = _op_seek_func; + _op_callbacks.tell = _op_tell_func; + _op_callbacks.close = _op_close_func; +} + +Error AudioStreamPlaybackOpus::set_file(const String &p_file) { + file=p_file; + stream_valid=false; + Error err; + f=FileAccess::open(file,FileAccess::READ,&err); + + if (err) { + ERR_FAIL_COND_V( err, err ); + } + + int _err; + + opus_file = op_open_callbacks(f,&_op_callbacks,NULL,0,&_err); + + switch (_err) { + case OP_EREAD: { // - Can't read the file. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CANT_READ ); + } break; + case OP_EVERSION: // - Unrecognized version number. + case OP_ENOTFORMAT: // - Stream is not Opus data. + case OP_EIMPL : { // - Stream used non-implemented feature. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_UNRECOGNIZED ); + } break; + case OP_EBADLINK: // - Failed to find old data after seeking. + case OP_EBADTIMESTAMP: // - Timestamp failed the validity checks. + case OP_EBADHEADER: { // - Invalid or mising Opus bitstream header. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CORRUPT ); + } break; + case OP_EFAULT: { // - Internal logic fault; indicates a bug or heap/stack corruption. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_BUG ); + } break; + } + + const OpusHead *oinfo = op_head(opus_file,-1); + + stream_channels=oinfo->channel_count; + pre_skip=oinfo->pre_skip; + frames_mixed=pre_skip; + ogg_int64_t len = op_pcm_total(opus_file,-1); + if(len < 0) { + length = 0; + } else { + length=(len/osrate); + } + + op_free(opus_file); + memdelete(f); + f=NULL; + stream_valid=true; + + + return OK; +} + +void AudioStreamPlaybackOpus::play(float p_from) { + if (playing) + stop(); + + if (_load_stream()!=OK) + return; + + frames_mixed=pre_skip; + playing=true; + if (p_from>0) { + seek_pos(p_from); + } +} + +void AudioStreamPlaybackOpus::stop() { + _clear_stream(); + playing=false; +} + +void AudioStreamPlaybackOpus::seek_pos(float p_time) { + if(!playing) return; + ogg_int64_t pcm_offset = (ogg_int64_t)(p_time * osrate); + bool ok = op_pcm_seek(opus_file,pcm_offset)==0; + if(!ok) { + ERR_PRINT("Seek time over stream size."); + return; + } + frames_mixed=osrate*p_time; +} + +int AudioStreamPlaybackOpus::mix(int16_t* p_bufer,int p_frames) { + if (!playing) + return 0; + + int total=p_frames; + + while (true) { + + int todo = p_frames; + + if (todo==0 || todo<MIN_MIX) { + break; + } + + int ret=op_read(opus_file,(opus_int16*)p_bufer,todo*stream_channels,¤t_section); + if (ret<0) { + playing = false; + ERR_EXPLAIN("Error reading Opus File: "+file); + ERR_BREAK(ret<0); + } else if (ret==0) { // end of song, reload? + op_free(opus_file); + + _close_file(); + + f=FileAccess::open(file,FileAccess::READ); + + int errv = 0; + opus_file = op_open_callbacks(f,&_op_callbacks,NULL,0,&errv); + if (errv!=0) { + playing=false; + break; // :( + } + + if (!has_loop()) { + playing=false; + repeats=1; + break; + } + + if (loop_restart_time) { + bool ok = op_pcm_seek(opus_file, (loop_restart_time*osrate)+pre_skip)==0; + if (!ok) { + playing=false; + ERR_PRINT("loop restart time rejected") + } + + frames_mixed=(loop_restart_time*osrate)+pre_skip; + } else { + frames_mixed=pre_skip; + } + repeats++; + continue; + + } + + stream_channels=op_head(opus_file,current_section)->channel_count; + + frames_mixed+=ret; + + p_bufer+=ret*stream_channels; + p_frames-=ret; + + } + + return total-p_frames; +} + +float AudioStreamPlaybackOpus::get_length() const { + if(!stream_loaded) { + if(const_cast<AudioStreamPlaybackOpus*>(this)->_load_stream() != OK) + return 0; + } + return length; +} + +float AudioStreamPlaybackOpus::get_pos() const { + + int32_t frames = int32_t(frames_mixed); + if (frames < 0) + frames=0; + return double(frames) / osrate; +} + +int AudioStreamPlaybackOpus::get_minimum_buffer_size() const { + return MIN_MIX; +} + +AudioStreamPlaybackOpus::~AudioStreamPlaybackOpus() { + _clear_stream(); +} + +RES ResourceFormatLoaderAudioStreamOpus::load(const String &p_path, const String& p_original_path, Error *r_error) { + if (r_error) + *r_error=OK; + + AudioStreamOpus *opus_stream = memnew(AudioStreamOpus); + opus_stream->set_file(p_path); + return Ref<AudioStreamOpus>(opus_stream); +} + +void ResourceFormatLoaderAudioStreamOpus::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("opus"); +} +String ResourceFormatLoaderAudioStreamOpus::get_resource_type(const String &p_path) const { + + if (p_path.get_extension().to_lower()=="opus") + return "AudioStreamOpus"; + return ""; +} + +bool ResourceFormatLoaderAudioStreamOpus::handles_type(const String& p_type) const { + return (p_type=="AudioStream" || p_type=="AudioStreamOpus"); +} diff --git a/modules/opus/audio_stream_opus.h b/modules/opus/audio_stream_opus.h new file mode 100644 index 000000000..5093456cc --- /dev/null +++ b/modules/opus/audio_stream_opus.h @@ -0,0 +1,141 @@ +/*************************************************************************/ +/* audio_stream_opus.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Author: George Marques <george@gmarqu.es> */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef AUDIO_STREAM_OPUS_H +#define AUDIO_STREAM_OPUS_H + +#include "io/resource_loader.h" +#include "os/file_access.h" +#include "scene/resources/audio_stream.h" + +#include <opus/opusfile.h> + +class AudioStreamPlaybackOpus : public AudioStreamPlayback { + + GDCLASS(AudioStreamPlaybackOpus,AudioStreamPlayback) + + enum { + MIN_MIX=1024 + }; + + FileAccess *f; + + OpusFileCallbacks _op_callbacks; + float length; + static int _op_read_func(void *_stream, unsigned char *_ptr, int _nbytes); + static int _op_seek_func(void *_stream, opus_int64 _offset, int _whence); + static int _op_close_func(void *_stream); + static opus_int64 _op_tell_func(void *_stream); + static const float osrate; + + String file; + int64_t frames_mixed; + + bool stream_loaded; + volatile bool playing; + OggOpusFile *opus_file; + int stream_channels; + int current_section; + int pre_skip; + + bool paused; + bool loops; + int repeats; + + Error _load_stream(); + void _clear_stream(); + void _close_file(); + + bool stream_valid; + float loop_restart_time; + +public: + Error set_file(const String& p_file); + + virtual void play(float p_from=0); + virtual void stop(); + virtual bool is_playing() const { return playing; } + + virtual void set_loop_restart_time(float p_time) { loop_restart_time=p_time; } + + virtual void set_paused(bool p_paused) { paused=p_paused; } + virtual bool is_paused() const { return paused; } + + virtual void set_loop(bool p_enable) { loops=p_enable; } + virtual bool has_loop() const {return loops; } + + virtual float get_length() const; + + virtual String get_stream_name() const { return ""; } + + virtual int get_loop_count() const { return repeats; } + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + virtual int get_channels() const { return stream_channels; } + virtual int get_mix_rate() const { return osrate; } + + virtual int get_minimum_buffer_size() const; + + virtual int mix(int16_t* p_bufer,int p_frames); + + AudioStreamPlaybackOpus(); + ~AudioStreamPlaybackOpus(); +}; + + +class AudioStreamOpus: public AudioStream { + + GDCLASS(AudioStreamOpus,AudioStream) + + String file; +public: + + Ref<AudioStreamPlayback> instance_playback() { + Ref<AudioStreamPlaybackOpus> pb = memnew( AudioStreamPlaybackOpus ); + pb->set_file(file); + return pb; + } + + void set_file(const String& p_file) { file=p_file; } + +}; + +class ResourceFormatLoaderAudioStreamOpus: public ResourceFormatLoader { +public: + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + +#endif // AUDIO_STREAM_OPUS_H diff --git a/modules/opus/config.py b/modules/opus/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/opus/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/opus/register_types.cpp b/modules/opus/register_types.cpp new file mode 100644 index 000000000..a177bc946 --- /dev/null +++ b/modules/opus/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "audio_stream_opus.h" + +static ResourceFormatLoaderAudioStreamOpus *opus_stream_loader = NULL; + +void register_opus_types() { + + opus_stream_loader = memnew( ResourceFormatLoaderAudioStreamOpus ); + ResourceLoader::add_resource_format_loader(opus_stream_loader); + ClassDB::register_class<AudioStreamOpus>(); +} + +void unregister_opus_types() { + + memdelete( opus_stream_loader ); +} diff --git a/modules/opus/register_types.h b/modules/opus/register_types.h new file mode 100644 index 000000000..09181b4f0 --- /dev/null +++ b/modules/opus/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_opus_types(); +void unregister_opus_types(); diff --git a/modules/pbm/SCsub b/modules/pbm/SCsub new file mode 100644 index 000000000..fa328be02 --- /dev/null +++ b/modules/pbm/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_pbm = env_modules.Clone() + +env_pbm.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/pbm/bitmap_loader_pbm.cpp b/modules/pbm/bitmap_loader_pbm.cpp new file mode 100644 index 000000000..ab0805a6b --- /dev/null +++ b/modules/pbm/bitmap_loader_pbm.cpp @@ -0,0 +1,252 @@ +/*************************************************************************/ +/* bitmap_loader_pbm.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "bitmap_loader_pbm.h" +#include "os/file_access.h" +#include "scene/resources/bit_mask.h" + + +static bool _get_token(FileAccessRef& f,uint8_t &saved,PoolVector<uint8_t>& r_token,bool p_binary=false,bool p_single_chunk=false) { + + + int token_max = r_token.size(); + PoolVector<uint8_t>::Write w; + if (token_max) + w=r_token.write(); + int ofs=0; + bool lf=false; + + + while(true) { + + uint8_t b; + if (saved) { + b=saved; + saved=0; + } else { + b = f->get_8(); + } + if (f->eof_reached()) { + if (ofs) { + w=PoolVector<uint8_t>::Write(); + r_token.resize(ofs); + return true; + } else { + return false; + } + } + + if (!ofs && !p_binary && b=='#') { + //skip comment + while(b!='\n') { + if (f->eof_reached()) { + return false; + } + + b = f->get_8(); + } + + lf=true; + + } else if (b<=32 && !(p_binary && (ofs || lf))) { + + if (b=='\n') { + lf=true; + } + + + if (ofs && !p_single_chunk) { + w=PoolVector<uint8_t>::Write(); + r_token.resize(ofs); + saved=b; + + return true; + } + } else { + + bool resized=false; + while (ofs>=token_max) { + if (token_max) + token_max<<=1; + else + token_max=1; + resized=true; + } + if (resized) { + w=PoolVector<uint8_t>::Write(); + r_token.resize(token_max); + w=r_token.write(); + } + w[ofs++]=b; + } + } + + return false; +} + +static int _get_number_from_token(PoolVector<uint8_t>& r_token) { + + int len = r_token.size(); + PoolVector<uint8_t>::Read r = r_token.read(); + return String::to_int((const char*)r.ptr(),len); + +} + + +RES ResourceFormatPBM::load(const String &p_path,const String& p_original_path,Error *r_error) { + +#define _RETURN(m_err)\ +{\ + if (r_error)\ + *r_error=m_err;\ + ERR_FAIL_V(RES());\ +} + + + FileAccessRef f=FileAccess::open(p_path,FileAccess::READ); + uint8_t saved=0; + if (!f) + _RETURN(ERR_CANT_OPEN); + + PoolVector<uint8_t> token; + + if (!_get_token(f,saved,token)) { + _RETURN(ERR_PARSE_ERROR); + } + + if (token.size()!=2) { + _RETURN(ERR_FILE_CORRUPT); + } + if (token[0]!='P') { + _RETURN(ERR_FILE_CORRUPT); + } + if (token[1]!='1' && token[1]!='4') { + _RETURN(ERR_FILE_CORRUPT); + } + + bool bits = token[1]=='4'; + + if (!_get_token(f,saved,token)) { + _RETURN(ERR_PARSE_ERROR); + } + + int width = _get_number_from_token(token); + if (width<=0) { + _RETURN(ERR_FILE_CORRUPT); + } + + + if (!_get_token(f,saved,token)) { + _RETURN(ERR_PARSE_ERROR); + } + + int height = _get_number_from_token(token); + if (height<=0) { + _RETURN(ERR_FILE_CORRUPT); + } + + + Ref<BitMap> bm; + bm.instance(); + bm->create(Size2i(width,height)); + + if (!bits) { + + int required_bytes = width*height; + if (!_get_token(f,saved,token,false,true)) { + _RETURN(ERR_PARSE_ERROR); + } + + if (token.size()<required_bytes) { + _RETURN(ERR_FILE_CORRUPT); + } + + PoolVector<uint8_t>::Read r=token.read(); + + for(int i=0;i<height;i++) { + for(int j=0;j<width;j++) { + + + char num = r[i*width+j]; + bm->set_bit(Point2i(j,i),num=='0'); + } + + } + + + + } else { + //a single, entire token of bits! + if (!_get_token(f,saved,token,true)) { + _RETURN(ERR_PARSE_ERROR); + } + int required_bytes = Math::ceil((width*height)/8.0); + if (token.size()<required_bytes) { + _RETURN(ERR_FILE_CORRUPT); + } + + PoolVector<uint8_t>::Read r=token.read(); + int bitwidth = width; + if (bitwidth % 8) + bitwidth+=8-(bitwidth%8); + + for(int i=0;i<height;i++) { + for(int j=0;j<width;j++) { + + int ofs = bitwidth*i+j; + + uint8_t byte = r[ofs/8]; + bool bit = (byte>>(7-(ofs%8)))&1; + + bm->set_bit(Point2i(j,i),!bit); + + } + + } + + } + + return bm; + + +} + +void ResourceFormatPBM::get_recognized_extensions(List<String> *p_extensions) const { + p_extensions->push_back("pbm"); +} +bool ResourceFormatPBM::handles_type(const String& p_type) const { + return p_type=="BitMap"; +} +String ResourceFormatPBM::get_resource_type(const String &p_path) const { + + if (p_path.get_extension().to_lower()=="pbm") + return "BitMap"; + return ""; +} + + diff --git a/modules/pbm/bitmap_loader_pbm.h b/modules/pbm/bitmap_loader_pbm.h new file mode 100644 index 000000000..b60b38fcc --- /dev/null +++ b/modules/pbm/bitmap_loader_pbm.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* bitmap_loader_pbm.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef BITMAP_LOADER_PBM_H +#define BITMAP_LOADER_PBM_H + +#include "io/resource_loader.h" + +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ +class ResourceFormatPBM : public ResourceFormatLoader { + + +public: + + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + + + +#endif diff --git a/modules/pbm/config.py b/modules/pbm/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/pbm/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/pbm/register_types.cpp b/modules/pbm/register_types.cpp new file mode 100644 index 000000000..0dd39ce1e --- /dev/null +++ b/modules/pbm/register_types.cpp @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "bitmap_loader_pbm.h" + +static ResourceFormatPBM * pbm_loader = NULL; + +void register_pbm_types() { + + pbm_loader = memnew( ResourceFormatPBM ); + ResourceLoader::add_resource_format_loader(pbm_loader); +} + +void unregister_pbm_types() { + + memdelete( pbm_loader ); +} diff --git a/modules/pbm/register_types.h b/modules/pbm/register_types.h new file mode 100644 index 000000000..c9a125083 --- /dev/null +++ b/modules/pbm/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_pbm_types(); +void unregister_pbm_types(); diff --git a/modules/pvr/SCsub b/modules/pvr/SCsub new file mode 100644 index 000000000..ddca7a794 --- /dev/null +++ b/modules/pvr/SCsub @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_pvr = env_modules.Clone() + +# Thirdparty source files +# Not unbundled so far since not widespread as shared library +thirdparty_dir = "#thirdparty/pvrtccompressor/" +thirdparty_sources = [ + "BitScale.cpp", + "MortonTable.cpp", + "PvrTcDecoder.cpp", + "PvrTcEncoder.cpp", + "PvrTcPacket.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_pvr.add_source_files(env.modules_sources, thirdparty_sources) +env_pvr.Append(CPPPATH=[thirdparty_dir]) + +# Godot source files +env_pvr.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/pvr/config.py b/modules/pvr/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/pvr/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/pvr/register_types.cpp b/modules/pvr/register_types.cpp new file mode 100644 index 000000000..2464e7898 --- /dev/null +++ b/modules/pvr/register_types.cpp @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "texture_loader_pvr.h" + +static ResourceFormatPVR *resource_loader_pvr = NULL; + +void register_pvr_types() { + + resource_loader_pvr = memnew( ResourceFormatPVR ); + ResourceLoader::add_resource_format_loader(resource_loader_pvr); +} + +void unregister_pvr_types() { + + memdelete(resource_loader_pvr); +} diff --git a/modules/pvr/register_types.h b/modules/pvr/register_types.h new file mode 100644 index 000000000..ac2ab748d --- /dev/null +++ b/modules/pvr/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_pvr_types(); +void unregister_pvr_types(); diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp new file mode 100644 index 000000000..0d8cabb33 --- /dev/null +++ b/modules/pvr/texture_loader_pvr.cpp @@ -0,0 +1,715 @@ +/*************************************************************************/ +/* texture_loader_pvr.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "texture_loader_pvr.h" +#include "os/file_access.h" +#include <string.h> +#include "PvrTcEncoder.h" +#include "RgbaBitmap.h" + +static void _pvrtc_decompress(Image* p_img); + +enum PVRFLags { + + PVR_HAS_MIPMAPS=0x00000100, + PVR_TWIDDLED=0x00000200, + PVR_NORMAL_MAP=0x00000400, + PVR_BORDER=0x00000800, + PVR_CUBE_MAP=0x00001000, + PVR_FALSE_MIPMAPS=0x00002000, + PVR_VOLUME_TEXTURES=0x00004000, + PVR_HAS_ALPHA=0x00008000, + PVR_VFLIP=0x00010000 + +}; + + + +RES ResourceFormatPVR::load(const String &p_path,const String& p_original_path,Error *r_error) { + + if (r_error) + *r_error=ERR_CANT_OPEN; + + Error err; + FileAccess *f = FileAccess::open(p_path,FileAccess::READ,&err); + if (!f) + return RES(); + + FileAccessRef faref(f); + + ERR_FAIL_COND_V(err,RES()); + + if (r_error) + *r_error=ERR_FILE_CORRUPT; + + uint32_t hsize = f->get_32(); + + ERR_FAIL_COND_V(hsize!=52,RES()); + uint32_t height = f->get_32(); + uint32_t width = f->get_32(); + uint32_t mipmaps = f->get_32(); + uint32_t flags = f->get_32(); + uint32_t surfsize = f->get_32(); + uint32_t bpp = f->get_32(); + uint32_t rmask = f->get_32(); + uint32_t gmask = f->get_32(); + uint32_t bmask = f->get_32(); + uint32_t amask = f->get_32(); + uint8_t pvrid[5]={0,0,0,0,0}; + f->get_buffer(pvrid,4); + ERR_FAIL_COND_V(String((char*)pvrid)!="PVR!",RES()); + uint32_t surfcount = f->get_32(); + +/* + print_line("height: "+itos(height)); + print_line("width: "+itos(width)); + print_line("mipmaps: "+itos(mipmaps)); + print_line("flags: "+itos(flags)); + print_line("surfsize: "+itos(surfsize)); + print_line("bpp: "+itos(bpp)); + print_line("rmask: "+itos(rmask)); + print_line("gmask: "+itos(gmask)); + print_line("bmask: "+itos(bmask)); + print_line("amask: "+itos(amask)); + print_line("surfcount: "+itos(surfcount)); +*/ + + PoolVector<uint8_t> data; + data.resize(surfsize); + + ERR_FAIL_COND_V(data.size()==0,RES()); + + + PoolVector<uint8_t>::Write w = data.write(); + f->get_buffer(&w[0],surfsize); + err = f->get_error(); + ERR_FAIL_COND_V(err!=OK,RES()); + + Image::Format format=Image::FORMAT_MAX; + + + switch(flags&0xFF) { + + case 0x18: + case 0xC: format=(flags&PVR_HAS_ALPHA)?Image::FORMAT_PVRTC2A:Image::FORMAT_PVRTC2; break; + case 0x19: + case 0xD: format=(flags&PVR_HAS_ALPHA)?Image::FORMAT_PVRTC4A:Image::FORMAT_PVRTC4; break; + case 0x16: + format=Image::FORMAT_L8; break; + case 0x17: + format=Image::FORMAT_LA8; break; + case 0x20: + case 0x80: + case 0x81: + format=Image::FORMAT_DXT1; break; + case 0x21: + case 0x22: + case 0x82: + case 0x83: + format=Image::FORMAT_DXT3; break; + case 0x23: + case 0x24: + case 0x84: + case 0x85: + format=Image::FORMAT_DXT5; break; + case 0x4: + case 0x15: + format=Image::FORMAT_RGB8; break; + case 0x5: + case 0x12: + format=Image::FORMAT_RGBA8; break; + case 0x36: + format=Image::FORMAT_ETC; break; + default: + ERR_EXPLAIN("Unsupported format in PVR texture: "+itos(flags&0xFF)); + ERR_FAIL_V(RES()); + + } + + w = PoolVector<uint8_t>::Write(); + + int tex_flags=Texture::FLAG_FILTER|Texture::FLAG_REPEAT; + + if (mipmaps) + tex_flags|=Texture::FLAG_MIPMAPS; + + + print_line("flip: "+itos(flags&PVR_VFLIP)); + + Image image(width,height,mipmaps,format,data); + ERR_FAIL_COND_V(image.empty(),RES()); + + Ref<ImageTexture> texture = memnew( ImageTexture ); + texture->create_from_image(image,tex_flags); + + if (r_error) + *r_error=OK; + + return texture; + +} + +void ResourceFormatPVR::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("pvr"); +} +bool ResourceFormatPVR::handles_type(const String& p_type) const { + + return ClassDB::is_parent_class(p_type,"Texture"); +} +String ResourceFormatPVR::get_resource_type(const String &p_path) const { + + + if (p_path.get_extension().to_lower()=="pvr") + return "Texture"; + return ""; +} + + + +static void _compress_pvrtc4(Image * p_img) { + + Image img = *p_img; + + bool make_mipmaps=false; + if (img.get_width()%8 || img.get_height()%8) { + make_mipmaps=img.has_mipmaps(); + img.resize(img.get_width()+(8-(img.get_width()%8)),img.get_height()+(8-(img.get_height()%8))); + } + img.convert(Image::FORMAT_RGBA8); + if (!img.has_mipmaps() && make_mipmaps) + img.generate_mipmaps(); + + bool use_alpha=img.detect_alpha(); + + Image new_img; + new_img.create(img.get_width(),img.get_height(),true,use_alpha?Image::FORMAT_PVRTC4A:Image::FORMAT_PVRTC4); + PoolVector<uint8_t> data=new_img.get_data(); + { + PoolVector<uint8_t>::Write wr=data.write(); + PoolVector<uint8_t>::Read r=img.get_data().read(); + + + for(int i=0;i<=new_img.get_mipmap_count();i++) { + + int ofs,size,w,h; + img.get_mipmap_offset_size_and_dimensions(i,ofs,size,w,h); + Javelin::RgbaBitmap bm(w,h); + copymem(bm.GetData(),&r[ofs],size); + { + Javelin::ColorRgba<unsigned char> *dp = bm.GetData(); + for(int j=0;j<size/4;j++) { + SWAP(dp[j].r,dp[j].b); + } + } + + new_img.get_mipmap_offset_size_and_dimensions(i,ofs,size,w,h); + Javelin::PvrTcEncoder::EncodeRgba4Bpp(&wr[ofs],bm); + } + + } + + *p_img = Image(new_img.get_width(),new_img.get_height(),new_img.has_mipmaps(),new_img.get_format(),data); + +} + +ResourceFormatPVR::ResourceFormatPVR() { + + + Image::_image_decompress_pvrtc=_pvrtc_decompress; + Image::_image_compress_pvrtc4_func=_compress_pvrtc4; + Image::_image_compress_pvrtc2_func=_compress_pvrtc4; + +} + +///////////////////////////////////////////////////////// + +//PVRTC decompressor, Based on PVRTC decompressor by IMGTEC. + +///////////////////////////////////////////////////////// + + +#define PT_INDEX 2 +#define BLK_Y_SIZE 4 +#define BLK_X_MAX 8 +#define BLK_X_2BPP 8 +#define BLK_X_4BPP 4 + +#define WRAP_COORD(Val, Size) ((Val) & ((Size)-1)) + +/* + Define an expression to either wrap or clamp large or small vals to the + legal coordinate range +*/ +#define LIMIT_COORD(Val, Size, p_tiled) \ + ((p_tiled)? WRAP_COORD((Val), (Size)): CLAMP((Val), 0, (Size)-1)) + + +struct PVRTCBlock { + //blocks are 64 bits + uint32_t data[2]; +}; + + + +_FORCE_INLINE_ bool is_po2( uint32_t p_input ) { + + if( p_input==0 ) + return 0; + uint32_t minus1=p_input- 1; + return ((p_input|minus1)==(p_input^minus1))?1:0; +} + + +static void unpack_5554(const PVRTCBlock *p_block, int p_ab_colors[2][4]) { + + uint32_t raw_bits[2]; + raw_bits[0] = p_block->data[1] & (0xFFFE); + raw_bits[1] = p_block->data[1] >> 16; + + for(int i=0;i<2;i++) { + + if(raw_bits[i] & (1<<15)) { + + p_ab_colors[i][0]= (raw_bits[i] >> 10) & 0x1F; + p_ab_colors[i][1]= (raw_bits[i] >> 5) & 0x1F; + p_ab_colors[i][2]= raw_bits[i] & 0x1F; + if(i==0) + p_ab_colors[0][2]|= p_ab_colors[0][2] >> 4; + p_ab_colors[i][3] = 0xF; + } else { + + p_ab_colors[i][0] = (raw_bits[i] >> (8-1)) & 0x1E; + p_ab_colors[i][1] = (raw_bits[i] >> (4-1)) & 0x1E; + + p_ab_colors[i][0] |= p_ab_colors[i][0] >> 4; + p_ab_colors[i][1] |= p_ab_colors[i][1] >> 4; + + p_ab_colors[i][2] = (raw_bits[i] & 0xF) << 1; + + if(i==0) + p_ab_colors[0][2] |= p_ab_colors[0][2] >> 3; + else + p_ab_colors[0][2] |= p_ab_colors[0][2] >> 4; + + p_ab_colors[i][3] = (raw_bits[i] >> 11) & 0xE; + } + } +} + + +static void unpack_modulations(const PVRTCBlock *p_block, const int p_2bit, int p_modulation[8][16], int p_modulation_modes[8][16], int p_x, int p_y) { + + int block_mod_mode = p_block->data[1] & 1; + uint32_t modulation_bits = p_block->data[0]; + + if(p_2bit && block_mod_mode) { + + for(int y = 0; y < BLK_Y_SIZE; y++) { + for(int x = 0; x < BLK_X_2BPP; x++) { + + p_modulation_modes[y+p_y][x+p_x] = block_mod_mode; + + if(((x^y)&1) == 0) { + p_modulation[y+p_y][x+p_x] = modulation_bits & 3; + modulation_bits >>= 2; + } + } + } + + } else if(p_2bit) { + + for(int y = 0; y < BLK_Y_SIZE; y++) { + for(int x = 0; x < BLK_X_2BPP; x++) { + p_modulation_modes[y+p_y][x+p_x] = block_mod_mode; + + if(modulation_bits & 1) + p_modulation[y+p_y][x+p_x] = 0x3; + else + p_modulation[y+p_y][x+p_x] = 0x0; + + modulation_bits >>= 1; + } + } + } else { + for(int y = 0; y < BLK_Y_SIZE; y++) { + for(int x = 0; x < BLK_X_4BPP; x++) { + p_modulation_modes[y+p_y][x+p_x] = block_mod_mode; + p_modulation[y+p_y][x+p_x] = modulation_bits & 3; + modulation_bits >>= 2; + } + } + } + + ERR_FAIL_COND(modulation_bits!=0); +} + + + +static void interpolate_colors(const int p_colorp[4], const int p_colorq[4], const int p_colorr[4], const int p_colors[4], bool p_2bit, const int x, const int y, int r_result[4]) { + int u, v, uscale; + int k; + + int tmp1, tmp2; + + int P[4], Q[4], R[4], S[4]; + + for(k = 0; k < 4; k++) { + P[k] = p_colorp[k]; + Q[k] = p_colorq[k]; + R[k] = p_colorr[k]; + S[k] = p_colors[k]; + } + + v = (y & 0x3) | ((~y & 0x2) << 1); + + if(p_2bit) + u = (x & 0x7) | ((~x & 0x4) << 1); + else + u = (x & 0x3) | ((~x & 0x2) << 1); + + v = v - BLK_Y_SIZE/2; + + if(p_2bit) { + u = u - BLK_X_2BPP/2; + uscale = 8; + } else { + u = u - BLK_X_4BPP/2; + uscale = 4; + } + + for(k = 0; k < 4; k++) { + tmp1 = P[k] * uscale + u * (Q[k] - P[k]); + tmp2 = R[k] * uscale + u * (S[k] - R[k]); + + tmp1 = tmp1 * 4 + v * (tmp2 - tmp1); + + r_result[k] = tmp1; + } + + if(p_2bit) { + for(k = 0; k < 3; k++) { + r_result[k] >>= 2; + } + + r_result[3] >>= 1; + } else { + for(k = 0; k < 3; k++) { + r_result[k] >>= 1; + } + } + + for(k = 0; k < 4; k++) { + ERR_FAIL_COND(r_result[k] >= 256); + } + + + for(k = 0; k < 3; k++) { + r_result[k] += r_result[k] >> 5; + } + + r_result[3] += r_result[3] >> 4; + + for(k = 0; k < 4; k++) { + ERR_FAIL_COND(r_result[k] >= 256); + } + +} + + +static void get_modulation_value(int x, int y, const int p_2bit, const int p_modulation[8][16], const int p_modulation_modes[8][16], int *r_mod, int *p_dopt) +{ + static const int rep_vals0[4] = {0, 3, 5, 8}; + static const int rep_vals1[4] = {0, 4, 4, 8}; + + int mod_val; + + y = (y & 0x3) | ((~y & 0x2) << 1); + + if(p_2bit) + x = (x & 0x7) | ((~x & 0x4) << 1); + else + x = (x & 0x3) | ((~x & 0x2) << 1); + + *p_dopt = 0; + + if(p_modulation_modes[y][x]==0) { + mod_val = rep_vals0[p_modulation[y][x]]; + } else if(p_2bit) { + if(((x^y)&1)==0) + mod_val = rep_vals0[p_modulation[y][x]]; + else if(p_modulation_modes[y][x] == 1) { + mod_val = (rep_vals0[p_modulation[y-1][x]] + + rep_vals0[p_modulation[y+1][x]] + + rep_vals0[p_modulation[y][x-1]] + + rep_vals0[p_modulation[y][x+1]] + 2) / 4; + } else if(p_modulation_modes[y][x] == 2) { + mod_val = (rep_vals0[p_modulation[y][x-1]] + + rep_vals0[p_modulation[y][x+1]] + 1) / 2; + } else { + mod_val = (rep_vals0[p_modulation[y-1][x]] + + rep_vals0[p_modulation[y+1][x]] + 1) / 2; + } + } else { + mod_val = rep_vals1[p_modulation[y][x]]; + + *p_dopt = p_modulation[y][x] == PT_INDEX; + } + + *r_mod =mod_val; +} + + +static int disable_twiddling = 0; + +static uint32_t twiddle_uv(uint32_t p_height, uint32_t p_width, uint32_t p_y, uint32_t p_x) { + + uint32_t twiddled; + + uint32_t min_dimension; + uint32_t max_value; + + uint32_t scr_bit_pos; + uint32_t dst_bit_pos; + + int shift_count; + + ERR_FAIL_COND_V(p_y >= p_height,0); + ERR_FAIL_COND_V(p_x >= p_width,0); + + ERR_FAIL_COND_V(!is_po2(p_height),0); + ERR_FAIL_COND_V(!is_po2(p_width),0); + + if(p_height < p_width) { + min_dimension = p_height; + max_value = p_x; + } else { + min_dimension = p_width; + max_value = p_y; + } + + if(disable_twiddling) + return (p_y* p_width + p_x); + + scr_bit_pos = 1; + dst_bit_pos = 1; + twiddled = 0; + shift_count = 0; + + while(scr_bit_pos < min_dimension) { + if(p_y & scr_bit_pos) { + twiddled |= dst_bit_pos; + } + + if(p_x & scr_bit_pos) { + twiddled |= (dst_bit_pos << 1); + } + + scr_bit_pos <<= 1; + dst_bit_pos <<= 2; + shift_count += 1; + + } + + max_value >>= shift_count; + + twiddled |= (max_value << (2*shift_count)); + + return twiddled; +} + +static void decompress_pvrtc(PVRTCBlock *p_comp_img, const int p_2bit, const int p_width, const int p_height, const int p_tiled, unsigned char* p_dst) { + int x, y; + int i, j; + + int block_x, blk_y; + int block_xp1, blk_yp1; + int x_block_size; + int block_width, block_height; + + int p_x, p_y; + + int p_modulation[8][16]; + int p_modulation_modes[8][16]; + + int Mod, DoPT; + + unsigned int u_pos; + + // local neighbourhood of blocks + PVRTCBlock *p_blocks[2][2]; + + PVRTCBlock *prev[2][2] = {{NULL, NULL}, {NULL, NULL}}; + + struct + { + int Reps[2][4]; + }colors5554[2][2]; + + + int ASig[4], BSig[4]; + + int r_result[4]; + + if(p_2bit) + x_block_size = BLK_X_2BPP; + else + x_block_size = BLK_X_4BPP; + + + block_width = MAX(2, p_width / x_block_size); + block_height = MAX(2, p_height / BLK_Y_SIZE); + + for(y = 0; y < p_height; y++) + { + for(x = 0; x < p_width; x++) + { + + block_x = (x - x_block_size/2); + blk_y = (y - BLK_Y_SIZE/2); + + block_x = LIMIT_COORD(block_x, p_width, p_tiled); + blk_y = LIMIT_COORD(blk_y, p_height, p_tiled); + + + block_x /= x_block_size; + blk_y /= BLK_Y_SIZE; + + block_xp1 = LIMIT_COORD(block_x+1, block_width, p_tiled); + blk_yp1 = LIMIT_COORD(blk_y+1, block_height, p_tiled); + + p_blocks[0][0] = p_comp_img +twiddle_uv(block_height, block_width, blk_y, block_x); + p_blocks[0][1] = p_comp_img +twiddle_uv(block_height, block_width, blk_y, block_xp1); + p_blocks[1][0] = p_comp_img +twiddle_uv(block_height, block_width, blk_yp1, block_x); + p_blocks[1][1] = p_comp_img +twiddle_uv(block_height, block_width, blk_yp1, block_xp1); + + if(memcmp(prev, p_blocks, 4*sizeof(void*)) != 0) { + p_y = 0; + for(i = 0; i < 2; i++) { + p_x = 0; + for(j = 0; j < 2; j++) { + unpack_5554(p_blocks[i][j], colors5554[i][j].Reps); + + unpack_modulations(p_blocks[i][j], + p_2bit, + p_modulation, + p_modulation_modes, + p_x, p_y); + + p_x += x_block_size; + } + + p_y += BLK_Y_SIZE; + } + + + memcpy(prev, p_blocks, 4*sizeof(void*)); + } + + + interpolate_colors(colors5554[0][0].Reps[0], + colors5554[0][1].Reps[0], + colors5554[1][0].Reps[0], + colors5554[1][1].Reps[0], + p_2bit, x, y, + ASig); + + interpolate_colors(colors5554[0][0].Reps[1], + colors5554[0][1].Reps[1], + colors5554[1][0].Reps[1], + colors5554[1][1].Reps[1], + p_2bit, x, y, + BSig); + + get_modulation_value(x,y, p_2bit, (const int (*)[16])p_modulation, (const int (*)[16])p_modulation_modes, + &Mod, &DoPT); + + for(i = 0; i < 4; i++) { + r_result[i] = ASig[i] * 8 + Mod * (BSig[i] - ASig[i]); + r_result[i] >>= 3; + } + + if(DoPT) + r_result[3] = 0; + + + u_pos = (x+y*p_width)<<2; + p_dst[u_pos+0] = (uint8_t)r_result[0]; + p_dst[u_pos+1] = (uint8_t)r_result[1]; + p_dst[u_pos+2] = (uint8_t)r_result[2]; + p_dst[u_pos+3] = (uint8_t)r_result[3]; + } + } +} + +static void _pvrtc_decompress(Image* p_img) { + + /* + static void decompress_pvrtc(const void *p_comp_img, const int p_2bit, const int p_width, const int p_height, unsigned char* p_dst) { + decompress_pvrtc((PVRTCBlock*)p_comp_img,p_2bit,p_width,p_height,1,p_dst); + } + */ + + ERR_FAIL_COND( p_img->get_format()!=Image::FORMAT_PVRTC2 && p_img->get_format()!=Image::FORMAT_PVRTC2A && p_img->get_format()!=Image::FORMAT_PVRTC4 && p_img->get_format()!=Image::FORMAT_PVRTC4A); + + bool _2bit = (p_img->get_format()==Image::FORMAT_PVRTC2 || p_img->get_format()==Image::FORMAT_PVRTC2A ); + + PoolVector<uint8_t> data = p_img->get_data(); + PoolVector<uint8_t>::Read r = data.read(); + + + PoolVector<uint8_t> newdata; + newdata.resize( p_img->get_width() * p_img->get_height() * 4); + PoolVector<uint8_t>::Write w=newdata.write(); + + decompress_pvrtc((PVRTCBlock*)r.ptr(),_2bit,p_img->get_width(),p_img->get_height(),0,(unsigned char*)w.ptr()); + + /* + for(int i=0;i<newdata.size();i++) { + print_line(itos(w[i])); + } + */ + + w=PoolVector<uint8_t>::Write(); + r=PoolVector<uint8_t>::Read(); + + bool make_mipmaps=p_img->has_mipmaps(); + Image newimg(p_img->get_width(),p_img->get_height(),false,Image::FORMAT_RGBA8,newdata); + if (make_mipmaps) + newimg.generate_mipmaps(); + *p_img=newimg; + +} + + + + + + + + diff --git a/modules/pvr/texture_loader_pvr.h b/modules/pvr/texture_loader_pvr.h new file mode 100644 index 000000000..bad48b456 --- /dev/null +++ b/modules/pvr/texture_loader_pvr.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* texture_loader_pvr.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef TEXTURE_LOADER_PVR_H +#define TEXTURE_LOADER_PVR_H + + +#include "scene/resources/texture.h" +#include "io/resource_loader.h" + + +class ResourceFormatPVR : public ResourceFormatLoader{ +public: + + virtual RES load(const String &p_path,const String& p_original_path,Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + + ResourceFormatPVR(); + virtual ~ResourceFormatPVR() {} +}; + + +#endif // TEXTURE_LOADER_PVR_H diff --git a/modules/regex/SCsub b/modules/regex/SCsub new file mode 100644 index 000000000..088240676 --- /dev/null +++ b/modules/regex/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.modules_sources, "*.cpp") + +Export('env') diff --git a/modules/regex/config.py b/modules/regex/config.py new file mode 100644 index 000000000..5347cfd24 --- /dev/null +++ b/modules/regex/config.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp new file mode 100644 index 000000000..e67040b5a --- /dev/null +++ b/modules/regex/regex.cpp @@ -0,0 +1,1507 @@ +/*************************************************************************/ +/* regex.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "regex.h" +#include <wctype.h> +#include <wchar.h> + +static int RegEx_hex2int(const CharType c) +{ + if ('0' <= c && c <= '9') + return int(c - '0'); + else if ('a' <= c && c <= 'f') + return int(c - 'a') + 10; + else if ('A' <= c && c <= 'F') + return int(c - 'A') + 10; + return -1; +} + +struct RegExSearch { + + Ref<RegExMatch> match; + const CharType* str; + int end; + int eof; + + // For standard quantifier behaviour, test_parent is used to check the + // rest of the pattern. If the pattern matches, to prevent the parent + // from testing again, the complete flag is used as a shortcut out. + bool complete; + + // With lookahead, the position needs to rewind to its starting position + // when test_parent is used. Due to functional programming, this state + // has to be kept as a parameter. + Vector<int> lookahead_pos; + + CharType at(int p_pos) { + return str[p_pos]; + } + + RegExSearch(Ref<RegExMatch>& p_match, int p_end, int p_lookahead) : match(p_match) { + + str = p_match->string.c_str(); + end = p_end; + eof = p_match->string.length(); + complete = false; + lookahead_pos.resize(p_lookahead); + } + +}; + +struct RegExNode { + + RegExNode* next; + RegExNode* previous; + RegExNode* parent; + bool quantifiable; + int length; + + RegExNode() { + + next = NULL; + previous = NULL; + parent = NULL; + quantifiable = false; + length = -1; + } + + virtual ~RegExNode() { + + if (next) + memdelete(next); + } + + // For avoiding RTTI + virtual bool is_look_behind() { return false; } + + virtual int test(RegExSearch& s, int pos) const { + + return next ? next->test(s, pos) : -1; + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + if (next) + pos = next->test(s, pos); + + if (pos >= 0) { + s.complete = true; + if (parent) + pos = parent->test_parent(s, pos); + } + + if (pos < 0) + s.complete = false; + + return pos; + } + + void increment_length(int amount, bool subtract = false) { + + if (amount >= 0 && length >= 0) { + if (!subtract) + length += amount; + else + length -= amount; + } else { + length = -1; + } + + if (parent) + parent->increment_length(amount, subtract); + + } + +}; + +struct RegExNodeChar : public RegExNode { + + CharType ch; + + RegExNodeChar(CharType p_char) { + + length = 1; + quantifiable = true; + ch = p_char; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos || s.at(pos) != ch) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } + + static CharType parse_escape(const CharType*& c) { + + int point = 0; + switch (c[1]) { + case 'x': + for (int i = 2; i <= 3; ++i) { + int res = RegEx_hex2int(c[i]); + if (res == -1) + return '\0'; + point = (point << 4) + res; + } + c = &c[3]; + return CharType(point); + case 'u': + for (int i = 2; i <= 5; ++i) { + int res = RegEx_hex2int(c[i]); + if (res == -1) + return '\0'; + point = (point << 4) + res; + } + c = &c[5]; + return CharType(point); + case '0': ++c; return '\0'; + case 'a': ++c; return '\a'; + case 'e': ++c; return '\e'; + case 'f': ++c; return '\f'; + case 'n': ++c; return '\n'; + case 'r': ++c; return '\r'; + case 't': ++c; return '\t'; + case 'v': ++c; return '\v'; + case 'b': ++c; return '\b'; + default: break; + } + return (++c)[0]; + } +}; + +struct RegExNodeRange : public RegExNode { + + CharType start; + CharType end; + + RegExNodeRange(CharType p_start, CharType p_end) { + + length = 1; + quantifiable = true; + start = p_start; + end = p_end; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos) + return -1; + + CharType c = s.at(pos); + if (c < start || end < c) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } +}; + +struct RegExNodeShorthand : public RegExNode { + + CharType repr; + + RegExNodeShorthand(CharType p_repr) { + + length = 1; + quantifiable = true; + repr = p_repr; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos) + return -1; + + bool found = false; + bool invert = false; + CharType c = s.at(pos); + switch (repr) { + case '.': + found = true; + break; + case 'W': + invert = true; + case 'w': + found = (c == '_' || iswalnum(c) != 0); + break; + case 'D': + invert = true; + case 'd': + found = ('0' <= c && c <= '9'); + break; + case 'S': + invert = true; + case 's': + found = (iswspace(c) != 0); + break; + default: + break; + } + + if (found == invert) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } +}; + +struct RegExNodeClass : public RegExNode { + + enum Type { + Type_none, + Type_alnum, + Type_alpha, + Type_ascii, + Type_blank, + Type_cntrl, + Type_digit, + Type_graph, + Type_lower, + Type_print, + Type_punct, + Type_space, + Type_upper, + Type_xdigit, + Type_word + }; + + Type type; + + bool test_class(CharType c) const { + + static Vector<CharType> REGEX_NODE_SPACE = String(" \t\r\n\f"); + static Vector<CharType> REGEX_NODE_PUNCT = String("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"); + + switch (type) { + case Type_alnum: + if ('0' <= c && c <= '9') return true; + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + return false; + case Type_alpha: + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + return false; + case Type_ascii: + return (0x00 <= c && c <= 0x7F); + case Type_blank: + return (c == ' ' || c == '\t'); + case Type_cntrl: + return ((0x00 <= c && c <= 0x1F) || c == 0x7F); + case Type_digit: + return ('0' <= c && c <= '9'); + case Type_graph: + return (0x20 < c && c < 0x7F); + case Type_lower: + return ('a' <= c && c <= 'z'); + case Type_print: + return (0x1F < c && c < 0x1F); + case Type_punct: + return (REGEX_NODE_PUNCT.find(c) >= 0); + case Type_space: + return (REGEX_NODE_SPACE.find(c) >= 0); + case Type_upper: + return ('A' <= c && c <= 'Z'); + case Type_xdigit: + if ('0' <= c && c <= '9') return true; + if ('a' <= c && c <= 'f') return true; + if ('A' <= c && c <= 'F') return true; + return false; + case Type_word: + if ('0' <= c && c <= '9') return true; + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + return (c == '_'); + default: + return false; + } + return false; + } + + RegExNodeClass(Type p_type) { + + length = 1; + quantifiable = true; + type = p_type; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos) + return -1; + + if (!test_class(s.at(pos))) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } + +#define REGEX_CMP_CLASS(POS, NAME) if (cmp_class(POS, #NAME)) return Type_ ## NAME + + static Type parse_type(const CharType*& p_pos) { + + REGEX_CMP_CLASS(p_pos, alnum); + REGEX_CMP_CLASS(p_pos, alpha); + REGEX_CMP_CLASS(p_pos, ascii); + REGEX_CMP_CLASS(p_pos, blank); + REGEX_CMP_CLASS(p_pos, cntrl); + REGEX_CMP_CLASS(p_pos, digit); + REGEX_CMP_CLASS(p_pos, graph); + REGEX_CMP_CLASS(p_pos, lower); + REGEX_CMP_CLASS(p_pos, print); + REGEX_CMP_CLASS(p_pos, punct); + REGEX_CMP_CLASS(p_pos, space); + REGEX_CMP_CLASS(p_pos, upper); + REGEX_CMP_CLASS(p_pos, xdigit); + REGEX_CMP_CLASS(p_pos, word); + return Type_none; + } + + static bool cmp_class(const CharType*& p_pos, const char* p_text) { + + unsigned int i = 0; + for (i = 0; p_text[i] != '\0'; ++i) + if (p_pos[i] != p_text[i]) + return false; + + if (p_pos[i++] != ':' || p_pos[i] != ']') + return false; + + p_pos = &p_pos[i]; + return true; + } +}; + +struct RegExNodeAnchorStart : public RegExNode { + + RegExNodeAnchorStart() { + + length = 0; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (pos != 0) + return -1; + + return next ? next->test(s, pos) : pos; + } +}; + +struct RegExNodeAnchorEnd : public RegExNode { + + RegExNodeAnchorEnd() { + + length = 0; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (pos != s.eof) + return -1; + + return next ? next->test(s, pos) : pos; + } +}; + +struct RegExNodeWordBoundary : public RegExNode { + + bool inverse; + + RegExNodeWordBoundary(bool p_inverse) { + + length = 0; + inverse = p_inverse; + } + + virtual int test(RegExSearch& s, int pos) const { + + bool left = false; + bool right = false; + + if (pos != 0) { + CharType c = s.at(pos - 1); + if (c == '_' || iswalnum(c)) + left = true; + } + + if (pos != s.eof) { + CharType c = s.at(pos); + if (c == '_' || iswalnum(c)) + right = true; + } + + if ((left == right) != inverse) + return -1; + + return next ? next->test(s, pos) : pos; + } +}; + +struct RegExNodeQuantifier : public RegExNode { + + int min; + int max; + bool greedy; + RegExNode* child; + + RegExNodeQuantifier(int p_min, int p_max) { + + min = p_min; + max = p_max; + greedy = true; + child = NULL; + } + + ~RegExNodeQuantifier() { + + if (child) + memdelete(child); + } + + virtual int test(RegExSearch& s, int pos) const { + + return test_step(s, pos, 0, pos); + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + s.complete = false; + return pos; + } + + int test_step(RegExSearch& s, int pos, int level, int start) const { + + if (pos > s.end) + return -1; + + if (!greedy && level > min) { + int res = next ? next->test(s, pos) : pos; + if (s.complete) + return res; + + if (res >= 0 && parent->test_parent(s, res) >= 0) + return res; + } + + if (max >= 0 && level > max) + return -1; + + int res = pos; + if (level >= 1) { + if (level > min + 1 && pos == start) + return -1; + + res = child->test(s, pos); + if (s.complete) + return res; + } + + if (res >= 0) { + + int res_step = test_step(s, res, level + 1, start); + if (res_step >= 0) + return res_step; + + if (greedy && level >= min) { + if (next) + res = next->test(s, res); + if (s.complete) + return res; + + if (res >= 0 && parent->test_parent(s, res) >= 0) + return res; + } + } + return -1; + } +}; + +struct RegExNodeBackReference : public RegExNode { + + int id; + + RegExNodeBackReference(int p_id) { + + length = -1; + quantifiable = true; + id = p_id; + } + + virtual int test(RegExSearch& s, int pos) const { + + RegExMatch::Group& ref = s.match->captures[id]; + for (int i = 0; i < ref.length; ++i) { + + if (pos + i >= s.end) + return -1; + + if (s.at(ref.start + i) != s.at(pos + i)) + return -1; + } + return next ? next->test(s, pos + ref.length) : pos + ref.length; + } +}; + + +struct RegExNodeGroup : public RegExNode { + + bool inverse; + bool reset_pos; + Vector<RegExNode*> childset; + RegExNode* back; + + RegExNodeGroup() { + + length = 0; + quantifiable = true; + inverse = false; + reset_pos = false; + back = NULL; + } + + virtual ~RegExNodeGroup() { + + for (int i = 0; i < childset.size(); ++i) + memdelete(childset[i]); + } + + virtual int test(RegExSearch& s, int pos) const { + + for (int i = 0; i < childset.size(); ++i) { + + s.complete = false; + + int res = childset[i]->test(s, pos); + + if (s.complete) + return res; + + if (inverse) { + if (res < 0) + res = pos + 1; + else + return -1; + + if (i + 1 < childset.size()) + continue; + } + + if (res >= 0) { + if (reset_pos) + res = pos; + return next ? next->test(s, res) : res; + } + } + return -1; + } + + void add_child(RegExNode* node) { + + node->parent = this; + node->previous = back; + + if (back) + back->next = node; + else + childset.push_back(node); + + increment_length(node->length); + + back = node; + } + + void add_childset() { + + if (childset.size() > 0) + length = -1; + back = NULL; + } + + RegExNode* swap_back(RegExNode* node) { + + RegExNode* old = back; + + if (old) { + if (!old->previous) + childset.remove(childset.size() - 1); + back = old->previous; + increment_length(old->length, true); + } + + add_child(node); + + return old; + } +}; + +struct RegExNodeCapturing : public RegExNodeGroup { + + int id; + + RegExNodeCapturing(int p_id = 0) { + + id = p_id; + } + + virtual int test(RegExSearch& s, int pos) const { + + RegExMatch::Group& ref = s.match->captures[id]; + int old_start = ref.start; + ref.start = pos; + + int res = RegExNodeGroup::test(s, pos); + + if (res >= 0) { + if (!s.complete) + ref.length = res - pos; + } else { + ref.start = old_start; + } + + return res; + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + RegExMatch::Group& ref = s.match->captures[id]; + ref.length = pos - ref.start; + return RegExNode::test_parent(s, pos); + } + + static Variant parse_name(const CharType*& c, bool p_allow_numeric) { + + if (c[1] == '0') { + return -1; + } else if ('1' <= c[1] && c[1] <= '9') { + if (!p_allow_numeric) + return -1; + int res = (++c)[0] - '0'; + while ('0' <= c[1] && c[1] <= '9') + res = res * 10 + int((++c)[0] - '0'); + if ((++c)[0] != '>') + return -1; + return res; + } else if (iswalnum(c[1])) { + String res(++c, 1); + while (iswalnum(c[1])) + res += String(++c, 1); + if ((++c)[0] != '>') + return -1; + return res; + } + return -1; + } +}; + +struct RegExNodeLookAhead : public RegExNodeGroup { + + int id; + + RegExNodeLookAhead(bool p_inverse, int p_id = 0) { + + quantifiable = false; + inverse = p_inverse; + reset_pos = true; + id = p_id; + } + + virtual int test(RegExSearch& s, int pos) const { + + s.lookahead_pos[id] = pos; + return RegExNodeGroup::test(s, pos); + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + return RegExNode::test_parent(s, s.lookahead_pos[id]); + } +}; + +struct RegExNodeLookBehind : public RegExNodeGroup { + + RegExNodeLookBehind(bool p_inverse, int p_id = 0) { + + quantifiable = false; + inverse = p_inverse; + reset_pos = true; + } + + virtual bool is_look_behind() { return true; } + + virtual int test(RegExSearch& s, int pos) const { + + if (pos < length) + return -1; + return RegExNodeGroup::test(s, pos - length); + } +}; + +struct RegExNodeBracket : public RegExNode { + + bool inverse; + Vector<RegExNode*> children; + + RegExNodeBracket() { + + length = 1; + quantifiable = true; + inverse = false; + } + + virtual ~RegExNodeBracket() { + + for (int i = 0; i < children.size(); ++i) + memdelete(children[i]); + } + + virtual int test(RegExSearch& s, int pos) const { + + for (int i = 0; i < children.size(); ++i) { + + int res = children[i]->test(s, pos); + + if (inverse) { + if (res < 0) + res = pos + 1; + else + return -1; + + if (i + 1 < children.size()) + continue; + } + + if (res >= 0) + return next ? next->test(s, res) : res; + } + return -1; + } + + void add_child(RegExNode* node) { + + node->parent = this; + children.push_back(node); + } + + void pop_back() { + + memdelete(children[children.size() - 1]); + children.remove(children.size() - 1); + } +}; + +#define REGEX_EXPAND_FAIL(MSG)\ +{\ + ERR_PRINT(MSG);\ + return String();\ +} + +String RegExMatch::expand(const String& p_template) const { + + String res; + for (const CharType* c = p_template.c_str(); *c != '\0'; ++c) { + if (c[0] == '\\') { + if (('1' <= c[1] && c[1] <= '9') || (c[1] == 'g' && c[2] == '{')) { + + int ref = 0; + bool unclosed = false; + + if (c[1] == 'g') { + unclosed = true; + c = &c[2]; + } + + while ('0' <= c[1] && c[1] <= '9') { + ref = ref * 10 + int(c[1] - '0'); + ++c; + } + + if (unclosed) { + if (c[1] != '}') + REGEX_EXPAND_FAIL("unclosed backreference '{'"); + ++c; + } + + res += get_string(ref); + + } else if (c[1] =='g' && c[2] == '<') { + + const CharType* d = &c[2]; + + Variant name = RegExNodeCapturing::parse_name(d, true); + if (name == Variant(-1)) + REGEX_EXPAND_FAIL("unrecognised character for group name"); + + c = d; + + res += get_string(name); + + } else { + + const CharType* d = c; + CharType ch = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_EXPAND_FAIL("invalid escape token"); + res += String(&ch, 1); + c = d; + } + } else { + res += String(c, 1); + } + } + return res; +} + +int RegExMatch::get_group_count() const { + + int count = 0; + for (int i = 1; i < captures.size(); ++i) + if (captures[i].name.get_type() == Variant::INT) + ++count; + return count; +} + +Array RegExMatch::get_group_array() const { + + Array res; + for (int i = 1; i < captures.size(); ++i) { + const RegExMatch::Group& capture = captures[i]; + if (capture.name.get_type() != Variant::INT) + continue; + + if (capture.start >= 0) + res.push_back(string.substr(capture.start, capture.length)); + else + res.push_back(String()); + } + return res; +} + +Array RegExMatch::get_names() const { + + Array res; + for (int i = 1; i < captures.size(); ++i) + if (captures[i].name.get_type() == Variant::STRING) + res.push_back(captures[i].name); + return res; +} + +Dictionary RegExMatch::get_name_dict() const { + + Dictionary res; + for (int i = 1; i < captures.size(); ++i) { + const RegExMatch::Group& capture = captures[i]; + if (capture.name.get_type() != Variant::STRING) + continue; + + if (capture.start >= 0) + res[capture.name] = string.substr(capture.start, capture.length); + else + res[capture.name] = String(); + } + return res; +} + +String RegExMatch::get_string(const Variant& p_name) const { + + for (int i = 0; i < captures.size(); ++i) { + + const RegExMatch::Group& capture = captures[i]; + + if (capture.name != p_name) + continue; + + if (capture.start == -1) + return String(); + + return string.substr(capture.start, capture.length); + } + return String(); +} + +int RegExMatch::get_start(const Variant& p_name) const { + + for (int i = 0; i < captures.size(); ++i) + if (captures[i].name == p_name) + return captures[i].start; + return -1; +} + +int RegExMatch::get_end(const Variant& p_name) const { + + for (int i = 0; i < captures.size(); ++i) + if (captures[i].name == p_name) + return captures[i].start + captures[i].length; + return -1; +} + +RegExMatch::RegExMatch() { + +} + +static bool RegEx_is_shorthand(CharType ch) { + + switch (ch) { + case 'w': + case 'W': + case 'd': + case 'D': + case 's': + case 'S': + return true; + default: + break; + } + return false; +} + +#define REGEX_COMPILE_FAIL(MSG)\ +{\ + ERR_PRINT(MSG);\ + clear();\ + return FAILED;\ +} + +Error RegEx::compile(const String& p_pattern) { + + ERR_FAIL_COND_V(p_pattern.length() == 0, FAILED); + + if (pattern == p_pattern && root) + return OK; + + clear(); + pattern = p_pattern; + group_names.push_back(0); + RegExNodeGroup* root_group = memnew(RegExNodeCapturing(0)); + root = root_group; + Vector<RegExNodeGroup*> stack; + stack.push_back(root_group); + int lookahead_level = 0; + int numeric_groups = 0; + const int numeric_max = 9; + + for (const CharType* c = p_pattern.c_str(); *c != '\0'; ++c) { + + switch (c[0]) { + case '(': + if (c[1] == '?') { + + RegExNodeGroup* group = NULL; + switch (c[2]) { + case ':': + c = &c[2]; + group = memnew(RegExNodeGroup()); + break; + case '!': + case '=': + group = memnew(RegExNodeLookAhead((c[2] == '!'), lookahead_level++)); + if (lookahead_depth < lookahead_level) + lookahead_depth = lookahead_level; + c = &c[2]; + break; + case '<': + if (c[3] == '!' || c[3] == '=') { + group = memnew(RegExNodeLookBehind((c[3] == '!'), lookahead_level++)); + c = &c[3]; + } + break; + case 'P': + if (c[3] == '<') { + const CharType* d = &c[3]; + Variant name = RegExNodeCapturing::parse_name(d, false); + if (name == Variant(-1)) + REGEX_COMPILE_FAIL("unrecognised character for group name"); + group = memnew(RegExNodeCapturing(group_names.size())); + group_names.push_back(name); + c = d; + } + default: + break; + } + if (!group) + REGEX_COMPILE_FAIL("unrecognised qualifier for group"); + stack[0]->add_child(group); + stack.insert(0, group); + + } else if (numeric_groups < numeric_max) { + + RegExNodeCapturing* group = memnew(RegExNodeCapturing(group_names.size())); + group_names.push_back(++numeric_groups); + stack[0]->add_child(group); + stack.insert(0, group); + + } else { + + RegExNodeGroup* group = memnew(RegExNodeGroup()); + stack[0]->add_child(group); + stack.insert(0, group); + } + break; + case ')': + if (stack.size() == 1) + REGEX_COMPILE_FAIL("unexpected ')'"); + stack.remove(0); + break; + case '\\': + if (('1' <= c[1] && c[1] <= '9') || (c[1] == 'g' && c[2] == '{')) { + + int ref = 0; + bool unclosed = false; + + if (c[1] == 'g') { + unclosed = true; + c = &c[2]; + } + + while ('0' <= c[1] && c[1] <= '9') { + ref = ref * 10 + int(c[1] - '0'); + ++c; + } + + if (unclosed) { + if (c[1] != '}') + REGEX_COMPILE_FAIL("unclosed backreference '{'"); + ++c; + } + + if (ref > numeric_groups || ref <= 0) + REGEX_COMPILE_FAIL("backreference not found"); + + for (int i = 0; i < stack.size(); ++i) + if (stack[i]->is_look_behind()) + REGEX_COMPILE_FAIL("backreferences inside lookbehind not supported"); + + for (int i = 0; i < group_names.size(); ++i) { + if (group_names[i].get_type() == Variant::INT && int(group_names[i]) == ref) { + ref = group_names[i]; + break; + } + } + + stack[0]->add_child(memnew(RegExNodeBackReference(ref))); + + } if (c[1] =='g' && c[2] == '<') { + + const CharType* d = &c[2]; + + Variant name = RegExNodeCapturing::parse_name(d, true); + if (name == Variant(-1)) + REGEX_COMPILE_FAIL("unrecognised character for group name"); + + c = d; + + for (int i = 0; i < stack.size(); ++i) + if (stack[i]->is_look_behind()) + REGEX_COMPILE_FAIL("backreferences inside lookbehind not supported"); + + int ref = -1; + + for (int i = 0; i < group_names.size(); ++i) { + if (group_names[i].get_type() == Variant::INT && int(group_names[i]) == ref) { + ref = group_names[i]; + break; + } + } + + if (ref == -1) + REGEX_COMPILE_FAIL("backreference not found"); + + stack[0]->add_child(memnew(RegExNodeBackReference(ref))); + + } else if (c[1] == 'b' || c[1] == 'B') { + + stack[0]->add_child(memnew(RegExNodeWordBoundary(*(++c) == 'B'))); + + } else if (RegEx_is_shorthand(c[1])) { + + stack[0]->add_child(memnew(RegExNodeShorthand(*(++c)))); + + } else { + + const CharType* d = c; + CharType ch = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_COMPILE_FAIL("invalid escape token"); + stack[0]->add_child(memnew(RegExNodeChar(ch))); + c = d; + + } + break; + case '[': + { + RegExNodeBracket* bracket = memnew(RegExNodeBracket()); + stack[0]->add_child(bracket); + if (c[1] == '^') { + bracket->inverse = true; + ++c; + } + bool first_child = true; + CharType previous_child; + bool previous_child_single = false; + while (true) { + ++c; + if (!first_child && c[0] == ']') { + + break; + + } else if (c[0] == '\0') { + + REGEX_COMPILE_FAIL("unclosed bracket expression '['"); + + } else if (c[0] == '\\') { + + if (RegEx_is_shorthand(c[1])) { + bracket->add_child(memnew(RegExNodeShorthand(*(++c)))); + } else { + const CharType* d = c; + CharType ch = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_COMPILE_FAIL("invalid escape token"); + bracket->add_child(memnew(RegExNodeChar(ch))); + c = d; + previous_child = ch; + previous_child_single = true; + } + + } else if (c[0] == ']' && c[1] == ':') { + + const CharType* d = &c[2]; + RegExNodeClass::Type type = RegExNodeClass::parse_type(d); + if (type != RegExNodeClass::Type_none) { + + c = d; + previous_child_single = false; + + } else { + + bracket->add_child(memnew(RegExNodeChar('['))); + previous_child = '['; + previous_child_single = true; + } + } else if (previous_child_single && c[0] == '-') { + + if (c[1] != '\0' && c[1] != ']') { + + CharType next; + + if (c[1] == '\\') { + const CharType* d = ++c; + next = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_COMPILE_FAIL("invalid escape token"); + } else { + next = *(++c); + } + + if (next < previous_child) + REGEX_COMPILE_FAIL("text range out of order"); + + bracket->pop_back(); + bracket->add_child(memnew(RegExNodeRange(previous_child, next))); + previous_child_single = false; + } else { + + bracket->add_child(memnew(RegExNodeChar('-'))); + previous_child = '-'; + previous_child_single = true; + } + } else { + + bracket->add_child(memnew(RegExNodeChar(c[0]))); + previous_child = c[0]; + previous_child_single = true; + } + first_child = false; + } + } + break; + case '|': + for (int i = 0; i < stack.size(); ++i) + if (stack[i]->is_look_behind()) + REGEX_COMPILE_FAIL("alternations inside lookbehind not supported"); + stack[0]->add_childset(); + break; + case '^': + stack[0]->add_child(memnew(RegExNodeAnchorStart())); + break; + case '$': + stack[0]->add_child(memnew(RegExNodeAnchorEnd())); + break; + case '.': + stack[0]->add_child(memnew(RegExNodeShorthand('.'))); + break; + case '?': + case '*': + case '+': + case '{': + { + int min_val = 0; + int max_val = -1; + bool valid = true; + const CharType* d = c; + bool max_set = true; + switch (c[0]) { + case '?': + min_val = 0; + max_val = 1; + break; + case '*': + min_val = 0; + max_val = -1; + break; + case '+': + min_val = 1; + max_val = -1; + break; + case '{': + max_set = false; + while (valid) { + ++d; + if (d[0] == '}') { + break; + } else if (d[0] == ',') { + max_set = true; + } else if ('0' <= d[0] && d[0] <= '9') { + if (max_set) { + if (max_val < 0) + max_val = int(d[0] - '0'); + else + max_val = max_val * 10 + int(d[0] - '0'); + } else { + min_val = min_val * 10 + int(d[0] - '0'); + } + } else { + valid = false; + } + } + break; + default: + break; + } + + if (!max_set) + max_val = min_val; + + if (valid) { + + c = d; + + if (stack[0]->back == NULL || !stack[0]->back->quantifiable) + REGEX_COMPILE_FAIL("element not quantifiable"); + + if (min_val != max_val) + for (int i = 0; i < stack.size(); ++i) + if (stack[i]->is_look_behind()) + REGEX_COMPILE_FAIL("variable length quantifiers inside lookbehind not supported"); + + RegExNodeQuantifier* quant = memnew(RegExNodeQuantifier(min_val, max_val)); + quant->child = stack[0]->swap_back(quant); + quant->child->previous = NULL; + quant->child->parent = quant; + + if (min_val == max_val && quant->child->length >= 0) + quant->length = max_val * quant->child->length; + + if (c[1] == '?') { + quant->greedy = false; + ++c; + } + break; + } + } + default: + stack[0]->add_child(memnew(RegExNodeChar(c[0]))); + break; + } + } + if (stack.size() > 1) + REGEX_COMPILE_FAIL("unclosed group '('"); + return OK; +} + +Ref<RegExMatch> RegEx::search(const String& p_text, int p_start, int p_end) const { + + ERR_FAIL_COND_V(!is_valid(), NULL); + ERR_FAIL_COND_V(p_start < 0, NULL); + ERR_FAIL_COND_V(p_start >= p_text.length(), NULL); + ERR_FAIL_COND_V(p_end > p_text.length(), NULL); + ERR_FAIL_COND_V(p_end != -1 && p_end < p_start, NULL); + + Ref<RegExMatch> res = memnew(RegExMatch()); + + for (int i = 0; i < group_names.size(); ++i) { + RegExMatch::Group group; + group.name = group_names[i]; + res->captures.push_back(group); + } + + res->string = p_text; + + if (p_end == -1) + p_end = p_text.length(); + + RegExSearch s(res, p_end, lookahead_depth); + + for (int i = p_start; i <= s.end; ++i) { + for (int c = 0; c < group_names.size(); ++c) { + res->captures[c].start = -1; + res->captures[c].length = 0; + } + if (root->test(s, i) >= 0) + break; + } + + if (res->captures[0].start >= 0) + return res; + return NULL; +} + +String RegEx::sub(const String& p_text, const String& p_replacement, bool p_all, int p_start, int p_end) const { + + ERR_FAIL_COND_V(!is_valid(), p_text); + ERR_FAIL_COND_V(p_start < 0, p_text); + ERR_FAIL_COND_V(p_start >= p_text.length(), p_text); + ERR_FAIL_COND_V(p_end > p_text.length(), p_text); + ERR_FAIL_COND_V(p_end != -1 && p_end < p_start, p_text); + + String text = p_text; + int start = p_start; + + if (p_end == -1) + p_end = p_text.length(); + + while (start < text.length() && (p_all || start == p_start)) { + + Ref<RegExMatch> m = search(text, start, p_end); + + RegExMatch::Group& s = m->captures[0]; + + if (s.start < 0) + break; + + String res = text.substr(0, s.start) + m->expand(p_replacement); + + start = res.length(); + + if (s.length == 0) + ++start; + + int sub_end = s.start + s.length; + if (sub_end < text.length()) + res += text.substr(sub_end, text.length() - sub_end); + + p_end += res.length() - text.length(); + + text = res; + } + return text; +} + +void RegEx::clear() { + + if (root) + memdelete(root); + + root = NULL; + group_names.clear(); + lookahead_depth = 0; +} + +bool RegEx::is_valid() const { + + return (root != NULL); +} + +String RegEx::get_pattern() const { + + return pattern; +} + +int RegEx::get_group_count() const { + + int count = 0; + for (int i = 1; i < group_names.size(); ++i) + if (group_names[i].get_type() == Variant::INT) + ++count; + return count; +} + +Array RegEx::get_names() const { + + Array res; + for (int i = 1; i < group_names.size(); ++i) + if (group_names[i].get_type() == Variant::STRING) + res.push_back(group_names[i]); + return res; +} + +RegEx::RegEx() { + + root = NULL; + lookahead_depth = 0; +} + +RegEx::RegEx(const String& p_pattern) { + + root = NULL; + compile(p_pattern); +} + +RegEx::~RegEx() { + + if (root) + memdelete(root); +} + +void RegExMatch::_bind_methods() { + + ClassDB::bind_method(_MD("expand","template"),&RegExMatch::expand); + ClassDB::bind_method(_MD("get_group_count"),&RegExMatch::get_group_count); + ClassDB::bind_method(_MD("get_group_array"),&RegExMatch::get_group_array); + ClassDB::bind_method(_MD("get_names"),&RegExMatch::get_names); + ClassDB::bind_method(_MD("get_name_dict"),&RegExMatch::get_name_dict); + ClassDB::bind_method(_MD("get_string","name"),&RegExMatch::get_string, DEFVAL(0)); + ClassDB::bind_method(_MD("get_start","name"),&RegExMatch::get_start, DEFVAL(0)); + ClassDB::bind_method(_MD("get_end","name"),&RegExMatch::get_end, DEFVAL(0)); +} + +void RegEx::_bind_methods() { + + ClassDB::bind_method(_MD("clear"),&RegEx::clear); + ClassDB::bind_method(_MD("compile","pattern"),&RegEx::compile); + ClassDB::bind_method(_MD("search","text","start","end"),&RegEx::search, DEFVAL(0), DEFVAL(-1)); + ClassDB::bind_method(_MD("sub","text","replacement","all","start","end"),&RegEx::sub, DEFVAL(false), DEFVAL(0), DEFVAL(-1)); + ClassDB::bind_method(_MD("is_valid"),&RegEx::is_valid); + ClassDB::bind_method(_MD("get_pattern"),&RegEx::get_pattern); + ClassDB::bind_method(_MD("get_group_count"),&RegEx::get_group_count); + ClassDB::bind_method(_MD("get_names"),&RegEx::get_names); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "pattern"), _SCS("compile"), _SCS("get_pattern")); +} + diff --git a/modules/regex/regex.h b/modules/regex/regex.h new file mode 100644 index 000000000..193d818da --- /dev/null +++ b/modules/regex/regex.h @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* regex.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef REGEX_H +#define REGEX_H + +#include "core/vector.h" +#include "core/ustring.h" +#include "core/dictionary.h" +#include "core/reference.h" +#include "core/resource.h" + +class RegExNode; + +class RegExMatch : public Reference { + + GDCLASS(RegExMatch, Reference); + + struct Group { + Variant name; + int start; + int length; + }; + + Vector<Group> captures; + String string; + + friend class RegEx; + friend class RegExSearch; + friend class RegExNodeCapturing; + friend class RegExNodeBackReference; + +protected: + + static void _bind_methods(); + +public: + + String expand(const String& p_template) const; + + int get_group_count() const; + Array get_group_array() const; + + Array get_names() const; + Dictionary get_name_dict() const; + + String get_string(const Variant& p_name) const; + int get_start(const Variant& p_name) const; + int get_end(const Variant& p_name) const; + + RegExMatch(); + +}; + +class RegEx : public Resource { + + GDCLASS(RegEx, Resource); + + RegExNode* root; + Vector<Variant> group_names; + String pattern; + int lookahead_depth; + +protected: + + static void _bind_methods(); + +public: + + void clear(); + Error compile(const String& p_pattern); + + Ref<RegExMatch> search(const String& p_text, int p_start = 0, int p_end = -1) const; + String sub(const String& p_text, const String& p_replacement, bool p_all = false, int p_start = 0, int p_end = -1) const; + + bool is_valid() const; + String get_pattern() const; + int get_group_count() const; + Array get_names() const; + + RegEx(); + RegEx(const String& p_pattern); + ~RegEx(); + +}; + +#endif // REGEX_H + diff --git a/modules/ik/register_types.cpp b/modules/regex/register_types.cpp index e7df7f55b..d44c7e563 100644 --- a/modules/ik/register_types.cpp +++ b/modules/regex/register_types.cpp @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -26,22 +26,18 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ + #include "register_types.h" -#ifndef _3D_DISABLED #include "object_type_db.h" -#include "ik.h" -#endif +#include "regex.h" -void register_ik_types() { +void register_regex_types() { -#ifndef _3D_DISABLED - ObjectTypeDB::register_type<InverseKinematics>(); -#endif + ClassDB::register_class<RegExMatch>(); + ClassDB::register_class<RegEx>(); } - - -void unregister_ik_types() { - +void unregister_regex_types() { } + diff --git a/modules/gdscript/gd_pretty_print.cpp b/modules/regex/register_types.h index cca3cd398..5d676b6da 100644 --- a/modules/gdscript/gd_pretty_print.cpp +++ b/modules/regex/register_types.h @@ -1,11 +1,11 @@ /*************************************************************************/ -/* gd_pretty_print.cpp */ +/* register_types.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -26,9 +26,6 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gd_pretty_print.h" -GDPrettyPrint::GDPrettyPrint() { - - -} +void register_regex_types(); +void unregister_regex_types(); diff --git a/modules/register_module_types.h b/modules/register_module_types.h index 3cc0422d8..7d9a130ea 100644 --- a/modules/register_module_types.h +++ b/modules/register_module_types.h @@ -1,3 +1,31 @@ +/*************************************************************************/ +/* register_module_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ #ifndef REGISTER_MODULE_TYPES_H #define REGISTER_MODULE_TYPES_H diff --git a/modules/squish/SCsub b/modules/squish/SCsub new file mode 100644 index 000000000..cca7c038f --- /dev/null +++ b/modules/squish/SCsub @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_squish = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_squish'] != 'no'): + thirdparty_dir = "#thirdparty/squish/" + thirdparty_sources = [ + "alpha.cpp", + "clusterfit.cpp", + "colourblock.cpp", + "colourfit.cpp", + "colourset.cpp", + "maths.cpp", + "rangefit.cpp", + "singlecolourfit.cpp", + "squish.cpp", + ] + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_squish.add_source_files(env.modules_sources, thirdparty_sources) + env_squish.Append(CPPPATH=[thirdparty_dir]) + +# Godot source files +env_squish.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/squish/config.py b/modules/squish/config.py new file mode 100644 index 000000000..cc8f09801 --- /dev/null +++ b/modules/squish/config.py @@ -0,0 +1,11 @@ + +def can_build(platform): + return True + + +def configure(env): + # Tools only, disabled for non-tools + # TODO: Find a cleaner way to achieve that + if (env["tools"] == "no"): + env["module_squish_enabled"] = "no" + env.disabled_modules.append("squish") diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_compress_squish.cpp new file mode 100644 index 000000000..741065860 --- /dev/null +++ b/modules/squish/image_compress_squish.cpp @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* image_compress_squish.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "image_compress_squish.h" + +#include "print_string.h" + +#include <squish.h> + +void image_compress_squish(Image *p_image) { + + int w=p_image->get_width(); + int h=p_image->get_height(); + + if (!p_image->has_mipmaps() ) { + ERR_FAIL_COND( !w || w % 4 != 0); + ERR_FAIL_COND( !h || h % 4 != 0); + } else { + ERR_FAIL_COND( !w || w !=nearest_power_of_2(w) ); + ERR_FAIL_COND( !h || h !=nearest_power_of_2(h) ); + }; + + if (p_image->get_format()>=Image::FORMAT_DXT1) + return; //do not compress, already compressed + + int shift=0; + int squish_comp=squish::kColourRangeFit; + Image::Format target_format; + + if (p_image->get_format()==Image::FORMAT_LA8) { + //compressed normalmap + target_format = Image::FORMAT_DXT5; squish_comp|=squish::kDxt5;; + } else if (p_image->detect_alpha()!=Image::ALPHA_NONE) { + + target_format = Image::FORMAT_DXT3; squish_comp|=squish::kDxt3;; + } else { + target_format = Image::FORMAT_DXT1; shift=1; squish_comp|=squish::kDxt1;; + } + + p_image->convert(Image::FORMAT_RGBA8); //always expects rgba + + int mm_count = p_image->get_mipmap_count(); + + PoolVector<uint8_t> data; + int target_size = Image::get_image_data_size(w,h,target_format,mm_count); + data.resize(target_size); + + PoolVector<uint8_t>::Read rb = p_image->get_data().read(); + PoolVector<uint8_t>::Write wb = data.write(); + + int dst_ofs=0; + + for(int i=0;i<=mm_count;i++) { + + int src_ofs = p_image->get_mipmap_offset(i); + squish::CompressImage( &rb[src_ofs],w,h,&wb[dst_ofs],squish_comp); + dst_ofs+=(MAX(4,w)*MAX(4,h))>>shift; + w>>=1; + h>>=1; + } + + rb = PoolVector<uint8_t>::Read(); + wb = PoolVector<uint8_t>::Write(); + + p_image->create(p_image->get_width(),p_image->get_height(),p_image->has_mipmaps(),target_format,data); + +} diff --git a/modules/squish/image_compress_squish.h b/modules/squish/image_compress_squish.h new file mode 100644 index 000000000..198889402 --- /dev/null +++ b/modules/squish/image_compress_squish.h @@ -0,0 +1,36 @@ +/*************************************************************************/ +/* image_compress_squish.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef IMAGE_COMPRESS_SQUISH_H +#define IMAGE_COMPRESS_SQUISH_H + +#include "image.h" + +void image_compress_squish(Image *p_image); + +#endif // IMAGE_COMPRESS_SQUISH_H diff --git a/modules/squish/register_types.cpp b/modules/squish/register_types.cpp new file mode 100644 index 000000000..995711c75 --- /dev/null +++ b/modules/squish/register_types.cpp @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#ifdef TOOLS_ENABLED + +#include "image_compress_squish.h" + +void register_squish_types() { + + Image::set_compress_bc_func(image_compress_squish); +} + +void unregister_squish_types() {} + +#endif diff --git a/modules/squish/register_types.h b/modules/squish/register_types.h new file mode 100644 index 000000000..0db430199 --- /dev/null +++ b/modules/squish/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef TOOLS_ENABLED +void register_squish_types(); +void unregister_squish_types(); +#endif diff --git a/modules/theora/SCsub b/modules/theora/SCsub new file mode 100644 index 000000000..2de4d2964 --- /dev/null +++ b/modules/theora/SCsub @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_theora = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_libtheora'] != 'no'): + thirdparty_dir = "#thirdparty/libtheora/" + thirdparty_sources = [ + #"analyze.c", + #"apiwrapper.c", + "bitpack.c", + "cpu.c", + #"decapiwrapper.c", + "decinfo.c", + "decode.c", + "dequant.c", + #"encapiwrapper.c", + #"encfrag.c", + #"encinfo.c", + #"encode.c", + #"encoder_disabled.c", + #"enquant.c", + #"fdct.c", + "fragment.c", + "huffdec.c", + #"huffenc.c", + "idct.c", + "info.c", + "internal.c", + #"mathops.c", + #"mcenc.c", + "quant.c", + #"rate.c", + "state.c", + #"tokenize.c", + ] + + thirdparty_sources_x86 = [ + #"x86/mmxencfrag.c", + #"x86/mmxfdct.c", + "x86/mmxfrag.c", + "x86/mmxidct.c", + "x86/mmxstate.c", + #"x86/sse2fdct.c", + #"x86/x86enc.c", + "x86/x86state.c", + ] + + thirdparty_sources_x86_vc = [ + #"x86_vc/mmxencfrag.c", + #"x86_vc/mmxfdct.c", + "x86_vc/mmxfrag.c", + "x86_vc/mmxidct.c", + "x86_vc/mmxstate.c", + #"x86_vc/x86enc.c", + "x86_vc/x86state.c", + ] + + if (env["x86_libtheora_opt_gcc"]): + thirdparty_sources += thirdparty_sources_x86 + + if (env["x86_libtheora_opt_vc"]): + thirdparty_sources += thirdparty_sources_x86_vc + + if (env["x86_libtheora_opt_gcc"] or env["x86_libtheora_opt_vc"]): + env_theora.Append(CCFLAGS=["-DOC_X86_ASM"]) + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_theora.add_source_files(env.modules_sources, thirdparty_sources) + env_theora.Append(CPPPATH=[thirdparty_dir]) + + # also requires libogg and libvorbis + if (env['builtin_libogg'] != 'no'): + env_theora.Append(CPPPATH=["#thirdparty/libogg"]) + if (env['builtin_libvorbis'] != 'no'): + env_theora.Append(CPPPATH=["#thirdparty/libvorbis"]) + +# Godot source files +env_theora.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/theora/config.py b/modules/theora/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/theora/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/theora/register_types.cpp b/modules/theora/register_types.cpp new file mode 100644 index 000000000..58f63465c --- /dev/null +++ b/modules/theora/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "video_stream_theora.h" + +static ResourceFormatLoaderVideoStreamTheora* theora_stream_loader = NULL; + +void register_theora_types() { + + theora_stream_loader = memnew( ResourceFormatLoaderVideoStreamTheora ); + ResourceLoader::add_resource_format_loader(theora_stream_loader); + ClassDB::register_class<VideoStreamTheora>(); +} + +void unregister_theora_types() { + + memdelete( theora_stream_loader ); +} diff --git a/modules/theora/register_types.h b/modules/theora/register_types.h new file mode 100644 index 000000000..582aa785c --- /dev/null +++ b/modules/theora/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_theora_types(); +void unregister_theora_types(); diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp new file mode 100644 index 000000000..9d2dfc7f5 --- /dev/null +++ b/modules/theora/video_stream_theora.cpp @@ -0,0 +1,943 @@ +/*************************************************************************/ +/* video_stream_theora.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "video_stream_theora.h" + +#include "globals.h" +#include "os/os.h" +#include "yuv2rgb.h" + +int VideoStreamPlaybackTheora:: buffer_data() { + + char *buffer=ogg_sync_buffer(&oy,4096); + +#ifdef THEORA_USE_THREAD_STREAMING + + int read; + + do { + thread_sem->post(); + read = MIN(ring_buffer.data_left(),4096); + if (read) { + ring_buffer.read((uint8_t*)buffer,read); + ogg_sync_wrote(&oy,read); + } else { + OS::get_singleton()->delay_usec(100); + } + + } while(read==0); + + return read; + +#else + + int bytes=file->get_buffer((uint8_t*)buffer, 4096); + ogg_sync_wrote(&oy,bytes); + return(bytes); + +#endif +} + +int VideoStreamPlaybackTheora::queue_page(ogg_page *page){ + if(theora_p) { + ogg_stream_pagein(&to,page); + if (to.e_o_s) + theora_eos=true; + } + if(vorbis_p) { + ogg_stream_pagein(&vo,page); + if (vo.e_o_s) + vorbis_eos=true; + } + return 0; +} + +void VideoStreamPlaybackTheora::video_write(void){ + th_ycbcr_buffer yuv; + th_decode_ycbcr_out(td,yuv); + + // FIXME: The way stuff is commented out with `//*/` closing comments + // sounds very fishy... + + /* + int y_offset, uv_offset; + y_offset=(ti.pic_x&~1)+yuv[0].stride*(ti.pic_y&~1); + + { + int pixels = size.x * size.y; + frame_data.resize(pixels * 4); + PoolVector<uint8_t>::Write w = frame_data.write(); + char* dst = (char*)w.ptr(); + int p = 0; + for (int i=0; i<size.y; i++) { + + char *in_y = (char *)yuv[0].data+y_offset+yuv[0].stride*i; + char *out = dst + (int)size.x * 4 * i; + for (int j=0;j<size.x;j++) { + + dst[p++] = in_y[j]; + dst[p++] = in_y[j]; + dst[p++] = in_y[j]; + dst[p++] = 255; + }; + } + format = Image::FORMAT_RGBA8; + } + //*/ + + //* + + int pitch = 4; + frame_data.resize(size.x * size.y * pitch); + { + PoolVector<uint8_t>::Write w = frame_data.write(); + char* dst = (char*)w.ptr(); + + //uv_offset=(ti.pic_x/2)+(yuv[1].stride)*(ti.pic_y/2); + + if (px_fmt == TH_PF_444) { + + yuv444_2_rgb8888((uint8_t*)dst, (uint8_t*)yuv[0].data, (uint8_t*)yuv[1].data, (uint8_t*)yuv[2].data, size.x, size.y, yuv[0].stride, yuv[1].stride, size.x<<2, 0); + + } else if (px_fmt == TH_PF_422) { + + yuv422_2_rgb8888((uint8_t*)dst, (uint8_t*)yuv[0].data, (uint8_t*)yuv[1].data, (uint8_t*)yuv[2].data, size.x, size.y, yuv[0].stride, yuv[1].stride, size.x<<2, 0); + + } else if (px_fmt == TH_PF_420) { + + yuv420_2_rgb8888((uint8_t*)dst, (uint8_t*)yuv[0].data, (uint8_t*)yuv[2].data, (uint8_t*)yuv[1].data, size.x, size.y, yuv[0].stride, yuv[1].stride, size.x<<2, 0); + }; + + format = Image::FORMAT_RGBA8; + } + + Image img(size.x,size.y,0,Image::FORMAT_RGBA8,frame_data); //zero copy image creation + + texture->set_data(img); //zero copy send to visual server + + /* + + if (px_fmt == TH_PF_444) { + + int pitch = 3; + frame_data.resize(size.x * size.y * pitch); + PoolVector<uint8_t>::Write w = frame_data.write(); + char* dst = (char*)w.ptr(); + + for(int i=0;i<size.y;i++) { + + char *in_y = (char *)yuv[0].data+y_offset+yuv[0].stride*i; + char *out = dst + (int)size.x * pitch * i; + char *in_u = (char *)yuv[1].data+uv_offset+yuv[1].stride*i; + char *in_v = (char *)yuv[2].data+uv_offset+yuv[2].stride*i; + for (int j=0;j<size.x;j++) { + + out[j*3+0] = in_y[j]; + out[j*3+1] = in_u[j]; + out[j*3+2] = in_v[j]; + }; + } + + format = Image::FORMAT_YUV_444; + + } else { + + int div; + if (px_fmt!=TH_PF_422) { + div = 2; + } + + bool rgba = true; + if (rgba) { + + int pitch = 4; + frame_data.resize(size.x * size.y * pitch); + PoolVector<uint8_t>::Write w = frame_data.write(); + char* dst = (char*)w.ptr(); + + uv_offset=(ti.pic_x/2)+(yuv[1].stride)*(ti.pic_y / div); + for(int i=0;i<size.y;i++) { + char *in_y = (char *)yuv[0].data+y_offset+yuv[0].stride*i; + char *in_u = (char *)yuv[1].data+uv_offset+yuv[1].stride*(i/div); + char *in_v = (char *)yuv[2].data+uv_offset+yuv[2].stride*(i/div); + uint8_t *out = (uint8_t*)dst + (int)size.x * pitch * i; + int ofs = 0; + for (int j=0;j<size.x;j++) { + + uint8_t y, u, v; + y = in_y[j]; + u = in_u[j/2]; + v = in_v[j/2]; + + int32_t r = Math::fast_ftoi(1.164 * (y - 16) + 1.596 * (v - 128)); + int32_t g = Math::fast_ftoi(1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128)); + int32_t b = Math::fast_ftoi(1.164 * (y - 16) + 2.018 * (u - 128)); + + out[ofs++] = CLAMP(r, 0, 255); + out[ofs++] = CLAMP(g, 0, 255); + out[ofs++] = CLAMP(b, 0, 255); + out[ofs++] = 255; + } + } + + format = Image::FORMAT_RGBA8; + + } else { + + int pitch = 2; + frame_data.resize(size.x * size.y * pitch); + PoolVector<uint8_t>::Write w = frame_data.write(); + char* dst = (char*)w.ptr(); + + uv_offset=(ti.pic_x/2)+(yuv[1].stride)*(ti.pic_y / div); + for(int i=0;i<size.y;i++) { + char *in_y = (char *)yuv[0].data+y_offset+yuv[0].stride*i; + char *out = dst + (int)size.x * pitch * i; + for (int j=0;j<size.x;j++) + out[j*2] = in_y[j]; + char *in_u = (char *)yuv[1].data+uv_offset+yuv[1].stride*(i/div); + char *in_v = (char *)yuv[2].data+uv_offset+yuv[2].stride*(i/div); + for (int j=0;j<(int)size.x>>1;j++) { + out[j*4+1] = in_u[j]; + out[j*4+3] = in_v[j]; + } + } + + format = Image::FORMAT_YUV_422; + }; + }; + //*/ + + frames_pending = 1; +} + +void VideoStreamPlaybackTheora::clear() { + + if (!file) + return; + + if(vorbis_p){ + ogg_stream_clear(&vo); + if (vorbis_p >= 3) { + vorbis_block_clear(&vb); + vorbis_dsp_clear(&vd); + }; + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); + vorbis_p = 0; + } + if(theora_p){ + ogg_stream_clear(&to); + th_decode_free(td); + th_comment_clear(&tc); + th_info_clear(&ti); + theora_p = 0; + } + ogg_sync_clear(&oy); + +#ifdef THEORA_USE_THREAD_STREAMING + thread_exit=true; + thread_sem->post(); //just in case + Thread::wait_to_finish(thread); + memdelete(thread); + thread=NULL; + ring_buffer.clear(); +#endif + //file_name = ""; + + theora_p = 0; + vorbis_p = 0; + videobuf_ready = 0; + frames_pending = 0; + videobuf_time = 0; + theora_eos=false; + vorbis_eos=false; + + if (file) { + memdelete(file); + } + file=NULL; + playing = false; +}; + +void VideoStreamPlaybackTheora::set_file(const String& p_file) { + + ERR_FAIL_COND(playing); + ogg_packet op; + th_setup_info *ts = NULL; + + file_name = p_file; + if (file) { + memdelete(file); + } + file = FileAccess::open(p_file, FileAccess::READ); + ERR_FAIL_COND(!file); + +#ifdef THEORA_USE_THREAD_STREAMING + thread_exit=false; + thread_eof=false; + //pre-fill buffer + int to_read = ring_buffer.space_left(); + int read = file->get_buffer(read_buffer.ptr(),to_read); + ring_buffer.write(read_buffer.ptr(),read); + + thread=Thread::create(_streaming_thread,this); + +#endif + + ogg_sync_init(&oy); + + /* init supporting Vorbis structures needed in header parsing */ + vorbis_info_init(&vi); + vorbis_comment_init(&vc); + + /* init supporting Theora structures needed in header parsing */ + th_comment_init(&tc); + th_info_init(&ti); + + theora_eos=false; + vorbis_eos=false; + + /* Ogg file open; parse the headers */ + /* Only interested in Vorbis/Theora streams */ + int stateflag = 0; + + int audio_track_skip=audio_track; + + + while(!stateflag){ + int ret=buffer_data(); + if(ret==0)break; + while(ogg_sync_pageout(&oy,&og)>0){ + ogg_stream_state test; + + /* is this a mandated initial header? If not, stop parsing */ + if(!ogg_page_bos(&og)){ + /* don't leak the page; get it into the appropriate stream */ + queue_page(&og); + stateflag=1; + break; + } + + ogg_stream_init(&test,ogg_page_serialno(&og)); + ogg_stream_pagein(&test,&og); + ogg_stream_packetout(&test,&op); + + + /* identify the codec: try theora */ + if(!theora_p && th_decode_headerin(&ti,&tc,&ts,&op)>=0){ + /* it is theora */ + copymem(&to,&test,sizeof(test)); + theora_p=1; + }else if(!vorbis_p && vorbis_synthesis_headerin(&vi,&vc,&op)>=0){ + + + /* it is vorbis */ + if (audio_track_skip) { + vorbis_info_clear(&vi); + vorbis_comment_clear(&vc); + ogg_stream_clear(&test); + vorbis_info_init(&vi); + vorbis_comment_init(&vc); + + audio_track_skip--; + } else { + copymem(&vo,&test,sizeof(test)); + vorbis_p=1; + } + }else{ + /* whatever it is, we don't care about it */ + ogg_stream_clear(&test); + } + } + /* fall through to non-bos page parsing */ + } + + /* we're expecting more header packets. */ + while((theora_p && theora_p<3) || (vorbis_p && vorbis_p<3)){ + int ret; + + /* look for further theora headers */ + while(theora_p && (theora_p<3) && (ret=ogg_stream_packetout(&to,&op))){ + if(ret<0){ + fprintf(stderr,"Error parsing Theora stream headers; " + "corrupt stream?\n"); + clear(); + return; + } + if(!th_decode_headerin(&ti,&tc,&ts,&op)){ + fprintf(stderr,"Error parsing Theora stream headers; " + "corrupt stream?\n"); + clear(); + return; + } + theora_p++; + } + + /* look for more vorbis header packets */ + while(vorbis_p && (vorbis_p<3) && (ret=ogg_stream_packetout(&vo,&op))){ + if(ret<0){ + fprintf(stderr,"Error parsing Vorbis stream headers; corrupt stream?\n"); + clear(); + return; + } + ret = vorbis_synthesis_headerin(&vi,&vc,&op); + if(ret){ + fprintf(stderr,"Error parsing Vorbis stream headers; corrupt stream?\n"); + clear(); + return; + } + vorbis_p++; + if(vorbis_p==3)break; + } + + /* The header pages/packets will arrive before anything else we + care about, or the stream is not obeying spec */ + + if(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); /* demux into the appropriate stream */ + }else{ + int ret=buffer_data(); /* someone needs more data */ + if(ret==0){ + fprintf(stderr,"End of file while searching for codec headers.\n"); + clear(); + return; + } + } + } + + /* and now we have it all. initialize decoders */ + if(theora_p){ + td=th_decode_alloc(&ti,ts); + printf("Ogg logical stream %lx is Theora %dx%d %.02f fps", + to.serialno,ti.pic_width,ti.pic_height, + (double)ti.fps_numerator/ti.fps_denominator); + px_fmt=ti.pixel_fmt; + switch(ti.pixel_fmt){ + case TH_PF_420: printf(" 4:2:0 video\n"); break; + case TH_PF_422: printf(" 4:2:2 video\n"); break; + case TH_PF_444: printf(" 4:4:4 video\n"); break; + case TH_PF_RSVD: + default: + printf(" video\n (UNKNOWN Chroma sampling!)\n"); + break; + } + if(ti.pic_width!=ti.frame_width || ti.pic_height!=ti.frame_height) + printf(" Frame content is %dx%d with offset (%d,%d).\n", + ti.frame_width, ti.frame_height, ti.pic_x, ti.pic_y); + th_decode_ctl(td,TH_DECCTL_GET_PPLEVEL_MAX,&pp_level_max, + sizeof(pp_level_max)); + pp_level=pp_level_max; + pp_level=0; + th_decode_ctl(td,TH_DECCTL_SET_PPLEVEL,&pp_level,sizeof(pp_level)); + pp_inc=0; + + /*{ + int arg = 0xffff; + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_MBMODE,&arg,sizeof(arg)); + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_MV,&arg,sizeof(arg)); + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_QI,&arg,sizeof(arg)); + arg=10; + th_decode_ctl(td,TH_DECCTL_SET_TELEMETRY_BITS,&arg,sizeof(arg)); + }*/ + + int w; + int h; + w=(ti.pic_x+ti.frame_width+1&~1)-(ti.pic_x&~1); + h=(ti.pic_y+ti.frame_height+1&~1)-(ti.pic_y&~1); + size.x = w; + size.y = h; + + texture->create(w,h,Image::FORMAT_RGBA8,Texture::FLAG_FILTER|Texture::FLAG_VIDEO_SURFACE); + + }else{ + /* tear down the partial theora setup */ + th_info_clear(&ti); + th_comment_clear(&tc); + } + + th_setup_free(ts); + + if(vorbis_p){ + vorbis_synthesis_init(&vd,&vi); + vorbis_block_init(&vd,&vb); + fprintf(stderr,"Ogg logical stream %lx is Vorbis %d channel %ld Hz audio.\n", + vo.serialno,vi.channels,vi.rate); + //_setup(vi.channels, vi.rate); + + }else{ + /* tear down the partial vorbis setup */ + vorbis_info_clear(&vi); + vorbis_comment_clear(&vc); + } + + playing = false; + buffering=true; + time=0; + audio_frames_wrote=0; + + +}; + +float VideoStreamPlaybackTheora::get_time() const { + + //print_line("total: "+itos(get_total())+" todo: "+itos(get_todo())); + //return MAX(0,time-((get_total())/(float)vi.rate)); + return time-AudioServer::get_singleton()->get_output_delay()-delay_compensation;//-((get_total())/(float)vi.rate); +}; + +Ref<Texture> VideoStreamPlaybackTheora::get_texture() { + + return texture; +} + +void VideoStreamPlaybackTheora::update(float p_delta) { + + if (!file) + return; + + if (!playing || paused) { + //printf("not playing\n"); + return; + }; + + + +#ifdef THEORA_USE_THREAD_STREAMING + thread_sem->post(); +#endif + + //double ctime =AudioServer::get_singleton()->get_mix_time(); + + //print_line("play "+rtos(p_delta)); + time+=p_delta; + + if (videobuf_time>get_time()) { + return; //no new frames need to be produced + } + + bool frame_done=false; + bool audio_done=!vorbis_p; + + while (!frame_done || (!audio_done && !vorbis_eos)) { + //a frame needs to be produced + + ogg_packet op; + bool no_theora=false; + + + while (vorbis_p) { + int ret; + float **pcm; + + bool buffer_full=false; + + /* if there's pending, decoded audio, grab it */ + if ((ret=vorbis_synthesis_pcmout(&vd,&pcm))>0) { + + + + const int AUXBUF_LEN=4096; + int to_read = ret; + int16_t aux_buffer[AUXBUF_LEN]; + + while(to_read) { + + int m = MIN(AUXBUF_LEN/vi.channels,to_read); + + int count = 0; + + for(int j=0;j<m;j++){ + for(int i=0;i<vi.channels;i++){ + + int val=Math::fast_ftoi(pcm[i][j]*32767.f); + if(val>32767)val=32767; + if(val<-32768)val=-32768; + aux_buffer[count++] = val; + } + } + + if (mix_callback) { + int mixed = mix_callback(mix_udata,aux_buffer,m); + to_read-=mixed; + if (mixed!=m) { //could mix no more + buffer_full=true; + break; + } + } else { + to_read-=m; //just pretend we sent the audio + } + + + } + + + int tr = vorbis_synthesis_read(&vd, ret-to_read); + + + if (vd.granulepos>=0) { + //print_line("wrote: "+itos(audio_frames_wrote)+" gpos: "+itos(vd.granulepos)); + } + + //print_line("mix audio!"); + + audio_frames_wrote+=ret-to_read; + + //print_line("AGP: "+itos(vd.granulepos)+" added "+itos(ret-to_read)); + + + } else { + + /* no pending audio; is there a pending packet to decode? */ + if (ogg_stream_packetout(&vo,&op)>0){ + if(vorbis_synthesis(&vb,&op)==0) { /* test for success! */ + vorbis_synthesis_blockin(&vd,&vb); + } + } else { /* we need more data; break out to suck in another page */ + //printf("need moar data\n"); + break; + }; + } + + + audio_done = videobuf_time < (audio_frames_wrote/float(vi.rate)); + + if (buffer_full) + break; + } + + while(theora_p && !frame_done){ + /* theora is one in, one out... */ + if(ogg_stream_packetout(&to,&op)>0){ + + + if(false && pp_inc){ + pp_level+=pp_inc; + th_decode_ctl(td,TH_DECCTL_SET_PPLEVEL,&pp_level, + sizeof(pp_level)); + pp_inc=0; + } + /*HACK: This should be set after a seek or a gap, but we might not have + a granulepos for the first packet (we only have them for the last + packet on a page), so we just set it as often as we get it. + To do this right, we should back-track from the last packet on the + page and compute the correct granulepos for the first packet after + a seek or a gap.*/ + if(op.granulepos>=0){ + th_decode_ctl(td,TH_DECCTL_SET_GRANPOS,&op.granulepos, + sizeof(op.granulepos)); + } + ogg_int64_t videobuf_granulepos; + if(th_decode_packetin(td,&op,&videobuf_granulepos)==0){ + videobuf_time=th_granule_time(td,videobuf_granulepos); + + //printf("frame time %f, play time %f, ready %i\n", (float)videobuf_time, get_time(), videobuf_ready); + + /* is it already too old to be useful? This is only actually + useful cosmetically after a SIGSTOP. Note that we have to + decode the frame even if we don't show it (for now) due to + keyframing. Soon enough libtheora will be able to deal + with non-keyframe seeks. */ + + if(videobuf_time>=get_time()) { + frame_done=true; + } else{ + /*If we are too slow, reduce the pp level.*/ + pp_inc=pp_level>0?-1:0; + } + } else { + + } + + } else { + no_theora=true; + break; + } + } + + + //print_line("no theora: "+itos(no_theora)+" theora eos: "+itos(theora_eos)+" frame done "+itos(frame_done)); + +#ifdef THEORA_USE_THREAD_STREAMING + if (file && thread_eof && no_theora && theora_eos && ring_buffer.data_left()==0) { +#else + if (file && /*!videobuf_ready && */ no_theora && theora_eos) { +#endif + printf("video done, stopping\n"); + stop(); + return; + }; + #if 0 + if (!videobuf_ready || audio_todo > 0){ + /* no data yet for somebody. Grab another page */ + + buffer_data(); + while(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); + } + } + #else + + + if (!frame_done || !audio_done){ + //what's the point of waiting for audio to grab a page? + + buffer_data(); + while(ogg_sync_pageout(&oy,&og)>0){ + queue_page(&og); + } + } + #endif + /* If playback has begun, top audio buffer off immediately. */ + //if(stateflag) audio_write_nonblocking(); + + /* are we at or past time for this video frame? */ + if(videobuf_ready && videobuf_time<=get_time()){ + + //video_write(); + //videobuf_ready=0; + } else { + //printf("frame at %f not ready (time %f), ready %i\n", (float)videobuf_time, get_time(), videobuf_ready); + } + + float tdiff=videobuf_time-get_time(); + /*If we have lots of extra time, increase the post-processing level.*/ + if(tdiff>ti.fps_denominator*0.25/ti.fps_numerator){ + pp_inc=pp_level<pp_level_max?1:0; + } + else if(tdiff<ti.fps_denominator*0.05/ti.fps_numerator){ + pp_inc=pp_level>0?-1:0; + } + + } + + video_write(); + +}; + + +void VideoStreamPlaybackTheora::play() { + + if (!playing) + time=0; + else { + stop(); + } + + playing = true; + delay_compensation=GlobalConfig::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation/=1000.0; + + +}; + +void VideoStreamPlaybackTheora::stop() { + + if (playing) { + + clear(); + set_file(file_name); //reset + } + playing = false; + time=0; +}; + +bool VideoStreamPlaybackTheora::is_playing() const { + + return playing; +}; + +void VideoStreamPlaybackTheora::set_paused(bool p_paused) { + + paused=p_paused; + //pau = !p_paused; +}; + +bool VideoStreamPlaybackTheora::is_paused(bool p_paused) const { + + return paused; +}; + +void VideoStreamPlaybackTheora::set_loop(bool p_enable) { + +}; + +bool VideoStreamPlaybackTheora::has_loop() const { + + return false; +}; + +float VideoStreamPlaybackTheora::get_length() const { + + return 0; +}; + +String VideoStreamPlaybackTheora::get_stream_name() const { + + return ""; +}; + +int VideoStreamPlaybackTheora::get_loop_count() const { + + return 0; +}; + +float VideoStreamPlaybackTheora::get_pos() const { + + return get_time(); +}; + +void VideoStreamPlaybackTheora::seek_pos(float p_time) { + + // no +}; + +void VideoStreamPlaybackTheora::set_mix_callback(AudioMixCallback p_callback,void *p_userdata) { + + mix_callback=p_callback; + mix_udata=p_userdata; +} + +int VideoStreamPlaybackTheora::get_channels() const{ + + return vi.channels; +} + +void VideoStreamPlaybackTheora::set_audio_track(int p_idx) { + + audio_track=p_idx; +} + +int VideoStreamPlaybackTheora::get_mix_rate() const{ + + return vi.rate; +} + +#ifdef THEORA_USE_THREAD_STREAMING + + +void VideoStreamPlaybackTheora::_streaming_thread(void *ud) { + + VideoStreamPlaybackTheora *vs=(VideoStreamPlaybackTheora*)ud; + + while(!vs->thread_exit) { + + //just fill back the buffer + if (!vs->thread_eof) { + + int to_read = vs->ring_buffer.space_left(); + if (to_read) { + int read = vs->file->get_buffer(vs->read_buffer.ptr(),to_read); + vs->ring_buffer.write(vs->read_buffer.ptr(),read); + vs->thread_eof=vs->file->eof_reached(); + } + + + } + + vs->thread_sem->wait(); + } +} + +#endif + +VideoStreamPlaybackTheora::VideoStreamPlaybackTheora() { + + file = NULL; + theora_p = 0; + vorbis_p = 0; + videobuf_ready = 0; + playing = false; + frames_pending = 0; + videobuf_time = 0; + paused=false; + + buffering=false; + texture = Ref<ImageTexture>( memnew(ImageTexture )); + mix_callback=NULL; + mix_udata=NULL; + audio_track=0; + delay_compensation=0; + audio_frames_wrote=0; + +#ifdef THEORA_USE_THREAD_STREAMING + int rb_power = nearest_shift(RB_SIZE_KB*1024); + ring_buffer.resize(rb_power); + read_buffer.resize(RB_SIZE_KB*1024); + thread_sem=Semaphore::create(); + thread=NULL; + thread_exit=false; + thread_eof=false; + +#endif +}; + +VideoStreamPlaybackTheora::~VideoStreamPlaybackTheora() { + +#ifdef THEORA_USE_THREAD_STREAMING + + memdelete(thread_sem); +#endif + clear(); + + if (file) + memdelete(file); + + +}; + + +RES ResourceFormatLoaderVideoStreamTheora::load(const String &p_path,const String& p_original_path, Error *r_error) { + if (r_error) + *r_error=ERR_FILE_CANT_OPEN; + + VideoStreamTheora *stream = memnew(VideoStreamTheora); + stream->set_file(p_path); + + if (r_error) + *r_error=OK; + + return Ref<VideoStreamTheora>(stream); +} + +void ResourceFormatLoaderVideoStreamTheora::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("ogm"); + p_extensions->push_back("ogv"); +} +bool ResourceFormatLoaderVideoStreamTheora::handles_type(const String& p_type) const { + return (p_type=="VideoStream" || p_type=="VideoStreamTheora"); +} + +String ResourceFormatLoaderVideoStreamTheora::get_resource_type(const String &p_path) const { + + String exl=p_path.get_extension().to_lower(); + if (exl=="ogm" || exl=="ogv") + return "VideoStreamTheora"; + return ""; +} diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h new file mode 100644 index 000000000..0e1f5fa86 --- /dev/null +++ b/modules/theora/video_stream_theora.h @@ -0,0 +1,199 @@ +/*************************************************************************/ +/* video_stream_theora.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef VIDEO_STREAM_THEORA_H +#define VIDEO_STREAM_THEORA_H + +#include "io/resource_loader.h" +#include "os/file_access.h" +#include "os/thread.h" +#include "os/semaphore.h" +#include "ring_buffer.h" +#include "scene/resources/video_stream.h" + +#include <theora/theoradec.h> +#include <vorbis/codec.h> + +//#define THEORA_USE_THREAD_STREAMING + +class VideoStreamPlaybackTheora : public VideoStreamPlayback { + + GDCLASS(VideoStreamPlaybackTheora, VideoStreamPlayback); + + enum { + MAX_FRAMES = 4, + }; + + //Image frames[MAX_FRAMES]; + Image::Format format; + PoolVector<uint8_t> frame_data; + int frames_pending; + FileAccess* file; + String file_name; + int audio_frames_wrote; + Point2i size; + + int buffer_data(); + int queue_page(ogg_page *page); + void video_write(void); + float get_time() const; + + bool theora_eos; + bool vorbis_eos; + + ogg_sync_state oy; + ogg_page og; + ogg_stream_state vo; + ogg_stream_state to; + th_info ti; + th_comment tc; + th_dec_ctx *td; + vorbis_info vi; + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_comment vc; + th_pixel_fmt px_fmt; + double videobuf_time; + int pp_inc; + + int theora_p; + int vorbis_p; + int pp_level_max; + int pp_level; + int videobuf_ready; + + bool playing; + bool buffering; + + double last_update_time; + double time; + double delay_compensation; + + Ref<ImageTexture> texture; + + AudioMixCallback mix_callback; + void* mix_udata; + bool paused; + +#ifdef THEORA_USE_THREAD_STREAMING + + enum { + RB_SIZE_KB=1024 + }; + + RingBuffer<uint8_t> ring_buffer; + Vector<uint8_t> read_buffer; + bool thread_eof; + Semaphore *thread_sem; + Thread *thread; + volatile bool thread_exit; + + static void _streaming_thread(void *ud); + +#endif + + + int audio_track; + +protected: + + void clear(); + +public: + + virtual void play(); + virtual void stop(); + virtual bool is_playing() const; + + virtual void set_paused(bool p_paused); + virtual bool is_paused(bool p_paused) const; + + virtual void set_loop(bool p_enable); + virtual bool has_loop() const; + + virtual float get_length() const; + + virtual String get_stream_name() const; + + virtual int get_loop_count() const; + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + + void set_file(const String& p_file); + + virtual Ref<Texture> get_texture(); + virtual void update(float p_delta); + + virtual void set_mix_callback(AudioMixCallback p_callback,void *p_userdata); + virtual int get_channels() const; + virtual int get_mix_rate() const; + + virtual void set_audio_track(int p_idx); + + VideoStreamPlaybackTheora(); + ~VideoStreamPlaybackTheora(); +}; + + + +class VideoStreamTheora : public VideoStream { + + GDCLASS(VideoStreamTheora,VideoStream); + + String file; + int audio_track; + + +public: + + Ref<VideoStreamPlayback> instance_playback() { + Ref<VideoStreamPlaybackTheora> pb = memnew( VideoStreamPlaybackTheora ); + pb->set_audio_track(audio_track); + pb->set_file(file); + return pb; + } + + void set_file(const String& p_file) { file=p_file; } + void set_audio_track(int p_track) { audio_track=p_track; } + + VideoStreamTheora() { audio_track=0; } + +}; + +class ResourceFormatLoaderVideoStreamTheora : public ResourceFormatLoader { +public: + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + +}; + +#endif diff --git a/modules/theora/yuv2rgb.h b/modules/theora/yuv2rgb.h new file mode 100644 index 000000000..431b47e65 --- /dev/null +++ b/modules/theora/yuv2rgb.h @@ -0,0 +1,1121 @@ +#ifndef YUV2RGB_H +#define YUV2RGB_H + +#include "typedefs.h" + +static const uint32_t tables[256*3] = +{ + /* y_table */ + 0x7FFFFFEDU, + 0x7FFFFFEFU, + 0x7FFFFFF0U, + 0x7FFFFFF1U, + 0x7FFFFFF2U, + 0x7FFFFFF3U, + 0x7FFFFFF4U, + 0x7FFFFFF6U, + 0x7FFFFFF7U, + 0x7FFFFFF8U, + 0x7FFFFFF9U, + 0x7FFFFFFAU, + 0x7FFFFFFBU, + 0x7FFFFFFDU, + 0x7FFFFFFEU, + 0x7FFFFFFFU, + 0x80000000U, + 0x80400801U, + 0x80A01002U, + 0x80E01803U, + 0x81202805U, + 0x81803006U, + 0x81C03807U, + 0x82004008U, + 0x82604809U, + 0x82A0500AU, + 0x82E0600CU, + 0x8340680DU, + 0x8380700EU, + 0x83C0780FU, + 0x84208010U, + 0x84608811U, + 0x84A09813U, + 0x8500A014U, + 0x8540A815U, + 0x8580B016U, + 0x85E0B817U, + 0x8620C018U, + 0x8660D01AU, + 0x86C0D81BU, + 0x8700E01CU, + 0x8740E81DU, + 0x87A0F01EU, + 0x87E0F81FU, + 0x88210821U, + 0x88811022U, + 0x88C11823U, + 0x89012024U, + 0x89412825U, + 0x89A13026U, + 0x89E14028U, + 0x8A214829U, + 0x8A81502AU, + 0x8AC1582BU, + 0x8B01602CU, + 0x8B61682DU, + 0x8BA1782FU, + 0x8BE18030U, + 0x8C418831U, + 0x8C819032U, + 0x8CC19833U, + 0x8D21A034U, + 0x8D61B036U, + 0x8DA1B837U, + 0x8E01C038U, + 0x8E41C839U, + 0x8E81D03AU, + 0x8EE1D83BU, + 0x8F21E83DU, + 0x8F61F03EU, + 0x8FC1F83FU, + 0x90020040U, + 0x90420841U, + 0x90A21042U, + 0x90E22044U, + 0x91222845U, + 0x91823046U, + 0x91C23847U, + 0x92024048U, + 0x92624849U, + 0x92A2504AU, + 0x92E2604CU, + 0x9342684DU, + 0x9382704EU, + 0x93C2784FU, + 0x94228050U, + 0x94628851U, + 0x94A29853U, + 0x9502A054U, + 0x9542A855U, + 0x9582B056U, + 0x95E2B857U, + 0x9622C058U, + 0x9662D05AU, + 0x96C2D85BU, + 0x9702E05CU, + 0x9742E85DU, + 0x97A2F05EU, + 0x97E2F85FU, + 0x98230861U, + 0x98831062U, + 0x98C31863U, + 0x99032064U, + 0x99632865U, + 0x99A33066U, + 0x99E34068U, + 0x9A434869U, + 0x9A83506AU, + 0x9AC3586BU, + 0x9B23606CU, + 0x9B63686DU, + 0x9BA3786FU, + 0x9BE38070U, + 0x9C438871U, + 0x9C839072U, + 0x9CC39873U, + 0x9D23A074U, + 0x9D63B076U, + 0x9DA3B877U, + 0x9E03C078U, + 0x9E43C879U, + 0x9E83D07AU, + 0x9EE3D87BU, + 0x9F23E87DU, + 0x9F63F07EU, + 0x9FC3F87FU, + 0xA0040080U, + 0xA0440881U, + 0xA0A41082U, + 0xA0E42084U, + 0xA1242885U, + 0xA1843086U, + 0xA1C43887U, + 0xA2044088U, + 0xA2644889U, + 0xA2A4588BU, + 0xA2E4608CU, + 0xA344688DU, + 0xA384708EU, + 0xA3C4788FU, + 0xA4248090U, + 0xA4649092U, + 0xA4A49893U, + 0xA504A094U, + 0xA544A895U, + 0xA584B096U, + 0xA5E4B897U, + 0xA624C098U, + 0xA664D09AU, + 0xA6C4D89BU, + 0xA704E09CU, + 0xA744E89DU, + 0xA7A4F09EU, + 0xA7E4F89FU, + 0xA82508A1U, + 0xA88510A2U, + 0xA8C518A3U, + 0xA90520A4U, + 0xA96528A5U, + 0xA9A530A6U, + 0xA9E540A8U, + 0xAA4548A9U, + 0xAA8550AAU, + 0xAAC558ABU, + 0xAB2560ACU, + 0xAB6568ADU, + 0xABA578AFU, + 0xAC0580B0U, + 0xAC4588B1U, + 0xAC8590B2U, + 0xACE598B3U, + 0xAD25A0B4U, + 0xAD65B0B6U, + 0xADA5B8B7U, + 0xAE05C0B8U, + 0xAE45C8B9U, + 0xAE85D0BAU, + 0xAEE5D8BBU, + 0xAF25E8BDU, + 0xAF65F0BEU, + 0xAFC5F8BFU, + 0xB00600C0U, + 0xB04608C1U, + 0xB0A610C2U, + 0xB0E620C4U, + 0xB12628C5U, + 0xB18630C6U, + 0xB1C638C7U, + 0xB20640C8U, + 0xB26648C9U, + 0xB2A658CBU, + 0xB2E660CCU, + 0xB34668CDU, + 0xB38670CEU, + 0xB3C678CFU, + 0xB42680D0U, + 0xB46690D2U, + 0xB4A698D3U, + 0xB506A0D4U, + 0xB546A8D5U, + 0xB586B0D6U, + 0xB5E6B8D7U, + 0xB626C8D9U, + 0xB666D0DAU, + 0xB6C6D8DBU, + 0xB706E0DCU, + 0xB746E8DDU, + 0xB7A6F0DEU, + 0xB7E6F8DFU, + 0xB82708E1U, + 0xB88710E2U, + 0xB8C718E3U, + 0xB90720E4U, + 0xB96728E5U, + 0xB9A730E6U, + 0xB9E740E8U, + 0xBA4748E9U, + 0xBA8750EAU, + 0xBAC758EBU, + 0xBB2760ECU, + 0xBB6768EDU, + 0xBBA778EFU, + 0xBC0780F0U, + 0xBC4788F1U, + 0xBC8790F2U, + 0xBCE798F3U, + 0xBD27A0F4U, + 0xBD67B0F6U, + 0xBDC7B8F7U, + 0xBE07C0F8U, + 0xBE47C8F9U, + 0xBEA7D0FAU, + 0xBEE7D8FBU, + 0xBF27E8FDU, + 0xBF87F0FEU, + 0xBFC7F8FFU, + 0xC0080100U, + 0xC0480901U, + 0xC0A81102U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + 0xC0E82104U, + /* u_table */ + 0x0C400103U, + 0x0C200105U, + 0x0C200107U, + 0x0C000109U, + 0x0BE0010BU, + 0x0BC0010DU, + 0x0BA0010FU, + 0x0BA00111U, + 0x0B800113U, + 0x0B600115U, + 0x0B400117U, + 0x0B400119U, + 0x0B20011BU, + 0x0B00011DU, + 0x0AE0011FU, + 0x0AE00121U, + 0x0AC00123U, + 0x0AA00125U, + 0x0A800127U, + 0x0A600129U, + 0x0A60012BU, + 0x0A40012DU, + 0x0A20012FU, + 0x0A000131U, + 0x0A000132U, + 0x09E00134U, + 0x09C00136U, + 0x09A00138U, + 0x09A0013AU, + 0x0980013CU, + 0x0960013EU, + 0x09400140U, + 0x09400142U, + 0x09200144U, + 0x09000146U, + 0x08E00148U, + 0x08C0014AU, + 0x08C0014CU, + 0x08A0014EU, + 0x08800150U, + 0x08600152U, + 0x08600154U, + 0x08400156U, + 0x08200158U, + 0x0800015AU, + 0x0800015CU, + 0x07E0015EU, + 0x07C00160U, + 0x07A00162U, + 0x07A00164U, + 0x07800166U, + 0x07600168U, + 0x0740016AU, + 0x0720016CU, + 0x0720016EU, + 0x07000170U, + 0x06E00172U, + 0x06C00174U, + 0x06C00176U, + 0x06A00178U, + 0x0680017AU, + 0x0660017CU, + 0x0660017EU, + 0x06400180U, + 0x06200182U, + 0x06000184U, + 0x05E00185U, + 0x05E00187U, + 0x05C00189U, + 0x05A0018BU, + 0x0580018DU, + 0x0580018FU, + 0x05600191U, + 0x05400193U, + 0x05200195U, + 0x05200197U, + 0x05000199U, + 0x04E0019BU, + 0x04C0019DU, + 0x04C0019FU, + 0x04A001A1U, + 0x048001A3U, + 0x046001A5U, + 0x044001A7U, + 0x044001A9U, + 0x042001ABU, + 0x040001ADU, + 0x03E001AFU, + 0x03E001B1U, + 0x03C001B3U, + 0x03A001B5U, + 0x038001B7U, + 0x038001B9U, + 0x036001BBU, + 0x034001BDU, + 0x032001BFU, + 0x032001C1U, + 0x030001C3U, + 0x02E001C5U, + 0x02C001C7U, + 0x02A001C9U, + 0x02A001CBU, + 0x028001CDU, + 0x026001CFU, + 0x024001D1U, + 0x024001D3U, + 0x022001D5U, + 0x020001D7U, + 0x01E001D8U, + 0x01E001DAU, + 0x01C001DCU, + 0x01A001DEU, + 0x018001E0U, + 0x016001E2U, + 0x016001E4U, + 0x014001E6U, + 0x012001E8U, + 0x010001EAU, + 0x010001ECU, + 0x00E001EEU, + 0x00C001F0U, + 0x00A001F2U, + 0x00A001F4U, + 0x008001F6U, + 0x006001F8U, + 0x004001FAU, + 0x004001FCU, + 0x002001FEU, + 0x00000200U, + 0xFFE00202U, + 0xFFC00204U, + 0xFFC00206U, + 0xFFA00208U, + 0xFF80020AU, + 0xFF60020CU, + 0xFF60020EU, + 0xFF400210U, + 0xFF200212U, + 0xFF000214U, + 0xFF000216U, + 0xFEE00218U, + 0xFEC0021AU, + 0xFEA0021CU, + 0xFEA0021EU, + 0xFE800220U, + 0xFE600222U, + 0xFE400224U, + 0xFE200226U, + 0xFE200228U, + 0xFE000229U, + 0xFDE0022BU, + 0xFDC0022DU, + 0xFDC0022FU, + 0xFDA00231U, + 0xFD800233U, + 0xFD600235U, + 0xFD600237U, + 0xFD400239U, + 0xFD20023BU, + 0xFD00023DU, + 0xFCE0023FU, + 0xFCE00241U, + 0xFCC00243U, + 0xFCA00245U, + 0xFC800247U, + 0xFC800249U, + 0xFC60024BU, + 0xFC40024DU, + 0xFC20024FU, + 0xFC200251U, + 0xFC000253U, + 0xFBE00255U, + 0xFBC00257U, + 0xFBC00259U, + 0xFBA0025BU, + 0xFB80025DU, + 0xFB60025FU, + 0xFB400261U, + 0xFB400263U, + 0xFB200265U, + 0xFB000267U, + 0xFAE00269U, + 0xFAE0026BU, + 0xFAC0026DU, + 0xFAA0026FU, + 0xFA800271U, + 0xFA800273U, + 0xFA600275U, + 0xFA400277U, + 0xFA200279U, + 0xFA20027BU, + 0xFA00027CU, + 0xF9E0027EU, + 0xF9C00280U, + 0xF9A00282U, + 0xF9A00284U, + 0xF9800286U, + 0xF9600288U, + 0xF940028AU, + 0xF940028CU, + 0xF920028EU, + 0xF9000290U, + 0xF8E00292U, + 0xF8E00294U, + 0xF8C00296U, + 0xF8A00298U, + 0xF880029AU, + 0xF860029CU, + 0xF860029EU, + 0xF84002A0U, + 0xF82002A2U, + 0xF80002A4U, + 0xF80002A6U, + 0xF7E002A8U, + 0xF7C002AAU, + 0xF7A002ACU, + 0xF7A002AEU, + 0xF78002B0U, + 0xF76002B2U, + 0xF74002B4U, + 0xF74002B6U, + 0xF72002B8U, + 0xF70002BAU, + 0xF6E002BCU, + 0xF6C002BEU, + 0xF6C002C0U, + 0xF6A002C2U, + 0xF68002C4U, + 0xF66002C6U, + 0xF66002C8U, + 0xF64002CAU, + 0xF62002CCU, + 0xF60002CEU, + 0xF60002CFU, + 0xF5E002D1U, + 0xF5C002D3U, + 0xF5A002D5U, + 0xF5A002D7U, + 0xF58002D9U, + 0xF56002DBU, + 0xF54002DDU, + 0xF52002DFU, + 0xF52002E1U, + 0xF50002E3U, + 0xF4E002E5U, + 0xF4C002E7U, + 0xF4C002E9U, + 0xF4A002EBU, + 0xF48002EDU, + 0xF46002EFU, + 0xF46002F1U, + 0xF44002F3U, + 0xF42002F5U, + 0xF40002F7U, + 0xF3E002F9U, + 0xF3E002FBU, + /* v_table */ + 0x1A09A000U, + 0x19E9A800U, + 0x19A9B800U, + 0x1969C800U, + 0x1949D000U, + 0x1909E000U, + 0x18C9E800U, + 0x18A9F800U, + 0x186A0000U, + 0x182A1000U, + 0x180A2000U, + 0x17CA2800U, + 0x17AA3800U, + 0x176A4000U, + 0x172A5000U, + 0x170A6000U, + 0x16CA6800U, + 0x168A7800U, + 0x166A8000U, + 0x162A9000U, + 0x160AA000U, + 0x15CAA800U, + 0x158AB800U, + 0x156AC000U, + 0x152AD000U, + 0x14EAE000U, + 0x14CAE800U, + 0x148AF800U, + 0x146B0000U, + 0x142B1000U, + 0x13EB2000U, + 0x13CB2800U, + 0x138B3800U, + 0x134B4000U, + 0x132B5000U, + 0x12EB6000U, + 0x12CB6800U, + 0x128B7800U, + 0x124B8000U, + 0x122B9000U, + 0x11EBA000U, + 0x11ABA800U, + 0x118BB800U, + 0x114BC000U, + 0x112BD000U, + 0x10EBE000U, + 0x10ABE800U, + 0x108BF800U, + 0x104C0000U, + 0x100C1000U, + 0x0FEC2000U, + 0x0FAC2800U, + 0x0F8C3800U, + 0x0F4C4000U, + 0x0F0C5000U, + 0x0EEC5800U, + 0x0EAC6800U, + 0x0E6C7800U, + 0x0E4C8000U, + 0x0E0C9000U, + 0x0DEC9800U, + 0x0DACA800U, + 0x0D6CB800U, + 0x0D4CC000U, + 0x0D0CD000U, + 0x0CCCD800U, + 0x0CACE800U, + 0x0C6CF800U, + 0x0C4D0000U, + 0x0C0D1000U, + 0x0BCD1800U, + 0x0BAD2800U, + 0x0B6D3800U, + 0x0B2D4000U, + 0x0B0D5000U, + 0x0ACD5800U, + 0x0AAD6800U, + 0x0A6D7800U, + 0x0A2D8000U, + 0x0A0D9000U, + 0x09CD9800U, + 0x098DA800U, + 0x096DB800U, + 0x092DC000U, + 0x090DD000U, + 0x08CDD800U, + 0x088DE800U, + 0x086DF800U, + 0x082E0000U, + 0x07EE1000U, + 0x07CE1800U, + 0x078E2800U, + 0x076E3800U, + 0x072E4000U, + 0x06EE5000U, + 0x06CE5800U, + 0x068E6800U, + 0x064E7800U, + 0x062E8000U, + 0x05EE9000U, + 0x05CE9800U, + 0x058EA800U, + 0x054EB800U, + 0x052EC000U, + 0x04EED000U, + 0x04AED800U, + 0x048EE800U, + 0x044EF000U, + 0x042F0000U, + 0x03EF1000U, + 0x03AF1800U, + 0x038F2800U, + 0x034F3000U, + 0x030F4000U, + 0x02EF5000U, + 0x02AF5800U, + 0x028F6800U, + 0x024F7000U, + 0x020F8000U, + 0x01EF9000U, + 0x01AF9800U, + 0x016FA800U, + 0x014FB000U, + 0x010FC000U, + 0x00EFD000U, + 0x00AFD800U, + 0x006FE800U, + 0x004FF000U, + 0x00100000U, + 0xFFD01000U, + 0xFFB01800U, + 0xFF702800U, + 0xFF303000U, + 0xFF104000U, + 0xFED05000U, + 0xFEB05800U, + 0xFE706800U, + 0xFE307000U, + 0xFE108000U, + 0xFDD09000U, + 0xFD909800U, + 0xFD70A800U, + 0xFD30B000U, + 0xFD10C000U, + 0xFCD0D000U, + 0xFC90D800U, + 0xFC70E800U, + 0xFC30F000U, + 0xFBF10000U, + 0xFBD11000U, + 0xFB911800U, + 0xFB712800U, + 0xFB313000U, + 0xFAF14000U, + 0xFAD14800U, + 0xFA915800U, + 0xFA516800U, + 0xFA317000U, + 0xF9F18000U, + 0xF9D18800U, + 0xF9919800U, + 0xF951A800U, + 0xF931B000U, + 0xF8F1C000U, + 0xF8B1C800U, + 0xF891D800U, + 0xF851E800U, + 0xF831F000U, + 0xF7F20000U, + 0xF7B20800U, + 0xF7921800U, + 0xF7522800U, + 0xF7123000U, + 0xF6F24000U, + 0xF6B24800U, + 0xF6925800U, + 0xF6526800U, + 0xF6127000U, + 0xF5F28000U, + 0xF5B28800U, + 0xF5729800U, + 0xF552A800U, + 0xF512B000U, + 0xF4F2C000U, + 0xF4B2C800U, + 0xF472D800U, + 0xF452E800U, + 0xF412F000U, + 0xF3D30000U, + 0xF3B30800U, + 0xF3731800U, + 0xF3532800U, + 0xF3133000U, + 0xF2D34000U, + 0xF2B34800U, + 0xF2735800U, + 0xF2336800U, + 0xF2137000U, + 0xF1D38000U, + 0xF1B38800U, + 0xF1739800U, + 0xF133A800U, + 0xF113B000U, + 0xF0D3C000U, + 0xF093C800U, + 0xF073D800U, + 0xF033E000U, + 0xF013F000U, + 0xEFD40000U, + 0xEF940800U, + 0xEF741800U, + 0xEF342000U, + 0xEEF43000U, + 0xEED44000U, + 0xEE944800U, + 0xEE745800U, + 0xEE346000U, + 0xEDF47000U, + 0xEDD48000U, + 0xED948800U, + 0xED549800U, + 0xED34A000U, + 0xECF4B000U, + 0xECD4C000U, + 0xEC94C800U, + 0xEC54D800U, + 0xEC34E000U, + 0xEBF4F000U, + 0xEBB50000U, + 0xEB950800U, + 0xEB551800U, + 0xEB352000U, + 0xEAF53000U, + 0xEAB54000U, + 0xEA954800U, + 0xEA555800U, + 0xEA156000U, + 0xE9F57000U, + 0xE9B58000U, + 0xE9958800U, + 0xE9559800U, + 0xE915A000U, + 0xE8F5B000U, + 0xE8B5C000U, + 0xE875C800U, + 0xE855D800U, + 0xE815E000U, + 0xE7F5F000U, + 0xE7B60000U, + 0xE7760800U, + 0xE7561800U, + 0xE7162000U, + 0xE6D63000U, + 0xE6B64000U, + 0xE6764800U, + 0xE6365800U +}; + +#define FLAGS 0x40080100 +#define READUV(U,V) (tables[256 + (U)] + tables[512 + (V)]) +#define READY(Y) tables[Y] +#define FIXUP(Y) \ +do { \ + int tmp = (Y) & FLAGS; \ + if (tmp != 0) \ + { \ + tmp -= tmp>>8; \ + (Y) |= tmp; \ + tmp = FLAGS & ~(Y>>1); \ + (Y) += tmp>>8; \ + } \ +} while (0 == 1) + +#define STORE(Y,DSTPTR) \ +do { \ + *(DSTPTR)++ = (Y); \ + *(DSTPTR)++ = (Y)>>22; \ + *(DSTPTR)++ = (Y)>>11; \ + *(DSTPTR)++ = 255; \ +} while (0 == 1) + +static void yuv422_2_rgb8888(uint8_t *dst_ptr, + const uint8_t *y_ptr, + const uint8_t *u_ptr, + const uint8_t *v_ptr, + int32_t width, + int32_t height, + int32_t y_span, + int32_t uv_span, + int32_t dst_span, + int32_t dither) +{ + height -= 1; + while (height > 0) + { + height -= width<<16; + height += 1<<16; + while (height < 0) + { + /* Do top row pair */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + y1 = uv + READY(*y_ptr++); + FIXUP(y0); + FIXUP(y1); + STORE(y0, dst_ptr); + STORE(y1, dst_ptr); + height += (2<<16); + } + if ((height>>16) == 0) + { + /* Trailing top row pix */ + uint32_t uv, y0; + + uv = READUV(*u_ptr,*v_ptr); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, dst_ptr); + } + dst_ptr += dst_span-width*4; + y_ptr += y_span-width; + u_ptr += uv_span-(width>>1); + v_ptr += uv_span-(width>>1); + height = (height<<16)>>16; + height -= 1; + if (height == 0) + break; + height -= width<<16; + height += 1<<16; + while (height < 0) + { + /* Do second row pair */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + y1 = uv + READY(*y_ptr++); + FIXUP(y0); + FIXUP(y1); + STORE(y0, dst_ptr); + STORE(y1, dst_ptr); + height += (2<<16); + } + if ((height>>16) == 0) + { + /* Trailing bottom row pix */ + uint32_t uv, y0; + + uv = READUV(*u_ptr,*v_ptr); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, dst_ptr); + } + dst_ptr += dst_span-width*4; + y_ptr += y_span-width; + u_ptr += uv_span-(width>>1); + v_ptr += uv_span-(width>>1); + height = (height<<16)>>16; + height -= 1; + } +} + + +#undef FLAGS +#undef READUV +#undef READY +#undef FIXUP +#undef STORE + + +#define FLAGS 0x40080100 +#define READUV(U,V) (tables[256 + (U)] + tables[512 + (V)]) +#define READY(Y) tables[Y] +#define FIXUP(Y) \ +do { \ + int tmp = (Y) & FLAGS; \ + if (tmp != 0) \ + { \ + tmp -= tmp>>8; \ + (Y) |= tmp; \ + tmp = FLAGS & ~(Y>>1); \ + (Y) += tmp>>8; \ + } \ +} while (0 == 1) + +#define STORE(Y,DSTPTR) \ +do { \ + (DSTPTR) = 0xFF000000 | (Y & 0xFF) | (0xFF00 & (Y>>14)) | (0xFF0000 & (Y<<5));\ +} while (0 == 1) + +static void yuv420_2_rgb8888(uint8_t *dst_ptr_, + const uint8_t *y_ptr, + const uint8_t *u_ptr, + const uint8_t *v_ptr, + int32_t width, + int32_t height, + int32_t y_span, + int32_t uv_span, + int32_t dst_span, + int32_t dither) +{ + uint32_t *dst_ptr = (uint32_t *)(void *)dst_ptr_; + dst_span >>= 2; + + height -= 1; + while (height > 0) + { + height -= width<<16; + height += 1<<16; + while (height < 0) + { + /* Do 2 column pairs */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr++,*v_ptr++); + y1 = uv + READY(y_ptr[y_span]); + y0 = uv + READY(*y_ptr++); + FIXUP(y1); + FIXUP(y0); + STORE(y1, dst_ptr[dst_span]); + STORE(y0, *dst_ptr++); + y1 = uv + READY(y_ptr[y_span]); + y0 = uv + READY(*y_ptr++); + FIXUP(y1); + FIXUP(y0); + STORE(y1, dst_ptr[dst_span]); + STORE(y0, *dst_ptr++); + height += (2<<16); + } + if ((height>>16) == 0) + { + /* Trailing column pair */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr,*v_ptr); + y1 = uv + READY(y_ptr[y_span]); + y0 = uv + READY(*y_ptr++); + FIXUP(y1); + FIXUP(y0); + STORE(y0, dst_ptr[dst_span]); + STORE(y1, *dst_ptr++); + } + dst_ptr += dst_span*2-width; + y_ptr += y_span*2-width; + u_ptr += uv_span-(width>>1); + v_ptr += uv_span-(width>>1); + height = (height<<16)>>16; + height -= 2; + } + if (height == 0) + { + /* Trail row */ + height -= width<<16; + height += 1<<16; + while (height < 0) + { + /* Do a row pair */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr++,*v_ptr++); + y1 = uv + READY(*y_ptr++); + y0 = uv + READY(*y_ptr++); + FIXUP(y1); + FIXUP(y0); + STORE(y1, *dst_ptr++); + STORE(y0, *dst_ptr++); + height += (2<<16); + } + if ((height>>16) == 0) + { + /* Trailing pix */ + uint32_t uv, y0; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, *dst_ptr++); + } + } +} + + + +#undef FLAGS +#undef READUV +#undef READY +#undef FIXUP +#undef STORE + +#define FLAGS 0x40080100 +#define READUV(U,V) (tables[256 + (U)] + tables[512 + (V)]) +#define READY(Y) tables[Y] +#define FIXUP(Y) \ +do { \ + int tmp = (Y) & FLAGS; \ + if (tmp != 0) \ + { \ + tmp -= tmp>>8; \ + (Y) |= tmp; \ + tmp = FLAGS & ~(Y>>1); \ + (Y) += tmp>>8; \ + } \ +} while (0 == 1) + +#define STORE(Y,DSTPTR) \ +do { \ + *(DSTPTR)++ = (Y); \ + *(DSTPTR)++ = (Y)>>22; \ + *(DSTPTR)++ = (Y)>>11; \ + *(DSTPTR)++ = 255; \ +} while (0 == 1) + +static void yuv444_2_rgb8888(uint8_t *dst_ptr, + const uint8_t *y_ptr, + const uint8_t *u_ptr, + const uint8_t *v_ptr, + int32_t width, + int32_t height, + int32_t y_span, + int32_t uv_span, + int32_t dst_span, + int32_t dither) +{ + height -= 1; + while (height > 0) + { + height -= width<<16; + height += 1<<16; + while (height < 0) + { + /* Do top row pair */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, dst_ptr); + uv = READUV(*u_ptr++,*v_ptr++); + y1 = uv + READY(*y_ptr++); + FIXUP(y1); + STORE(y1, dst_ptr); + height += (2<<16); + } + if ((height>>16) == 0) + { + /* Trailing top row pix */ + uint32_t uv, y0; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, dst_ptr); + } + dst_ptr += dst_span-width*4; + y_ptr += y_span-width; + u_ptr += uv_span-width; + v_ptr += uv_span-width; + height = (height<<16)>>16; + height -= 1; + if (height == 0) + break; + height -= width<<16; + height += 1<<16; + while (height < 0) + { + /* Do second row pair */ + uint32_t uv, y0, y1; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, dst_ptr); + uv = READUV(*u_ptr++,*v_ptr++); + y1 = uv + READY(*y_ptr++); + FIXUP(y1); + STORE(y1, dst_ptr); + height += (2<<16); + } + if ((height>>16) == 0) + { + /* Trailing bottom row pix */ + uint32_t uv, y0; + + uv = READUV(*u_ptr++,*v_ptr++); + y0 = uv + READY(*y_ptr++); + FIXUP(y0); + STORE(y0, dst_ptr); + } + dst_ptr += dst_span-width*4; + y_ptr += y_span-width; + u_ptr += uv_span-width; + v_ptr += uv_span-width; + height = (height<<16)>>16; + height -= 1; + } +} +#endif // YUV2RGB_H diff --git a/modules/visual_script/SCsub b/modules/visual_script/SCsub new file mode 100644 index 000000000..088240676 --- /dev/null +++ b/modules/visual_script/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.modules_sources, "*.cpp") + +Export('env') diff --git a/modules/visual_script/config.py b/modules/visual_script/config.py new file mode 100644 index 000000000..5698a3729 --- /dev/null +++ b/modules/visual_script/config.py @@ -0,0 +1,8 @@ + + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp new file mode 100644 index 000000000..5fe87a495 --- /dev/null +++ b/modules/visual_script/register_types.cpp @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "visual_script.h" +#include "visual_script_editor.h" +#include "io/resource_loader.h" +#include "visual_script_nodes.h" +#include "visual_script_func_nodes.h" +#include "visual_script_builtin_funcs.h" +#include "visual_script_flow_control.h" +#include "visual_script_yield_nodes.h" +#include "visual_script_expression.h" + + +VisualScriptLanguage *visual_script_language=NULL; + + +void register_visual_script_types() { + + visual_script_language=memnew( VisualScriptLanguage ); + //script_language_gd->init(); + ScriptServer::register_language(visual_script_language); + + ClassDB::register_class<VisualScript>(); + ClassDB::register_virtual_class<VisualScriptNode>(); + ClassDB::register_virtual_class<VisualScriptFunctionState>(); + ClassDB::register_class<VisualScriptFunction>(); + ClassDB::register_class<VisualScriptOperator>(); + ClassDB::register_class<VisualScriptVariableSet>(); + ClassDB::register_class<VisualScriptVariableGet>(); + ClassDB::register_class<VisualScriptConstant>(); + ClassDB::register_class<VisualScriptIndexGet>(); + ClassDB::register_class<VisualScriptIndexSet>(); + ClassDB::register_class<VisualScriptGlobalConstant>(); + ClassDB::register_class<VisualScriptClassConstant>(); + ClassDB::register_class<VisualScriptMathConstant>(); + ClassDB::register_class<VisualScriptBasicTypeConstant>(); + ClassDB::register_class<VisualScriptEngineSingleton>(); + ClassDB::register_class<VisualScriptSceneNode>(); + ClassDB::register_class<VisualScriptSceneTree>(); + ClassDB::register_class<VisualScriptResourcePath>(); + ClassDB::register_class<VisualScriptSelf>(); + ClassDB::register_class<VisualScriptCustomNode>(); + ClassDB::register_class<VisualScriptSubCall>(); + ClassDB::register_class<VisualScriptComment>(); + ClassDB::register_class<VisualScriptConstructor>(); + ClassDB::register_class<VisualScriptLocalVar>(); + ClassDB::register_class<VisualScriptLocalVarSet>(); + ClassDB::register_class<VisualScriptInputAction>(); + ClassDB::register_class<VisualScriptDeconstruct>(); + ClassDB::register_class<VisualScriptPreload>(); + ClassDB::register_class<VisualScriptTypeCast>(); + + + ClassDB::register_class<VisualScriptFunctionCall>(); + ClassDB::register_class<VisualScriptPropertySet>(); + ClassDB::register_class<VisualScriptPropertyGet>(); + //ClassDB::register_type<VisualScriptScriptCall>(); + ClassDB::register_class<VisualScriptEmitSignal>(); + + ClassDB::register_class<VisualScriptReturn>(); + ClassDB::register_class<VisualScriptCondition>(); + ClassDB::register_class<VisualScriptWhile>(); + ClassDB::register_class<VisualScriptIterator>(); + ClassDB::register_class<VisualScriptSequence>(); + ClassDB::register_class<VisualScriptInputFilter>(); + ClassDB::register_class<VisualScriptSwitch >(); + + ClassDB::register_class<VisualScriptYield>(); + ClassDB::register_class<VisualScriptYieldSignal>(); + + ClassDB::register_class<VisualScriptBuiltinFunc>(); + + + ClassDB::register_class<VisualScriptExpression>(); + + register_visual_script_nodes(); + register_visual_script_func_nodes(); + register_visual_script_builtin_func_node(); + register_visual_script_flow_control_nodes(); + register_visual_script_yield_nodes(); + register_visual_script_expression_node(); + +#ifdef TOOLS_ENABLED + VisualScriptEditor::register_editor(); +#endif + + +} + +void unregister_visual_script_types() { + + + unregister_visual_script_nodes(); + + ScriptServer::unregister_language(visual_script_language); + +#ifdef TOOLS_ENABLED + VisualScriptEditor::free_clipboard(); +#endif + if (visual_script_language) + memdelete( visual_script_language ); + +} diff --git a/modules/visual_script/register_types.h b/modules/visual_script/register_types.h new file mode 100644 index 000000000..f6904420b --- /dev/null +++ b/modules/visual_script/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_visual_script_types(); +void unregister_visual_script_types(); diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp new file mode 100644 index 000000000..e1e180432 --- /dev/null +++ b/modules/visual_script/visual_script.cpp @@ -0,0 +1,2842 @@ +#include "visual_script.h" +#include "visual_script_nodes.h" +#include "scene/main/node.h" +#include "os/os.h" +#include "globals.h" + + + +//used by editor, this is not really saved +void VisualScriptNode::set_breakpoint(bool p_breakpoint) { + breakpoint=p_breakpoint; +} + +bool VisualScriptNode::is_breakpoint() const { + + return breakpoint; +} + +void VisualScriptNode::_notification(int p_what) { + + if (p_what==NOTIFICATION_POSTINITIALIZE) { + + int dvc = get_input_value_port_count(); + for(int i=0;i<dvc;i++) { + Variant::Type expected = get_input_value_port_info(i).type; + Variant::CallError ce; + default_input_values.push_back(Variant::construct(expected,NULL,0,ce,false)); + } + } +} + +void VisualScriptNode::ports_changed_notify(){ + + + default_input_values.resize( MAX(default_input_values.size(),get_input_value_port_count()) ); //let it grow as big as possible, we don't want to lose values on resize + + emit_signal("ports_changed"); + +} + +void VisualScriptNode::set_default_input_value(int p_port,const Variant& p_value) { + + ERR_FAIL_INDEX(p_port,default_input_values.size()); + + default_input_values[p_port]=p_value; + +#ifdef TOOLS_ENABLED + for (Set<VisualScript*>::Element *E=scripts_used.front();E;E=E->next()) { + E->get()->set_edited(true); + } +#endif + +} + +Variant VisualScriptNode::get_default_input_value(int p_port) const { + + ERR_FAIL_INDEX_V(p_port,default_input_values.size(),Variant()); + return default_input_values[p_port]; +} + +void VisualScriptNode::_set_default_input_values(Array p_values) { + + + default_input_values=p_values; +} + + +void VisualScriptNode::validate_input_default_values() { + + + + default_input_values.resize(get_input_value_port_count()); + + //actually validate on save + for(int i=0;i<get_input_value_port_count();i++) { + + Variant::Type expected = get_input_value_port_info(i).type; + + + if (expected==Variant::NIL || expected==default_input_values[i].get_type()) { + continue; + } else { + //not the same, reconvert + Variant::CallError ce; + Variant existing = default_input_values[i]; + const Variant *existingp=&existing; + default_input_values[i] = Variant::construct(expected,&existingp,1,ce,false); + if (ce.error!=Variant::CallError::CALL_OK) { + //could not convert? force.. + default_input_values[i] = Variant::construct(expected,NULL,0,ce,false); + } + } + } +} + +Array VisualScriptNode::_get_default_input_values() const { + + //validate on save, since on load there is little info about this + + return default_input_values; +} + + + +void VisualScriptNode::_bind_methods() { + + ClassDB::bind_method(_MD("get_visual_script:VisualScript"),&VisualScriptNode::get_visual_script); + ClassDB::bind_method(_MD("set_default_input_value","port_idx","value:Variant"),&VisualScriptNode::set_default_input_value); + ClassDB::bind_method(_MD("get_default_input_value:Variant","port_idx"),&VisualScriptNode::get_default_input_value); + ClassDB::bind_method(_MD("_set_default_input_values","values"),&VisualScriptNode::_set_default_input_values); + ClassDB::bind_method(_MD("_get_default_input_values"),&VisualScriptNode::_get_default_input_values); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY,"_default_input_values",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_default_input_values"),_SCS("_get_default_input_values")); + ADD_SIGNAL(MethodInfo("ports_changed")); +} + +VisualScriptNode::TypeGuess VisualScriptNode::guess_output_type(TypeGuess* p_inputs,int p_output) const { + + PropertyInfo pinfo = get_output_value_port_info(p_output); + + TypeGuess tg; + + tg.type=pinfo.type; + if (pinfo.hint==PROPERTY_HINT_RESOURCE_TYPE) { + tg.GDCLASS=pinfo.hint_string; + } + + return tg; +} + +Ref<VisualScript> VisualScriptNode::get_visual_script() const { + + if (scripts_used.size()) + return Ref<VisualScript>(scripts_used.front()->get()); + + return Ref<VisualScript>(); +} + +VisualScriptNode::VisualScriptNode() { + breakpoint=false; +} + +//////////////// + +///////////////////// + +VisualScriptNodeInstance::VisualScriptNodeInstance() { + + sequence_outputs=NULL; + input_ports=NULL; +} + +VisualScriptNodeInstance::~VisualScriptNodeInstance() { + + if (sequence_outputs) { + memdelete_arr(sequence_outputs); + } + + if (input_ports) { + memdelete_arr(input_ports); + } + + if (output_ports) { + memdelete_arr(output_ports); + } + +} + +void VisualScript::add_function(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!String(p_name).is_valid_identifier()); + ERR_FAIL_COND(functions.has(p_name)); + + functions[p_name]=Function(); + functions[p_name].scroll=Vector2(-50,-100); +} + +bool VisualScript::has_function(const StringName& p_name) const { + + return functions.has(p_name); + +} +void VisualScript::remove_function(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_name)); + + for (Map<int,Function::NodeData>::Element *E=functions[p_name].nodes.front();E;E=E->next()) { + + E->get().node->disconnect("ports_changed",this,"_node_ports_changed"); + E->get().node->scripts_used.erase(this); + } + + functions.erase(p_name); + +} + +void VisualScript::rename_function(const StringName& p_name,const StringName& p_new_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_name)); + if (p_new_name==p_name) + return; + + ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); + + ERR_FAIL_COND(functions.has(p_new_name)); + ERR_FAIL_COND(variables.has(p_new_name)); + ERR_FAIL_COND(custom_signals.has(p_new_name)); + + functions[p_new_name]=functions[p_name]; + functions.erase(p_name); + +} + +void VisualScript::set_function_scroll(const StringName& p_name, const Vector2& p_scroll) { + + ERR_FAIL_COND(!functions.has(p_name)); + functions[p_name].scroll=p_scroll; + +} + +Vector2 VisualScript::get_function_scroll(const StringName& p_name) const { + + ERR_FAIL_COND_V(!functions.has(p_name),Vector2()); + return functions[p_name].scroll; + +} + + +void VisualScript::get_function_list(List<StringName> *r_functions) const { + + for (const Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + r_functions->push_back(E->key()); + } + + r_functions->sort_custom<StringName::AlphCompare>(); + +} + +int VisualScript::get_function_node_id(const StringName& p_name) const { + + ERR_FAIL_COND_V(!functions.has(p_name),-1); + + return functions[p_name].function_id; + +} + + +void VisualScript::_node_ports_changed(int p_id) { + + + + StringName function; + + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + if (E->get().nodes.has(p_id)) { + function=E->key(); + break; + } + } + + ERR_FAIL_COND(function==StringName()); + + Function &func = functions[function]; + Ref<VisualScriptNode> vsn = func.nodes[p_id].node; + + if (OS::get_singleton()->get_main_loop() && OS::get_singleton()->get_main_loop()->cast_to<SceneTree>() && OS::get_singleton()->get_main_loop()->cast_to<SceneTree>()->is_editor_hint()) { + vsn->validate_input_default_values(); //force validate default values when editing on editor + } + + //must revalidate all the functions + + { + List<SequenceConnection> to_remove; + + for (Set<SequenceConnection>::Element *E=func.sequence_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id && E->get().from_output>=vsn->get_output_sequence_port_count()) { + + to_remove.push_back(E->get()); + } + if (E->get().to_node==p_id && !vsn->has_input_sequence_port()) { + + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.sequence_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + + { + + List<DataConnection> to_remove; + + + for (Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id && E->get().from_port>=vsn->get_output_value_port_count()) { + to_remove.push_back(E->get()); + } + if (E->get().to_node==p_id && E->get().to_port>=vsn->get_input_value_port_count()) { + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.data_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + +#ifdef TOOLS_ENABLED + set_edited(true); //something changed, let's set as edited + emit_signal("node_ports_changed",function,p_id); +#endif +} + +void VisualScript::add_node(const StringName& p_func,int p_id, const Ref<VisualScriptNode>& p_node, const Point2 &p_pos) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + + + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + ERR_FAIL_COND(E->get().nodes.has(p_id)); //id can exist only one in script, even for different functions + } + + Function &func = functions[p_func]; + + + if (p_node->cast_to<VisualScriptFunction>()) { + //the function indeed + ERR_EXPLAIN("A function node already has been set here."); + ERR_FAIL_COND(func.function_id>=0); + + func.function_id=p_id; + } + + Function::NodeData nd; + nd.node=p_node; + nd.pos=p_pos; + + Ref<VisualScriptNode> vsn = p_node; + vsn->connect("ports_changed",this,"_node_ports_changed",varray(p_id)); + vsn->scripts_used.insert(this); + + + + func.nodes[p_id]=nd; +} + +void VisualScript::remove_node(const StringName& p_func,int p_id){ + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + ERR_FAIL_COND(!func.nodes.has(p_id)); + { + List<SequenceConnection> to_remove; + + for (Set<SequenceConnection>::Element *E=func.sequence_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id || E->get().to_node==p_id) { + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.sequence_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + + { + + List<DataConnection> to_remove; + + + for (Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().from_node==p_id || E->get().to_node==p_id) { + to_remove.push_back(E->get()); + } + } + + while(to_remove.size()) { + func.data_connections.erase(to_remove.front()->get()); + to_remove.pop_front(); + } + } + + if (func.nodes[p_id].node->cast_to<VisualScriptFunction>()) { + func.function_id=-1; //revert to invalid + } + + func.nodes[p_id].node->disconnect("ports_changed",this,"_node_ports_changed"); + func.nodes[p_id].node->scripts_used.erase(this); + + func.nodes.erase(p_id); + + +} + + +bool VisualScript::has_node(const StringName& p_func,int p_id) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + return func.nodes.has(p_id); +} + +Ref<VisualScriptNode> VisualScript::get_node(const StringName& p_func,int p_id) const{ + + ERR_FAIL_COND_V(!functions.has(p_func),Ref<VisualScriptNode>()); + const Function &func = functions[p_func]; + + ERR_FAIL_COND_V(!func.nodes.has(p_id),Ref<VisualScriptNode>()); + + return func.nodes[p_id].node; +} + +void VisualScript::set_node_pos(const StringName& p_func,int p_id,const Point2& p_pos) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + ERR_FAIL_COND(!func.nodes.has(p_id)); + func.nodes[p_id].pos=p_pos; +} + +Point2 VisualScript::get_node_pos(const StringName& p_func,int p_id) const{ + + ERR_FAIL_COND_V(!functions.has(p_func),Point2()); + const Function &func = functions[p_func]; + + ERR_FAIL_COND_V(!func.nodes.has(p_id),Point2()); + return func.nodes[p_id].pos; +} + + +void VisualScript::get_node_list(const StringName& p_func,List<int> *r_nodes) const{ + + ERR_FAIL_COND(!functions.has(p_func)); + const Function &func = functions[p_func]; + + for (const Map<int,Function::NodeData>::Element *E=func.nodes.front();E;E=E->next()) { + r_nodes->push_back(E->key()); + } + +} + + +void VisualScript::sequence_connect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node){ + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + + SequenceConnection sc; + sc.from_node=p_from_node; + sc.from_output=p_from_output; + sc.to_node=p_to_node; + ERR_FAIL_COND(func.sequence_connections.has(sc)); + + func.sequence_connections.insert(sc); + +} + +void VisualScript::sequence_disconnect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node){ + + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + SequenceConnection sc; + sc.from_node=p_from_node; + sc.from_output=p_from_output; + sc.to_node=p_to_node; + ERR_FAIL_COND(!func.sequence_connections.has(sc)); + + func.sequence_connections.erase(sc); + +} + +bool VisualScript::has_sequence_connection(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node) const{ + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + SequenceConnection sc; + sc.from_node=p_from_node; + sc.from_output=p_from_output; + sc.to_node=p_to_node; + + return func.sequence_connections.has(sc); +} + +void VisualScript::get_sequence_connection_list(const StringName& p_func,List<SequenceConnection> *r_connection) const { + + ERR_FAIL_COND(!functions.has(p_func)); + const Function &func = functions[p_func]; + + for (const Set<SequenceConnection>::Element *E=func.sequence_connections.front();E;E=E->next()) { + r_connection->push_back(E->get()); + } +} + + +void VisualScript::data_connect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + DataConnection dc; + dc.from_node=p_from_node; + dc.from_port=p_from_port; + dc.to_node=p_to_node; + dc.to_port=p_to_port; + + ERR_FAIL_COND( func.data_connections.has(dc)); + + func.data_connections.insert(dc); +} + +void VisualScript::data_disconnect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) { + + ERR_FAIL_COND(!functions.has(p_func)); + Function &func = functions[p_func]; + + DataConnection dc; + dc.from_node=p_from_node; + dc.from_port=p_from_port; + dc.to_node=p_to_node; + dc.to_port=p_to_port; + + ERR_FAIL_COND( !func.data_connections.has(dc)); + + func.data_connections.erase(dc); + +} + +bool VisualScript::has_data_connection(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + DataConnection dc; + dc.from_node=p_from_node; + dc.from_port=p_from_port; + dc.to_node=p_to_node; + dc.to_port=p_to_port; + + return func.data_connections.has(dc); + +} + +bool VisualScript::is_input_value_port_connected(const StringName& p_func,int p_node,int p_port) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + for (const Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().to_node==p_node && E->get().to_port==p_port) + return true; + } + + return false; +} + +bool VisualScript::get_input_value_port_connection_source(const StringName& p_func,int p_node,int p_port,int *r_node,int *r_port) const { + + ERR_FAIL_COND_V(!functions.has(p_func),false); + const Function &func = functions[p_func]; + + for (const Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + if (E->get().to_node==p_node && E->get().to_port==p_port) { + *r_node=E->get().from_node; + *r_port=E->get().from_port; + return true; + } + } + + return false; + +} + +void VisualScript::get_data_connection_list(const StringName& p_func,List<DataConnection> *r_connection) const { + + ERR_FAIL_COND(!functions.has(p_func)); + const Function &func = functions[p_func]; + + for (const Set<DataConnection>::Element *E=func.data_connections.front();E;E=E->next()) { + r_connection->push_back(E->get()); + } +} + +void VisualScript::add_variable(const StringName& p_name,const Variant& p_default_value,bool p_export) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!String(p_name).is_valid_identifier()); + ERR_FAIL_COND(variables.has(p_name)); + + Variable v; + v.default_value=p_default_value; + v.info.type=p_default_value.get_type(); + v.info.name=p_name; + v.info.hint=PROPERTY_HINT_NONE; + v._export=p_export; + + variables[p_name]=v; + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif + +} + +bool VisualScript::has_variable(const StringName& p_name) const { + + return variables.has(p_name); +} + +void VisualScript::remove_variable(const StringName& p_name) { + + ERR_FAIL_COND(!variables.has(p_name)); + variables.erase(p_name); + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif +} + +void VisualScript::set_variable_default_value(const StringName& p_name,const Variant& p_value){ + + ERR_FAIL_COND(!variables.has(p_name)); + + variables[p_name].default_value=p_value; + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif + + +} +Variant VisualScript::get_variable_default_value(const StringName& p_name) const{ + + ERR_FAIL_COND_V(!variables.has(p_name),Variant()); + return variables[p_name].default_value; + +} +void VisualScript::set_variable_info(const StringName& p_name,const PropertyInfo& p_info){ + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!variables.has(p_name)); + variables[p_name].info=p_info; + variables[p_name].info.name=p_name; + +#ifdef TOOLS_ENABLED + _update_placeholders(); +#endif + + +} +PropertyInfo VisualScript::get_variable_info(const StringName& p_name) const{ + + ERR_FAIL_COND_V(!variables.has(p_name),PropertyInfo()); + return variables[p_name].info; +} + +void VisualScript::set_variable_export(const StringName& p_name,bool p_export) { + + ERR_FAIL_COND(!variables.has(p_name)); + + variables[p_name]._export=p_export; +} + +bool VisualScript::get_variable_export(const StringName& p_name) const { + + ERR_FAIL_COND_V(!variables.has(p_name),false); + return variables[p_name]._export; + +} + + +void VisualScript::_set_variable_info(const StringName& p_name,const Dictionary& p_info) { + + PropertyInfo pinfo; + if (p_info.has("type")) + pinfo.type=Variant::Type(int(p_info["type"])); + if (p_info.has("name")) + pinfo.name=p_info["name"]; + if (p_info.has("hint")) + pinfo.hint=PropertyHint(int(p_info["hint"])); + if (p_info.has("hint_string")) + pinfo.hint_string=p_info["hint_string"]; + if (p_info.has("usage")) + pinfo.usage=p_info["usage"]; + + set_variable_info(p_name,pinfo); +} + +Dictionary VisualScript::_get_variable_info(const StringName& p_name) const{ + + PropertyInfo pinfo=get_variable_info(p_name); + Dictionary d; + d["type"]=pinfo.type; + d["name"]=pinfo.name; + d["hint"]=pinfo.hint; + d["hint_string"]=pinfo.hint_string; + d["usage"]=pinfo.usage; + + return d; +} + +void VisualScript::get_variable_list(List<StringName> *r_variables) const{ + + + for (Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + r_variables->push_back(E->key()); + } + + r_variables->sort_custom<StringName::AlphCompare>(); +} + + +void VisualScript::set_instance_base_type(const StringName& p_type) { + + ERR_FAIL_COND( instances.size() ); + base_type=p_type; +} + + +void VisualScript::rename_variable(const StringName& p_name,const StringName& p_new_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!variables.has(p_name)); + if (p_new_name==p_name) + return; + + ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); + + ERR_FAIL_COND(functions.has(p_new_name)); + ERR_FAIL_COND(variables.has(p_new_name)); + ERR_FAIL_COND(custom_signals.has(p_new_name)); + + variables[p_new_name]=variables[p_name]; + variables.erase(p_name); + +} + +void VisualScript::add_custom_signal(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!String(p_name).is_valid_identifier()); + ERR_FAIL_COND(custom_signals.has(p_name)); + + custom_signals[p_name]=Vector<Argument>(); +} + +bool VisualScript::has_custom_signal(const StringName& p_name) const { + + return custom_signals.has(p_name); + +} +void VisualScript::custom_signal_add_argument(const StringName& p_func,Variant::Type p_type,const String& p_name,int p_index) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + Argument arg; + arg.type=p_type; + arg.name=p_name; + if (p_index<0) + custom_signals[p_func].push_back(arg); + else + custom_signals[p_func].insert(0,arg); + +} +void VisualScript::custom_signal_set_argument_type(const StringName& p_func,int p_argidx,Variant::Type p_type) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + custom_signals[p_func][p_argidx].type=p_type; +} +Variant::Type VisualScript::custom_signal_get_argument_type(const StringName& p_func,int p_argidx) const { + + ERR_FAIL_COND_V(!custom_signals.has(p_func),Variant::NIL); + ERR_FAIL_INDEX_V(p_argidx,custom_signals[p_func].size(),Variant::NIL); + return custom_signals[p_func][p_argidx].type; +} +void VisualScript::custom_signal_set_argument_name(const StringName& p_func,int p_argidx,const String& p_name) { + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + custom_signals[p_func][p_argidx].name=p_name; + +} +String VisualScript::custom_signal_get_argument_name(const StringName& p_func,int p_argidx) const { + + ERR_FAIL_COND_V(!custom_signals.has(p_func),String()); + ERR_FAIL_INDEX_V(p_argidx,custom_signals[p_func].size(),String()); + return custom_signals[p_func][p_argidx].name; + +} +void VisualScript::custom_signal_remove_argument(const StringName& p_func,int p_argidx) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + custom_signals[p_func].remove(p_argidx); + +} + +int VisualScript::custom_signal_get_argument_count(const StringName& p_func) const { + + ERR_FAIL_COND_V(!custom_signals.has(p_func),0); + return custom_signals[p_func].size(); + +} +void VisualScript::custom_signal_swap_argument(const StringName& p_func,int p_argidx,int p_with_argidx) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_func)); + ERR_FAIL_INDEX(p_argidx,custom_signals[p_func].size()); + ERR_FAIL_INDEX(p_with_argidx,custom_signals[p_func].size()); + + SWAP( custom_signals[p_func][p_argidx], custom_signals[p_func][p_with_argidx] ); + +} +void VisualScript::remove_custom_signal(const StringName& p_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_name)); + custom_signals.erase(p_name); + +} + +void VisualScript::rename_custom_signal(const StringName& p_name,const StringName& p_new_name) { + + ERR_FAIL_COND( instances.size() ); + ERR_FAIL_COND(!custom_signals.has(p_name)); + if (p_new_name==p_name) + return; + + ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); + + ERR_FAIL_COND(functions.has(p_new_name)); + ERR_FAIL_COND(variables.has(p_new_name)); + ERR_FAIL_COND(custom_signals.has(p_new_name)); + + custom_signals[p_new_name]=custom_signals[p_name]; + custom_signals.erase(p_name); + +} + +void VisualScript::get_custom_signal_list(List<StringName> *r_custom_signals) const { + + for (const Map<StringName,Vector<Argument> >::Element *E=custom_signals.front();E;E=E->next()) { + r_custom_signals->push_back(E->key()); + } + + r_custom_signals->sort_custom<StringName::AlphCompare>(); + +} + +int VisualScript::get_available_id() const { + + int max_id=0; + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + if (E->get().nodes.empty()) + continue; + + int last_id = E->get().nodes.back()->key(); + max_id=MAX(max_id,last_id+1); + } + + return max_id; +} + +///////////////////////////////// + + +bool VisualScript::can_instance() const { + + return true;//ScriptServer::is_scripting_enabled(); + +} + + +StringName VisualScript::get_instance_base_type() const { + + return base_type; +} + +Ref<Script> VisualScript::get_base_script() const { + return Ref<Script>(); // no inheritance in visual script +} + + +#ifdef TOOLS_ENABLED +void VisualScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { + + + placeholders.erase(p_placeholder); +} + + +void VisualScript::_update_placeholders() { + + if (placeholders.size()==0) + return; //no bother if no placeholders + List<PropertyInfo> pinfo; + Map<StringName,Variant> values; + + for (Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + + if (!E->get()._export) + continue; + + PropertyInfo p = E->get().info; + p.name=String(E->key()); + pinfo.push_back(p); + values[p.name]=E->get().default_value; + } + + for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) { + + E->get()->update(pinfo,values); + } + +} + +#endif + + +ScriptInstance* VisualScript::instance_create(Object *p_this) { + + + +#ifdef TOOLS_ENABLED + + if (!ScriptServer::is_scripting_enabled()) { + + + PlaceHolderScriptInstance *sins = memnew( PlaceHolderScriptInstance(VisualScriptLanguage::singleton,Ref<Script>((Script*)this),p_this)); + placeholders.insert(sins); + + List<PropertyInfo> pinfo; + Map<StringName,Variant> values; + + for (Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + + if (!E->get()._export) + continue; + + PropertyInfo p = E->get().info; + p.name=String(E->key()); + pinfo.push_back(p); + values[p.name]=E->get().default_value; + } + + sins->update(pinfo,values); + + return sins; + } +#endif + + + VisualScriptInstance *instance=memnew( VisualScriptInstance ); + instance->create(Ref<VisualScript>(this),p_this); + + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->lock(); + + instances[p_this]=instance; + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->unlock(); + + return instance; +} + +bool VisualScript::instance_has(const Object *p_this) const { + + return instances.has((Object*)p_this); +} + +bool VisualScript::has_source_code() const { + + return false; +} + +String VisualScript::get_source_code() const { + + return String(); +} + +void VisualScript::set_source_code(const String& p_code) { + +} + +Error VisualScript::reload(bool p_keep_state) { + + return OK; +} + + +bool VisualScript::is_tool() const { + + return false; +} + + +String VisualScript::get_node_type() const { + + return String(); +} + + +ScriptLanguage *VisualScript::get_language() const { + + return VisualScriptLanguage::singleton; +} + + +bool VisualScript::has_script_signal(const StringName& p_signal) const { + + return custom_signals.has(p_signal); +} + +void VisualScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + + for (const Map<StringName,Vector<Argument> >::Element *E=custom_signals.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + for(int i=0;i<E->get().size();i++) { + PropertyInfo arg; + arg.type=E->get()[i].type; + arg.name=E->get()[i].name; + mi.arguments.push_back(arg); + } + + + r_signals->push_back(mi); + } + + +} + + +bool VisualScript::get_property_default_value(const StringName& p_property,Variant& r_value) const { + + if (!variables.has(p_property)) + return false; + + r_value=variables[ p_property ].default_value; + return true; +} +void VisualScript::get_script_method_list(List<MethodInfo> *p_list) const { + + for (Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + if (E->get().function_id>=0) { + + Ref<VisualScriptFunction> func=E->get().nodes[E->get().function_id].node; + if (func.is_valid()) { + + for(int i=0;i<func->get_argument_count();i++) { + PropertyInfo arg; + arg.name=func->get_argument_name(i); + arg.type=func->get_argument_type(i); + mi.arguments.push_back(arg); + } + } + } + + p_list->push_back(mi); + } +} + +bool VisualScript::has_method(const StringName& p_method) const { + + return functions.has(p_method); +} +MethodInfo VisualScript::get_method_info(const StringName& p_method) const{ + + const Map<StringName,Function>::Element *E=functions.find(p_method); + if (!E) + return MethodInfo(); + + MethodInfo mi; + mi.name=E->key(); + if (E->get().function_id>=0) { + + Ref<VisualScriptFunction> func=E->get().nodes[E->get().function_id].node; + if (func.is_valid()) { + + for(int i=0;i<func->get_argument_count();i++) { + PropertyInfo arg; + arg.name=func->get_argument_name(i); + arg.type=func->get_argument_type(i); + mi.arguments.push_back(arg); + } + } + } + + return mi; +} + +void VisualScript::get_script_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> vars; + get_variable_list(&vars); + + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + if (!variables[E->get()]._export) + continue; + p_list->push_back(variables[E->get()].info); + } +} + +#ifdef TOOLS_ENABLED +bool VisualScript::are_subnodes_edited() const { + + for(const Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + for (const Map<int,Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + if (F->get().node->is_edited()) { + return true; + } + } + } + + return false; +} +#endif + +void VisualScript::_set_data(const Dictionary& p_data) { + + Dictionary d = p_data; + if (d.has("base_type")) + base_type=d["base_type"]; + + variables.clear(); + Array vars=d["variables"]; + for (int i=0;i<vars.size();i++) { + + Dictionary v=vars[i]; + StringName name = v["name"]; + add_variable(name); + _set_variable_info(name,v); + set_variable_default_value(name,v["default_value"]); + set_variable_export(name,v.has("export") && bool(v["export"])); + + } + + + custom_signals.clear(); + Array sigs=d["signals"]; + for (int i=0;i<sigs.size();i++) { + + Dictionary cs=sigs[i]; + add_custom_signal(cs["name"]); + + Array args=cs["arguments"]; + for(int j=0;j<args.size();j+=2) { + custom_signal_add_argument(cs["name"],Variant::Type(int(args[j+1])),args[j]); + } + } + + Array funcs=d["functions"]; + functions.clear(); + + for (int i=0;i<funcs.size();i++) { + + Dictionary func=funcs[i]; + + + StringName name=func["name"]; + //int id=func["function_id"]; + add_function(name); + + set_function_scroll(name,func["scroll"]); + + Array nodes = func["nodes"]; + + for(int i=0;i<nodes.size();i+=3) { + + add_node(name,nodes[i],nodes[i+2],nodes[i+1]); + } + + + Array sequence_connections=func["sequence_connections"]; + + for (int j=0;j<sequence_connections.size();j+=3) { + + sequence_connect(name,sequence_connections[j+0],sequence_connections[j+1],sequence_connections[j+2]); + } + + + Array data_connections=func["data_connections"]; + + for (int j=0;j<data_connections.size();j+=4) { + + data_connect(name,data_connections[j+0],data_connections[j+1],data_connections[j+2],data_connections[j+3]); + + } + + + } + +} + +Dictionary VisualScript::_get_data() const{ + + Dictionary d; + d["base_type"]=base_type; + Array vars; + for (const Map<StringName,Variable>::Element *E=variables.front();E;E=E->next()) { + + Dictionary var = _get_variable_info(E->key()); + var["name"]=E->key(); //make sure it's the right one + var["default_value"]=E->get().default_value; + var["export"]=E->get()._export; + vars.push_back(var); + } + d["variables"]=vars; + + Array sigs; + for (const Map<StringName,Vector<Argument> >::Element *E=custom_signals.front();E;E=E->next()) { + + Dictionary cs; + cs["name"]=E->key(); + Array args; + for(int i=0;i<E->get().size();i++) { + args.push_back(E->get()[i].name); + args.push_back(E->get()[i].type); + } + cs["arguments"]=args; + + sigs.push_back(cs); + } + + d["signals"]=sigs; + + Array funcs; + + for (const Map<StringName,Function>::Element *E=functions.front();E;E=E->next()) { + + Dictionary func; + func["name"]=E->key(); + func["function_id"]=E->get().function_id; + func["scroll"]=E->get().scroll; + + Array nodes; + + for (const Map<int,Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + + nodes.push_back(F->key()); + nodes.push_back(F->get().pos); + nodes.push_back(F->get().node); + + } + + func["nodes"]=nodes; + + Array sequence_connections; + + for (const Set<SequenceConnection>::Element *F=E->get().sequence_connections.front();F;F=F->next()) { + + sequence_connections.push_back(F->get().from_node); + sequence_connections.push_back(F->get().from_output); + sequence_connections.push_back(F->get().to_node); + + } + + + func["sequence_connections"]=sequence_connections; + + Array data_connections; + + for (const Set<DataConnection>::Element *F=E->get().data_connections.front();F;F=F->next()) { + + data_connections.push_back(F->get().from_node); + data_connections.push_back(F->get().from_port); + data_connections.push_back(F->get().to_node); + data_connections.push_back(F->get().to_port); + + } + + + func["data_connections"]=data_connections; + + funcs.push_back(func); + + } + + d["functions"]=funcs; + + + return d; + +} + +void VisualScript::_bind_methods() { + + + + ClassDB::bind_method(_MD("_node_ports_changed"),&VisualScript::_node_ports_changed); + + ClassDB::bind_method(_MD("add_function","name"),&VisualScript::add_function); + ClassDB::bind_method(_MD("has_function","name"),&VisualScript::has_function); + ClassDB::bind_method(_MD("remove_function","name"),&VisualScript::remove_function); + ClassDB::bind_method(_MD("rename_function","name","new_name"),&VisualScript::rename_function); + ClassDB::bind_method(_MD("set_function_scroll","ofs"),&VisualScript::set_function_scroll); + ClassDB::bind_method(_MD("get_function_scroll"),&VisualScript::get_function_scroll); + + ClassDB::bind_method(_MD("add_node","func","id","node","pos"),&VisualScript::add_node,DEFVAL(Point2())); + ClassDB::bind_method(_MD("remove_node","func","id"),&VisualScript::remove_node); + ClassDB::bind_method(_MD("get_function_node_id","name"),&VisualScript::get_function_node_id); + + ClassDB::bind_method(_MD("get_node","func","id"),&VisualScript::get_node); + ClassDB::bind_method(_MD("has_node","func","id"),&VisualScript::has_node); + ClassDB::bind_method(_MD("set_node_pos","func","id","pos"),&VisualScript::set_node_pos); + ClassDB::bind_method(_MD("get_node_pos","func","id"),&VisualScript::get_node_pos); + + ClassDB::bind_method(_MD("sequence_connect","func","from_node","from_output","to_node"),&VisualScript::sequence_connect); + ClassDB::bind_method(_MD("sequence_disconnect","func","from_node","from_output","to_node"),&VisualScript::sequence_disconnect); + ClassDB::bind_method(_MD("has_sequence_connection","func","from_node","from_output","to_node"),&VisualScript::has_sequence_connection); + + ClassDB::bind_method(_MD("data_connect","func","from_node","from_port","to_node","to_port"),&VisualScript::data_connect); + ClassDB::bind_method(_MD("data_disconnect","func","from_node","from_port","to_node","to_port"),&VisualScript::data_disconnect); + ClassDB::bind_method(_MD("has_data_connection","func","from_node","from_port","to_node","to_port"),&VisualScript::has_data_connection); + + ClassDB::bind_method(_MD("add_variable","name","default_value","export"),&VisualScript::add_variable,DEFVAL(Variant()),DEFVAL(false)); + ClassDB::bind_method(_MD("has_variable","name"),&VisualScript::has_variable); + ClassDB::bind_method(_MD("remove_variable","name"),&VisualScript::remove_variable); + ClassDB::bind_method(_MD("set_variable_default_value","name","value"),&VisualScript::set_variable_default_value); + ClassDB::bind_method(_MD("get_variable_default_value","name"),&VisualScript::get_variable_default_value); + ClassDB::bind_method(_MD("set_variable_info","name","value"),&VisualScript::_set_variable_info); + ClassDB::bind_method(_MD("get_variable_info","name"),&VisualScript::_get_variable_info); + ClassDB::bind_method(_MD("set_variable_export","name","enable"),&VisualScript::set_variable_export); + ClassDB::bind_method(_MD("get_variable_export","name"),&VisualScript::get_variable_export); + ClassDB::bind_method(_MD("rename_variable","name","new_name"),&VisualScript::rename_variable); + + ClassDB::bind_method(_MD("add_custom_signal","name"),&VisualScript::add_custom_signal); + ClassDB::bind_method(_MD("has_custom_signal","name"),&VisualScript::has_custom_signal); + ClassDB::bind_method(_MD("custom_signal_add_argument","name","type","argname","index"),&VisualScript::custom_signal_add_argument,DEFVAL(-1)); + ClassDB::bind_method(_MD("custom_signal_set_argument_type","name","argidx","type"),&VisualScript::custom_signal_set_argument_type); + ClassDB::bind_method(_MD("custom_signal_get_argument_type","name","argidx"),&VisualScript::custom_signal_get_argument_type); + ClassDB::bind_method(_MD("custom_signal_set_argument_name","name","argidx","argname"),&VisualScript::custom_signal_set_argument_name); + ClassDB::bind_method(_MD("custom_signal_get_argument_name","name","argidx"),&VisualScript::custom_signal_get_argument_name); + ClassDB::bind_method(_MD("custom_signal_remove_argument","argidx"),&VisualScript::custom_signal_remove_argument); + ClassDB::bind_method(_MD("custom_signal_get_argument_count","name"),&VisualScript::custom_signal_get_argument_count); + ClassDB::bind_method(_MD("custom_signal_swap_argument","name","argidx","withidx"),&VisualScript::custom_signal_swap_argument); + ClassDB::bind_method(_MD("remove_custom_signal","name"),&VisualScript::remove_custom_signal); + ClassDB::bind_method(_MD("rename_custom_signal","name","new_name"),&VisualScript::rename_custom_signal); + + //ClassDB::bind_method(_MD("set_variable_info","name","info"),&VScript::set_variable_info); + //ClassDB::bind_method(_MD("get_variable_info","name"),&VScript::set_variable_info); + + ClassDB::bind_method(_MD("set_instance_base_type","type"),&VisualScript::set_instance_base_type); + + ClassDB::bind_method(_MD("_set_data","data"),&VisualScript::_set_data); + ClassDB::bind_method(_MD("_get_data"),&VisualScript::_get_data); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY,"data",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_data"),_SCS("_get_data")); + + ADD_SIGNAL(MethodInfo("node_ports_changed",PropertyInfo(Variant::STRING,"function"),PropertyInfo(Variant::INT,"id"))); +} + +VisualScript::VisualScript() { + + base_type="Object"; + +} + +VisualScript::~VisualScript() { + + while(!functions.empty()) { + remove_function(functions.front()->key()); + } + +} + +//////////////////////////////////////////// + + + +bool VisualScriptInstance::set(const StringName& p_name, const Variant& p_value) { + + + Map<StringName,Variant>::Element *E=variables.find(p_name); + if (!E) + return false; + + E->get()=p_value; + + return true; +} + + +bool VisualScriptInstance::get(const StringName& p_name, Variant &r_ret) const { + + const Map<StringName,Variant>::Element *E=variables.find(p_name); + if (!E) + return false; + + r_ret=E->get(); + return true; +} +void VisualScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const{ + + for (const Map<StringName,VisualScript::Variable>::Element *E=script->variables.front();E;E=E->next()) { + + if (!E->get()._export) + continue; + PropertyInfo p = E->get().info; + p.name=String(E->key()); + p_properties->push_back(p); + + } +} +Variant::Type VisualScriptInstance::get_property_type(const StringName& p_name,bool *r_is_valid) const{ + + + const Map<StringName,VisualScript::Variable>::Element *E=script->variables.find(p_name); + if (!E) { + if (r_is_valid) + *r_is_valid=false; + ERR_FAIL_V(Variant::NIL); + } + + if (r_is_valid) + *r_is_valid=true; + + return E->get().info.type; + +} + +void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const{ + + for (const Map<StringName,VisualScript::Function>::Element *E=script->functions.front();E;E=E->next()) { + + MethodInfo mi; + mi.name=E->key(); + if (E->get().function_id>=0 && E->get().nodes.has(E->get().function_id)) { + + Ref<VisualScriptFunction> vsf = E->get().nodes[E->get().function_id].node; + if (vsf.is_valid()) { + + for(int i=0;i<vsf->get_argument_count();i++) { + PropertyInfo arg; + arg.name=vsf->get_argument_name(i); + arg.type=vsf->get_argument_type(i); + + mi.arguments.push_back(arg); + } + + //vsf->Get_ for now at least it does not return.. + } + } + + p_list->push_back(mi); + } + +} +bool VisualScriptInstance::has_method(const StringName& p_method) const{ + + return script->functions.has(p_method); +} + + +//#define VSDEBUG(m_text) print_line(m_text) +#define VSDEBUG(m_text) + +void VisualScriptInstance::_dependency_step(VisualScriptNodeInstance* node,int p_pass,int *pass_stack,const Variant **input_args,Variant **output_args,Variant *variant_stack,Variant::CallError& r_error,String& error_str,VisualScriptNodeInstance** r_error_node) { + + ERR_FAIL_COND(node->pass_idx==-1); + + if (pass_stack[node->pass_idx]==p_pass) + return; + + pass_stack[node->pass_idx]=p_pass; + + if (!node->dependencies.empty()) { + + int dc = node->dependencies.size(); + VisualScriptNodeInstance **deps=node->dependencies.ptr(); + + for(int i=0;i<dc;i++) { + + _dependency_step(deps[i],p_pass,pass_stack,input_args,output_args,variant_stack,r_error,error_str,r_error_node); + if (r_error.error!=Variant::CallError::CALL_OK) + return; + + } + } + + + for(int i=0;i<node->input_port_count;i++) { + + int index = node->input_ports[i] & VisualScriptNodeInstance::INPUT_MASK; + + + if (node->input_ports[i] & VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT) { + //is a default value (unassigned input port) + input_args[i]=&default_values[index]; + } else { + //regular temporary in stack + input_args[i]=&variant_stack[index]; + + } + } + for(int i=0 ; i<node->output_port_count ; i++) { + output_args[i] = &variant_stack[ node->output_ports[i] ]; + } + + Variant *working_mem=node->working_mem_idx>=0 ? &variant_stack[node->working_mem_idx] : (Variant*)NULL; + + node->step(input_args,output_args,VisualScriptNodeInstance::START_MODE_BEGIN_SEQUENCE,working_mem,r_error,error_str); + //ignore return + if (r_error.error!=Variant::CallError::CALL_OK) { + *r_error_node=node; + } + +} + +Variant VisualScriptInstance::_call_internal(const StringName& p_method, void* p_stack, int p_stack_size, VisualScriptNodeInstance* p_node, int p_flow_stack_pos, int p_pass, bool p_resuming_yield, Variant::CallError &r_error) { + + Map<StringName,Function>::Element *F = functions.find(p_method); + ERR_FAIL_COND_V(!F,Variant()); + Function *f=&F->get(); + + //this call goes separate, so it can e yielded and suspended + Variant *variant_stack=(Variant*)p_stack; + bool *sequence_bits = (bool*)(variant_stack + f->max_stack); + const Variant **input_args=(const Variant**)(sequence_bits+f->node_count); + Variant **output_args=(Variant**)(input_args + max_input_args); + int flow_max = f->flow_stack_size; + int* flow_stack = flow_max? (int*)(output_args + max_output_args) : (int*)NULL; + int *pass_stack = flow_stack + flow_max; + + String error_str; + + VisualScriptNodeInstance* node=p_node; + bool error=false; + int current_node_id=f->node; + Variant return_value; + Variant *working_mem=NULL; + + int flow_stack_pos=p_flow_stack_pos; + +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + VisualScriptLanguage::singleton->enter_function(this,&p_method,variant_stack,&working_mem,¤t_node_id); + } +#endif + + while(true) { + + p_pass++; //increment pass + current_node_id=node->get_id(); + + VSDEBUG("==========AT NODE: "+itos(current_node_id)+" base: "+node->get_base_node()->get_class_name()); + VSDEBUG("AT STACK POS: "+itos(flow_stack_pos)); + + + //setup working mem + working_mem=node->working_mem_idx>=0 ? &variant_stack[node->working_mem_idx] : (Variant*)NULL; + + VSDEBUG("WORKING MEM: "+itos(node->working_mem_idx)); + + if (current_node_id==f->node) { + //if function node, set up function arguments from begining of stack + + for(int i=0;i<f->argument_count;i++) { + input_args[i]=&variant_stack[i]; + } + } else { + + //run dependencies first + + + if (!node->dependencies.empty()) { + + int dc = node->dependencies.size(); + VisualScriptNodeInstance **deps=node->dependencies.ptr(); + + for(int i=0;i<dc;i++) { + + _dependency_step(deps[i],p_pass,pass_stack,input_args,output_args,variant_stack,r_error,error_str,&node); + if (r_error.error!=Variant::CallError::CALL_OK) { + error=true; + current_node_id=node->id; + break; + } + } + } + + if (!error) { + + //setup input pointers normally + VSDEBUG("INPUT PORTS: "+itos(node->input_port_count)); + + for(int i=0 ; i<node->input_port_count ; i++) { + + + int index = node->input_ports[i] & VisualScriptNodeInstance::INPUT_MASK; + + if (node->input_ports[i] & VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT) { + //is a default value (unassigned input port) + input_args[i]=&default_values[index]; + VSDEBUG("\tPORT "+itos(i)+" DEFAULT VAL"); + } else { + //regular temporary in stack + input_args[i]=&variant_stack[index]; + VSDEBUG("PORT "+itos(i)+" AT STACK "+itos(index)); + + } + } + } + } + + if (error) + break; + + //setup output pointers + + VSDEBUG("OUTPUT PORTS: "+itos(node->output_port_count)); + for(int i=0 ; i<node->output_port_count ; i++) { + output_args[i] = &variant_stack[ node->output_ports[i] ]; + VSDEBUG("PORT "+itos(i)+" AT STACK "+itos(node->output_ports[i])); + } + + //do step + + VisualScriptNodeInstance::StartMode start_mode; + { + if (p_resuming_yield) + start_mode=VisualScriptNodeInstance::START_MODE_RESUME_YIELD; + else if (!flow_stack || !(flow_stack[flow_stack_pos] & VisualScriptNodeInstance::FLOW_STACK_PUSHED_BIT)) //if there is a push bit, it means we are continuing a sequence + start_mode=VisualScriptNodeInstance::START_MODE_BEGIN_SEQUENCE; + else + start_mode=VisualScriptNodeInstance::START_MODE_CONTINUE_SEQUENCE; + } + + VSDEBUG("STEP - STARTSEQ: "+itos(start_mode)); + + int ret = node->step(input_args,output_args,start_mode,working_mem,r_error,error_str); + + if (r_error.error!=Variant::CallError::CALL_OK) { + //use error from step + error=true; + break; + } + + if (ret&VisualScriptNodeInstance::STEP_YIELD_BIT) { + //yielded! + if (node->get_working_memory_size()==0) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("A node yielded without working memory, please read the docs on how to yield properly!"); + error=true; + break; + + } else { + Ref<VisualScriptFunctionState> state = *working_mem; + if (!state.is_valid()) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Node yielded, but did not return a function state in the first working memory."); + error=true; + break; + + } + + //step 1, capture all state + state->instance_id=get_owner_ptr()->get_instance_ID(); + state->script_id=get_script()->get_instance_ID(); + state->instance=this; + state->function=p_method; + state->working_mem_index=node->working_mem_idx; + state->variant_stack_size=f->max_stack; + state->node=node; + state->flow_stack_pos=flow_stack_pos; + state->stack.resize(p_stack_size); + state->pass=p_pass; + copymem(state->stack.ptr(),p_stack,p_stack_size); + //step 2, run away, return directly + r_error.error=Variant::CallError::CALL_OK; + + +#ifdef DEBUG_ENABLED + //will re-enter later, so exiting + if (ScriptDebugger::get_singleton()) { + VisualScriptLanguage::singleton->exit_function(); + } +#endif + + return state; + + } + } + +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + // line + bool do_break=false; + + if (ScriptDebugger::get_singleton()->get_lines_left()>0) { + + if (ScriptDebugger::get_singleton()->get_depth()<=0) + ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 ); + if (ScriptDebugger::get_singleton()->get_lines_left()<=0) + do_break=true; + } + + if (ScriptDebugger::get_singleton()->is_breakpoint(current_node_id,source)) + do_break=true; + + if (do_break) { + VisualScriptLanguage::singleton->debug_break("Breakpoint",true); + } + + ScriptDebugger::get_singleton()->line_poll(); + + } +#endif + int output = ret & VisualScriptNodeInstance::STEP_MASK; + + VSDEBUG("STEP RETURN: "+itos(ret)); + + if (ret & VisualScriptNodeInstance::STEP_EXIT_FUNCTION_BIT) { + if (node->get_working_memory_size()==0) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Return value must be assigned to first element of node working memory! Fix your node please."); + error=true; + } else { + //assign from working memory, first element + return_value=*working_mem; + } + + VSDEBUG("EXITING FUNCTION - VALUE "+String(return_value)); + break; //exit function requested, bye + } + + VisualScriptNodeInstance *next=NULL; //next node + + if ( (ret==output || ret&VisualScriptNodeInstance::STEP_FLAG_PUSH_STACK_BIT) && node->sequence_output_count) { + //if no exit bit was set, and has sequence outputs, guess next node + if (output<0 || output>=node->sequence_output_count) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Node returned an invalid sequence output: ")+itos(output); + error=true; + break; + } + + next = node->sequence_outputs[output]; + if (next) { + VSDEBUG("GOT NEXT NODE - "+itos(next->get_id())); + } else { + VSDEBUG("GOT NEXT NODE - NULL"); + } + } + + if (flow_stack) { + + //update flow stack pos (may have changed) + flow_stack[flow_stack_pos] = current_node_id; + + //add stack push bit if requested + if (ret & VisualScriptNodeInstance::STEP_FLAG_PUSH_STACK_BIT) { + + flow_stack[flow_stack_pos] |= VisualScriptNodeInstance::FLOW_STACK_PUSHED_BIT; + sequence_bits[ node ->sequence_index ]=true; //remember sequence bit + VSDEBUG("NEXT SEQ - FLAG BIT"); + } else { + sequence_bits[ node ->sequence_index ]=false; //forget sequence bit + VSDEBUG("NEXT SEQ - NORMAL"); + } + + + if (ret & VisualScriptNodeInstance::STEP_FLAG_GO_BACK_BIT) { + //go back request + + if (flow_stack_pos>0) { + flow_stack_pos--; + node = instances[ flow_stack[flow_stack_pos] & VisualScriptNodeInstance::FLOW_STACK_MASK ]; + VSDEBUG("NEXT IS GO BACK"); + } else { + VSDEBUG("NEXT IS GO BACK, BUT NO NEXT SO EXIT"); + break; //simply exit without value or error + } + } else if (next) { + + + if (sequence_bits[next->sequence_index]) { + // what happened here is that we are entering a node that is in the middle of doing a sequence (pushed stack) from the front + // because each node has a working memory, we can't really do a sub-sequence + // as a result, the sequence will be restarted and the stack will roll back to find where this node + // started the sequence + + bool found = false; + + for(int i=flow_stack_pos;i>=0;i--) { + + + if ( (flow_stack[i] & VisualScriptNodeInstance::FLOW_STACK_MASK ) == next->get_id() ) { + flow_stack_pos=i; //roll back and remove bit + flow_stack[i]=next->get_id(); + sequence_bits[next->sequence_index]=false; + found=true; + } + } + + if (!found) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Found sequence bit but not the node in the stack, report bug!"); + error=true; + break; + } + + node=next; + VSDEBUG("RE-ENTERED A LOOP, RETURNED STACK POS TO - "+itos(flow_stack_pos)); + + } else { + // check for stack overflow + if (flow_stack_pos+1 >= flow_max) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + error_str=RTR("Stack overflow with stack depth: ")+itos(output); + error=true; + break; + } + + node = next; + + flow_stack_pos++; + flow_stack[flow_stack_pos]=node->get_id(); + + VSDEBUG("INCREASE FLOW STACK"); + + } + + } else { + //no next node, try to go back in stack to pushed bit + + bool found = false; + + for(int i=flow_stack_pos;i>=0;i--) { + + VSDEBUG("FS "+itos(i)+" - "+itos(flow_stack[i])); + if (flow_stack[i] & VisualScriptNodeInstance::FLOW_STACK_PUSHED_BIT) { + + node = instances[ flow_stack[i] & VisualScriptNodeInstance::FLOW_STACK_MASK ]; + flow_stack_pos=i; + found=true; + break; + } + } + + if (!found) { + VSDEBUG("NO NEXT NODE, NO GO BACK, EXITING"); + break; //done, couldn't find a push stack bit + } + + VSDEBUG("NO NEXT NODE, GO BACK TO: "+itos(flow_stack_pos)); + + } + } else { + + node=next; //stackless mode, simply assign next node + } + + } + + + + if (error) { + + //error + // function, file, line, error, explanation + String err_file = script->get_path(); + String err_func = p_method; + int err_line=current_node_id; //not a line but it works as one + + if (node && (r_error.error!=Variant::CallError::CALL_ERROR_INVALID_METHOD || error_str==String())) { + + if (error_str!=String()) { + error_str+=" "; + } + + if (r_error.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { + int errorarg=r_error.argument; + error_str+="Cannot convert argument "+itos(errorarg+1)+" to "+Variant::get_type_name(r_error.expected)+"."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { + error_str+="Expected "+itos(r_error.argument)+" arguments."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { + error_str+="Expected "+itos(r_error.argument)+" arguments."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) { + error_str+="Invalid Call."; + } else if (r_error.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) { + error_str+="Base Instance is null"; + } + } + + + //if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) { + // debugger break did not happen + + if (!VisualScriptLanguage::singleton->debug_break(error_str,false)) { + + _err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,error_str.utf8().get_data(),ERR_HANDLER_SCRIPT); + } + + //} + } else { + + + //return_value= + } + +#ifdef DEBUG_ENABLED + if (ScriptDebugger::get_singleton()) { + VisualScriptLanguage::singleton->exit_function(); + } +#endif + + //clean up variant stack + for(int i=0;i<f->max_stack;i++) { + variant_stack[i].~Variant(); + } + + + return return_value; +} + + +Variant VisualScriptInstance::call(const StringName& p_method, const Variant** p_args, int p_argcount, Variant::CallError &r_error){ + + r_error.error=Variant::CallError::CALL_OK; //ok by default + + Map<StringName,Function>::Element *F = functions.find(p_method); + if (!F) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + } + + VSDEBUG("CALLING: "+String(p_method)); + + Function *f=&F->get(); + + int total_stack_size=0; + + total_stack_size+=f->max_stack*sizeof(Variant); //variants + total_stack_size+=f->node_count*sizeof(bool); + total_stack_size+=(max_input_args+max_output_args)*sizeof(Variant*); //arguments + total_stack_size+=f->flow_stack_size*sizeof(int); //flow + total_stack_size+=f->pass_stack_size*sizeof(int); + + VSDEBUG("STACK SIZE: "+itos(total_stack_size)); + VSDEBUG("STACK VARIANTS: : "+itos(f->max_stack)); + VSDEBUG("SEQBITS: : "+itos(f->node_count)); + VSDEBUG("MAX INPUT: "+itos(max_input_args)); + VSDEBUG("MAX OUTPUT: "+itos(max_output_args)); + VSDEBUG("FLOW STACK SIZE: "+itos(f->flow_stack_size)); + VSDEBUG("PASS STACK SIZE: "+itos(f->pass_stack_size)); + + void *stack = alloca(total_stack_size); + + Variant *variant_stack=(Variant*)stack; + bool *sequence_bits = (bool*)(variant_stack + f->max_stack); + const Variant **input_args=(const Variant**)(sequence_bits+f->node_count); + Variant **output_args=(Variant**)(input_args + max_input_args); + int flow_max = f->flow_stack_size; + int* flow_stack = flow_max? (int*)(output_args + max_output_args) : (int*)NULL; + int *pass_stack = flow_stack + flow_max; + + for(int i=0;i<f->node_count;i++) { + sequence_bits[i]=false; //all starts as false + } + + zeromem(pass_stack,f->pass_stack_size*sizeof(int)); + + Map<int,VisualScriptNodeInstance*>::Element *E = instances.find(f->node); + if (!E) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + + ERR_EXPLAIN("No VisualScriptFunction node in function!"); + ERR_FAIL_V(Variant()); + } + + VisualScriptNodeInstance *node = E->get(); + + + if (flow_stack) { + flow_stack[0]=node->get_id(); + } + + VSDEBUG("ARGUMENTS: "+itos(f->argument_count)=" RECEIVED: "+itos(p_argcount)); + + if (p_argcount<f->argument_count) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=node->get_input_port_count(); + + return Variant(); + } + + if (p_argcount>f->argument_count) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument=node->get_input_port_count(); + + return Variant(); + } + + //allocate variant stack + for(int i=0;i<f->max_stack;i++) { + memnew_placement(&variant_stack[i],Variant); + } + + //allocate function arguments (must be copied for yield to work properly) + for(int i=0;i<p_argcount;i++) { + variant_stack[i]=*p_args[i]; + } + + return _call_internal(p_method,stack,total_stack_size,node,0,0,false,r_error); + + +} + +void VisualScriptInstance::notification(int p_notification){ + + //do nothing as this is called using virtual + + Variant what=p_notification; + const Variant*whatp=&what; + Variant::CallError ce; + call(VisualScriptLanguage::singleton->notification,&whatp,1,ce); //do as call + +} + +Ref<Script> VisualScriptInstance::get_script() const{ + + return script; +} + +ScriptInstance::RPCMode VisualScriptInstance::get_rpc_mode(const StringName& p_method) const { + + const Map<StringName,VisualScript::Function>::Element *E = script->functions.find(p_method); + if (!E) { + return RPC_MODE_DISABLED; + } + + if (E->get().function_id>=0 && E->get().nodes.has(E->get().function_id)) { + + Ref<VisualScriptFunction> vsf = E->get().nodes[E->get().function_id].node; + if (vsf.is_valid()) { + + return vsf->get_rpc_mode(); + } + } + + return RPC_MODE_DISABLED; +} + +ScriptInstance::RPCMode VisualScriptInstance::get_rset_mode(const StringName& p_variable) const { + + return RPC_MODE_DISABLED; +} + + +void VisualScriptInstance::create(const Ref<VisualScript>& p_script,Object *p_owner) { + + script=p_script; + owner=p_owner; + source=p_script->get_path(); + + max_input_args = 0; + max_output_args = 0; + + if (p_owner->cast_to<Node>()) { + //turn on these if they exist and base is a node + Node* node = p_owner->cast_to<Node>(); + if (p_script->functions.has("_process")) + node->set_process(true); + if (p_script->functions.has("_fixed_process")) + node->set_fixed_process(true); + if (p_script->functions.has("_input")) + node->set_process_input(true); + if (p_script->functions.has("_unhandled_input")) + node->set_process_unhandled_input(true); + if (p_script->functions.has("_unhandled_key_input")) + node->set_process_unhandled_key_input(true); + } + + for(const Map<StringName,VisualScript::Variable>::Element *E=script->variables.front();E;E=E->next()) { + variables[E->key()]=E->get().default_value; + //no hacer que todo exporte, solo las que queres! + } + + + for(const Map<StringName,VisualScript::Function>::Element *E=script->functions.front();E;E=E->next()) { + + Function function; + function.node=E->get().function_id; + function.max_stack=0; + function.flow_stack_size=0; + function.pass_stack_size=0; + function.node_count=0; + Map<StringName,int> local_var_indices; + + if (function.node<0) { + VisualScriptLanguage::singleton->debug_break_parse(get_script()->get_path(),0,"No start node in function: "+String(E->key())); + + ERR_CONTINUE( function.node < 0 ); + } + + { + Ref<VisualScriptFunction> func_node = script->get_node(E->key(),E->get().function_id); + + if (func_node.is_null()) { + VisualScriptLanguage::singleton->debug_break_parse(get_script()->get_path(),0,"No VisualScriptFunction typed start node in function: "+String(E->key())); + } + + ERR_CONTINUE( !func_node.is_valid() ); + + function.argument_count=func_node->get_argument_count(); + function.max_stack+=function.argument_count; + function.flow_stack_size= func_node->is_stack_less() ? 0 : func_node->get_stack_size(); + + } + + //multiple passes are required to set up this complex thing.. + + + + + //first create the nodes + for (const Map<int,VisualScript::Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + + Ref<VisualScriptNode> node = F->get().node; + VisualScriptNodeInstance *instance = node->instance(this); //create instance + ERR_FAIL_COND(!instance); + + instance->base=node.ptr(); + + instance->id=F->key(); + instance->input_port_count = node->get_input_value_port_count(); + instance->input_ports=NULL; + instance->output_port_count = node->get_output_value_port_count(); + instance->output_ports=NULL; + instance->sequence_output_count = node->get_output_sequence_port_count(); + instance->sequence_index=function.node_count++; + instance->sequence_outputs=NULL; + instance->pass_idx=-1; + + if (instance->input_port_count) { + instance->input_ports = memnew_arr(int,instance->input_port_count); + for(int i=0;i<instance->input_port_count;i++) { + + instance->input_ports[i]=-1; //if not assigned, will become default value + } + } + + if (instance->output_port_count) { + instance->output_ports = memnew_arr(int,instance->output_port_count); + for(int i=0;i<instance->output_port_count;i++) { + instance->output_ports[i]=-1; //if not assigned, will output to trash + } + } + + if (instance->sequence_output_count) { + instance->sequence_outputs = memnew_arr(VisualScriptNodeInstance*,instance->sequence_output_count); + for(int i=0;i<instance->sequence_output_count;i++) { + instance->sequence_outputs[i]=NULL; //if it remains null, flow ends here + } + } + + if (node->cast_to<VisualScriptLocalVar>() || node->cast_to<VisualScriptLocalVarSet>()) { + //working memory is shared only for this node, for the same variables + Ref<VisualScriptLocalVar> vslv = node; + + StringName var_name; + + if (node->cast_to<VisualScriptLocalVar>()) + var_name = String(node->cast_to<VisualScriptLocalVar>()->get_var_name()).strip_edges(); + else + var_name = String(node->cast_to<VisualScriptLocalVarSet>()->get_var_name()).strip_edges(); + + if (!local_var_indices.has(var_name)) { + local_var_indices[var_name]=function.max_stack; + function.max_stack++; + } + + instance->working_mem_idx=local_var_indices[var_name]; + + } else if (instance->get_working_memory_size()) { + instance->working_mem_idx = function.max_stack; + function.max_stack+=instance->get_working_memory_size(); + } else { + instance->working_mem_idx=-1; //no working mem + } + + max_input_args = MAX( max_input_args, instance->input_port_count ); + max_output_args = MAX( max_output_args, instance->output_port_count ); + + instances[F->key()]=instance; + + + } + + function.trash_pos = function.max_stack++; //create pos for trash + + //second pass, do data connections + + for(const Set<VisualScript::DataConnection>::Element *F=E->get().data_connections.front();F;F=F->next()) { + + VisualScript::DataConnection dc = F->get(); + ERR_CONTINUE(!instances.has(dc.from_node)); + VisualScriptNodeInstance *from = instances[dc.from_node]; + ERR_CONTINUE(!instances.has(dc.to_node)); + VisualScriptNodeInstance *to = instances[dc.to_node]; + ERR_CONTINUE(dc.from_port >= from->output_port_count); + ERR_CONTINUE(dc.to_port >= to->input_port_count); + + if (from->output_ports[dc.from_port]==-1) { + + int stack_pos = function.max_stack++; + from->output_ports[dc.from_port] = stack_pos; + } + + + if (from->get_sequence_output_count()==0 && to->dependencies.find(from)==-1) { + //if the node we are reading from has no output sequence, we must call step() before reading from it. + if (from->pass_idx==-1) { + from->pass_idx=function.pass_stack_size; + function.pass_stack_size++; + } + to->dependencies.push_back(from); + } + + to->input_ports[dc.to_port] = from->output_ports[dc.from_port]; //read from wherever the stack is + + } + + //third pass, do sequence connections + + for(const Set<VisualScript::SequenceConnection>::Element *F=E->get().sequence_connections.front();F;F=F->next()) { + + VisualScript::SequenceConnection sc = F->get(); + ERR_CONTINUE(!instances.has(sc.from_node)); + VisualScriptNodeInstance *from = instances[sc.from_node]; + ERR_CONTINUE(!instances.has(sc.to_node)); + VisualScriptNodeInstance *to = instances[sc.to_node]; + ERR_CONTINUE(sc.from_output >= from->sequence_output_count); + + from->sequence_outputs[sc.from_output]=to; + + } + + //fourth pass: + // 1) unassigned input ports to default values + // 2) connect unassigned output ports to trash + + + for (const Map<int,VisualScript::Function::NodeData>::Element *F=E->get().nodes.front();F;F=F->next()) { + + ERR_CONTINUE(!instances.has(F->key())); + + Ref<VisualScriptNode> node = F->get().node; + VisualScriptNodeInstance *instance = instances[F->key()]; + + // conect to default values + for(int i=0;i<instance->input_port_count;i++) { + if (instance->input_ports[i]==-1) { + + //unassigned, connect to default val + instance->input_ports[i] = default_values.size() | VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT; + default_values.push_back( node->get_default_input_value(i) ); + } + } + + // conect to trash + for(int i=0;i<instance->output_port_count;i++) { + if (instance->output_ports[i]==-1) { + instance->output_ports[i] = function.trash_pos; //trash is same for all + } + } + } + + + functions[E->key()]=function; + } +} + +ScriptLanguage *VisualScriptInstance::get_language(){ + + return VisualScriptLanguage::singleton; +} + + +VisualScriptInstance::VisualScriptInstance() { + + +} + +VisualScriptInstance::~VisualScriptInstance() { + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->lock(); + + script->instances.erase(owner); + + if (VisualScriptLanguage::singleton->lock) + VisualScriptLanguage::singleton->lock->unlock(); + + for (Map<int,VisualScriptNodeInstance*>::Element *E=instances.front();E;E=E->next()) { + memdelete(E->get()); + } +} + + + +///////////////////////////////////////////// + + +///////////////////// + + +Variant VisualScriptFunctionState::_signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error) { + + ERR_FAIL_COND_V(function==StringName(),Variant()); + +#ifdef DEBUG_ENABLED + if (instance_id && !ObjectDB::get_instance(instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (script_id && !ObjectDB::get_instance(script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + + r_error.error=Variant::CallError::CALL_OK; + + Array args; + + if (p_argcount==0) { + r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument=1; + return Variant(); + } else if (p_argcount==1) { + //noooneee, reserved for me, me and only me. + } else { + + for(int i=0;i<p_argcount-1;i++) { + args.push_back(*p_args[i]); + } + } + + Ref<VisualScriptFunctionState> self = *p_args[p_argcount-1]; //hi, I'm myself, needed this to remain alive. + + if (self.is_null()) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=p_argcount-1; + r_error.expected=Variant::OBJECT; + return Variant(); + } + + r_error.error=Variant::CallError::CALL_OK; + + Variant *working_mem = ((Variant*)stack.ptr()) + working_mem_index; + + *working_mem=args; //arguments go to working mem. + + Variant ret = instance->_call_internal(function,stack.ptr(),stack.size(),node,flow_stack_pos,pass,true,r_error); + function=StringName(); //invalidate + return ret; +} + +void VisualScriptFunctionState::connect_to_signal(Object* p_obj, const String& p_signal, Array p_binds) { + + Vector<Variant> binds; + for(int i=0;i<p_binds.size();i++) { + binds.push_back(p_binds[i]); + } + binds.push_back(Ref<VisualScriptFunctionState>(this)); //add myself on the back to avoid dying from unreferencing + p_obj->connect(p_signal,this,"_signal_callback",binds); +} + +bool VisualScriptFunctionState::is_valid() const { + + return function!=StringName(); +} + +Variant VisualScriptFunctionState::resume(Array p_args) { + + ERR_FAIL_COND_V(function==StringName(),Variant()); +#ifdef DEBUG_ENABLED + if (instance_id && !ObjectDB::get_instance(instance_id)) { + ERR_EXPLAIN("Resumed after yield, but class instance is gone"); + ERR_FAIL_V(Variant()); + } + + if (script_id && !ObjectDB::get_instance(script_id)) { + ERR_EXPLAIN("Resumed after yield, but script is gone"); + ERR_FAIL_V(Variant()); + } +#endif + + Variant::CallError r_error; + r_error.error=Variant::CallError::CALL_OK; + + Variant *working_mem = ((Variant*)stack.ptr()) + working_mem_index; + + *working_mem=p_args; //arguments go to working mem. + + Variant ret= instance->_call_internal(function,stack.ptr(),stack.size(),node,flow_stack_pos,pass,true,r_error); + function=StringName(); //invalidate + return ret; +} + + +void VisualScriptFunctionState::_bind_methods() { + + ClassDB::bind_method(_MD("connect_to_signal","obj","signals","args"),&VisualScriptFunctionState::connect_to_signal); + ClassDB::bind_method(_MD("resume:Array","args"),&VisualScriptFunctionState::resume,DEFVAL(Variant())); + ClassDB::bind_method(_MD("is_valid"),&VisualScriptFunctionState::is_valid); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT,"_signal_callback",&VisualScriptFunctionState::_signal_callback,MethodInfo("_signal_callback")); +} + +VisualScriptFunctionState::VisualScriptFunctionState() { + +} + +VisualScriptFunctionState::~VisualScriptFunctionState() { + + if (function!=StringName()) { + Variant *s = ((Variant*)stack.ptr()); + for(int i=0;i<variant_stack_size;i++) { + s[i].~Variant(); + } + } +} + + + + + +/////////////////////////////////////////////// + +String VisualScriptLanguage::get_name() const { + + return "VisualScript"; +} + +/* LANGUAGE FUNCTIONS */ +void VisualScriptLanguage::init() { + + +} +String VisualScriptLanguage::get_type() const { + + return "VisualScript"; +} +String VisualScriptLanguage::get_extension() const { + + return "vs"; +} +Error VisualScriptLanguage::execute_file(const String& p_path) { + + return OK; +} +void VisualScriptLanguage::finish() { + + +} + +/* EDITOR FUNCTIONS */ +void VisualScriptLanguage::get_reserved_words(List<String> *p_words) const { + + +} +void VisualScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { + + +} +void VisualScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { + + +} +Ref<Script> VisualScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { + + Ref<VisualScript> script; + script.instance(); + script->set_instance_base_type(p_base_class_name); + return script; +} +bool VisualScriptLanguage::validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path,List<String> *r_functions) const { + + return false; +} +Script *VisualScriptLanguage::create_script() const { + + return memnew( VisualScript ); +} +bool VisualScriptLanguage::has_named_classes() const { + + return false; +} +int VisualScriptLanguage::find_function(const String& p_function,const String& p_code) const { + + return -1; +} +String VisualScriptLanguage::make_function(const String& p_class,const String& p_name,const PoolStringArray& p_args) const { + + return String(); +} + +void VisualScriptLanguage::auto_indent_code(String& p_code,int p_from_line,int p_to_line) const { + + +} +void VisualScriptLanguage::add_global_constant(const StringName& p_variable,const Variant& p_value) { + + +} + + +/* DEBUGGER FUNCTIONS */ + + + +bool VisualScriptLanguage::debug_break_parse(const String& p_file, int p_node,const String& p_error) { + //break because of parse error + + if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { + + _debug_parse_err_node=p_node; + _debug_parse_err_file=p_file; + _debug_error=p_error; + ScriptDebugger::get_singleton()->debug(this,false); + return true; + } else { + return false; + } + +} + +bool VisualScriptLanguage::debug_break(const String& p_error,bool p_allow_continue) { + + if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { + + _debug_parse_err_node=-1; + _debug_parse_err_file=""; + _debug_error=p_error; + ScriptDebugger::get_singleton()->debug(this,p_allow_continue); + return true; + } else { + return false; + } + +} + + +String VisualScriptLanguage::debug_get_error() const { + + return _debug_error; +} + +int VisualScriptLanguage::debug_get_stack_level_count() const { + + if (_debug_parse_err_node>=0) + return 1; + + + return _debug_call_stack_pos; +} +int VisualScriptLanguage::debug_get_stack_level_line(int p_level) const { + + if (_debug_parse_err_node>=0) + return _debug_parse_err_node; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,-1); + + int l = _debug_call_stack_pos - p_level -1; + + return *(_call_stack[l].current_id); + +} +String VisualScriptLanguage::debug_get_stack_level_function(int p_level) const { + + if (_debug_parse_err_node>=0) + return ""; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); + int l = _debug_call_stack_pos - p_level -1; + return *_call_stack[l].function; +} +String VisualScriptLanguage::debug_get_stack_level_source(int p_level) const { + + if (_debug_parse_err_node>=0) + return _debug_parse_err_file; + + ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); + int l = _debug_call_stack_pos - p_level -1; + return _call_stack[l].instance->get_script_ptr()->get_path(); + +} +void VisualScriptLanguage::debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_node>=0) + return; + + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + + int l = _debug_call_stack_pos - p_level -1; + const StringName *f = _call_stack[l].function; + + ERR_FAIL_COND(!_call_stack[l].instance->functions.has(*f)); + //VisualScriptInstance::Function *func = &_call_stack[l].instance->functions[*f]; + + VisualScriptNodeInstance *node =_call_stack[l].instance->instances[*_call_stack[l].current_id]; + ERR_FAIL_COND(!node); + + p_locals->push_back("node_name"); + p_values->push_back(node->get_base_node()->get_text()); + + for(int i=0;i<node->input_port_count;i++) { + String name = node->get_base_node()->get_input_value_port_info(i).name; + if (name==String()) { + name="in_"+itos(i); + } + + p_locals->push_back("input/"+name); + + //value is trickier + + int in_from = node->input_ports[i]; + int in_value = in_from&VisualScriptNodeInstance::INPUT_MASK; + + if (in_from&VisualScriptNodeInstance::INPUT_DEFAULT_VALUE_BIT) { + p_values->push_back(_call_stack[l].instance->default_values[in_value]); + } else { + p_values->push_back( _call_stack[l].stack[ in_value] ); + } + } + + for(int i=0;i<node->output_port_count;i++) { + + String name = node->get_base_node()->get_output_value_port_info(i).name; + if (name==String()) { + name="out_"+itos(i); + } + + p_locals->push_back("output/"+name); + + //value is trickier + + int in_from = node->output_ports[i]; + p_values->push_back( _call_stack[l].stack[ in_from] ); + + } + + for(int i=0;i<node->get_working_memory_size();i++) { + p_locals->push_back("working_mem/mem_"+itos(i)); + p_values->push_back( (*_call_stack[l].work_mem)[i]); + } + +/* + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + + + VisualFunction *f = _call_stack[l].function; + + List<Pair<StringName,int> > locals; + + f->debug_get_stack_member_state(*_call_stack[l].line,&locals); + for( List<Pair<StringName,int> >::Element *E = locals.front();E;E=E->next() ) { + + p_locals->push_back(E->get().first); + p_values->push_back(_call_stack[l].stack[E->get().second]); + } +*/ +} +void VisualScriptLanguage::debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_node>=0) + return; + + ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); + int l = _debug_call_stack_pos - p_level -1; + + + Ref<VisualScript> vs = _call_stack[l].instance->get_script(); + if (vs.is_null()) + return; + + List<StringName> vars; + vs->get_variable_list(&vars); + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + Variant v; + if (_call_stack[l].instance->get_variable(E->get(),&v)) { + p_members->push_back("variables/"+E->get()); + p_values->push_back(v); + } + } +} + +void VisualScriptLanguage::debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems,int p_max_depth) { + + //no globals are really reachable in gdscript +} +String VisualScriptLanguage::debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems,int p_max_depth) { + + if (_debug_parse_err_node>=0) + return ""; + return ""; +} + + + +void VisualScriptLanguage::reload_all_scripts() { + + +} +void VisualScriptLanguage::reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload) { + + +} +/* LOADER FUNCTIONS */ + +void VisualScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("vs"); + +} +void VisualScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { + + +} +void VisualScriptLanguage::get_public_constants(List<Pair<String,Variant> > *p_constants) const { + + +} + +void VisualScriptLanguage::profiling_start() { + + +} +void VisualScriptLanguage::profiling_stop() { + + +} + +int VisualScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,int p_info_max) { + + return 0; +} + +int VisualScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr,int p_info_max) { + + return 0; +} + + +VisualScriptLanguage* VisualScriptLanguage::singleton=NULL; + + +void VisualScriptLanguage::add_register_func(const String& p_name,VisualScriptNodeRegisterFunc p_func) { + + ERR_FAIL_COND(register_funcs.has(p_name)); + register_funcs[p_name]=p_func; +} + +Ref<VisualScriptNode> VisualScriptLanguage::create_node_from_name(const String& p_name) { + + ERR_FAIL_COND_V(!register_funcs.has(p_name),Ref<VisualScriptNode>()); + + return register_funcs[p_name](p_name); +} + +void VisualScriptLanguage::get_registered_node_names(List<String> *r_names) { + + for (Map<String,VisualScriptNodeRegisterFunc>::Element *E=register_funcs.front();E;E=E->next()) { + r_names->push_back(E->key()); + } +} + + +VisualScriptLanguage::VisualScriptLanguage() { + + notification="_notification"; + _step="_step"; + _subcall="_subcall"; + singleton=this; +#ifndef NO_THREADS + lock = Mutex::create(); +#endif + + + _debug_parse_err_node=-1; + _debug_parse_err_file=""; + _debug_call_stack_pos=0; + int dmcs=GLOBAL_DEF("debug/script/max_call_stack",1024); + if (ScriptDebugger::get_singleton()) { + //debugging enabled! + _debug_max_call_stack = dmcs; + if (_debug_max_call_stack<1024) + _debug_max_call_stack=1024; + _call_stack = memnew_arr( CallLevel, _debug_max_call_stack+1 ); + + } else { + _debug_max_call_stack=0; + _call_stack=NULL; + } + +} + +VisualScriptLanguage::~VisualScriptLanguage() { + + if (lock) + memdelete(lock); + + if (_call_stack) { + memdelete_arr(_call_stack); + } + singleton=NULL; +} diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h new file mode 100644 index 000000000..acd1c3a67 --- /dev/null +++ b/modules/visual_script/visual_script.h @@ -0,0 +1,616 @@ +#ifndef VSCRIPT_H +#define VSCRIPT_H + +#include "script_language.h" +#include "os/thread.h" + +class VisualScriptInstance; +class VisualScriptNodeInstance; +class VisualScript; + +class VisualScriptNode : public Resource { + GDCLASS(VisualScriptNode,Resource) + +friend class VisualScript; + + Set<VisualScript*> scripts_used; + + Array default_input_values; + bool breakpoint; + + void _set_default_input_values(Array p_values); + Array _get_default_input_values() const; + + void validate_input_default_values(); +protected: + + void _notification(int p_what); + void ports_changed_notify(); + static void _bind_methods(); +public: + + Ref<VisualScript> get_visual_script() const; + + virtual int get_output_sequence_port_count() const=0; + virtual bool has_input_sequence_port() const=0; + + virtual String get_output_sequence_port_text(int p_port) const=0; + + virtual bool has_mixed_input_and_sequence_ports() const { return false; } + + virtual int get_input_value_port_count() const=0; + virtual int get_output_value_port_count() const=0; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const=0; + virtual PropertyInfo get_output_value_port_info(int p_idx) const=0; + + void set_default_input_value(int p_port,const Variant& p_value); + Variant get_default_input_value(int p_port) const; + + virtual String get_caption() const=0; + virtual String get_text() const=0; + virtual String get_category() const=0; + + //used by editor, this is not really saved + void set_breakpoint(bool p_breakpoint); + bool is_breakpoint() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance)=0; + + struct TypeGuess { + + Variant::Type type; + InputEvent::Type ev_type; + StringName GDCLASS; + Ref<Script> script; + + TypeGuess() { type=Variant::NIL; ev_type=InputEvent::NONE; } + }; + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptNode(); +}; + + +class VisualScriptNodeInstance { +friend class VisualScriptInstance; +friend class VisualScriptLanguage; //for debugger + + + enum { //input argument addressing + INPUT_SHIFT=1<<24, + INPUT_MASK=INPUT_SHIFT-1, + INPUT_DEFAULT_VALUE_BIT=INPUT_SHIFT, // from unassigned input port, using default value (edited by user) + }; + + + int id; + int sequence_index; + VisualScriptNodeInstance **sequence_outputs; + int sequence_output_count; + Vector<VisualScriptNodeInstance*> dependencies; + int *input_ports; + int input_port_count; + int *output_ports; + int output_port_count; + int working_mem_idx; + int pass_idx; + + VisualScriptNode *base; + +public: + + enum StartMode { + START_MODE_BEGIN_SEQUENCE, + START_MODE_CONTINUE_SEQUENCE, + START_MODE_RESUME_YIELD + }; + + enum { + STEP_SHIFT=1<<24, + STEP_MASK=STEP_SHIFT-1, + STEP_FLAG_PUSH_STACK_BIT=STEP_SHIFT, //push bit to stack + STEP_FLAG_GO_BACK_BIT=STEP_SHIFT<<1, //go back to previous node + STEP_NO_ADVANCE_BIT=STEP_SHIFT<<2, //do not advance past this node + STEP_EXIT_FUNCTION_BIT=STEP_SHIFT<<3, //return from function + STEP_YIELD_BIT=STEP_SHIFT<<4, //yield (will find VisualScriptFunctionState state in first working memory) + + FLOW_STACK_PUSHED_BIT=1<<30, //in flow stack, means bit was pushed (must go back here if end of sequence) + FLOW_STACK_MASK=FLOW_STACK_PUSHED_BIT-1 + + }; + + _FORCE_INLINE_ int get_input_port_count() const { return input_port_count; } + _FORCE_INLINE_ int get_output_port_count() const { return output_port_count; } + _FORCE_INLINE_ int get_sequence_output_count() const { return sequence_output_count; } + + _FORCE_INLINE_ int get_id() const { return id; } + + virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str)=0; //do a step, return which sequence port to go out + + Ref<VisualScriptNode> get_base_node() { return Ref<VisualScriptNode>( base ); } + + VisualScriptNodeInstance(); + virtual ~VisualScriptNodeInstance(); +}; + + +class VisualScript : public Script { + + GDCLASS( VisualScript, Script ) + + RES_BASE_EXTENSION("vs"); + +public: + + struct SequenceConnection { + + union { + + struct { + uint64_t from_node : 24; + uint64_t from_output : 16; + uint64_t to_node : 24; + }; + uint64_t id; + }; + + bool operator<(const SequenceConnection& p_connection) const { + + return id<p_connection.id; + } + }; + + struct DataConnection { + + union { + + struct { + uint64_t from_node : 24; + uint64_t from_port : 8; + uint64_t to_node : 24; + uint64_t to_port : 8; + }; + uint64_t id; + }; + + bool operator<(const DataConnection& p_connection) const { + + return id<p_connection.id; + } + }; + + +private: +friend class VisualScriptInstance; + + StringName base_type; + struct Argument { + String name; + Variant::Type type; + }; + + struct Function { + struct NodeData { + Point2 pos; + Ref<VisualScriptNode> node; + }; + + Map<int,NodeData> nodes; + + Set<SequenceConnection> sequence_connections; + + Set<DataConnection> data_connections; + + int function_id; + + Vector2 scroll; + + + Function() { function_id=-1; } + + }; + + struct Variable { + PropertyInfo info; + Variant default_value; + bool _export; + }; + + + + Map<StringName,Function> functions; + Map<StringName,Variable> variables; + Map<StringName,Vector<Argument> > custom_signals; + + Map<Object*,VisualScriptInstance*> instances; + +#ifdef TOOLS_ENABLED + Set<PlaceHolderScriptInstance*> placeholders; + //void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); + virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); + void _update_placeholders(); +#endif + + void _set_variable_info(const StringName& p_name,const Dictionary& p_info); + Dictionary _get_variable_info(const StringName& p_name) const; + + + void _set_data(const Dictionary& p_data); + Dictionary _get_data() const; + +protected: + + void _node_ports_changed(int p_id); + static void _bind_methods(); +public: + + + void add_function(const StringName& p_name); + bool has_function(const StringName& p_name) const; + void remove_function(const StringName& p_name); + void rename_function(const StringName& p_name,const StringName& p_new_name); + void set_function_scroll(const StringName& p_name, const Vector2& p_scroll); + Vector2 get_function_scroll(const StringName& p_name) const; + void get_function_list(List<StringName> *r_functions) const; + int get_function_node_id(const StringName& p_name) const; + + + void add_node(const StringName& p_func,int p_id,const Ref<VisualScriptNode>& p_node,const Point2& p_pos=Point2()); + void remove_node(const StringName& p_func,int p_id); + bool has_node(const StringName& p_func,int p_id) const; + Ref<VisualScriptNode> get_node(const StringName& p_func,int p_id) const; + void set_node_pos(const StringName& p_func,int p_id,const Point2& p_pos); + Point2 get_node_pos(const StringName& p_func,int p_id) const; + void get_node_list(const StringName& p_func,List<int> *r_nodes) const; + + void sequence_connect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node); + void sequence_disconnect(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node); + bool has_sequence_connection(const StringName& p_func,int p_from_node,int p_from_output,int p_to_node) const; + void get_sequence_connection_list(const StringName& p_func,List<SequenceConnection> *r_connection) const; + + void data_connect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port); + void data_disconnect(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port); + bool has_data_connection(const StringName& p_func,int p_from_node,int p_from_port,int p_to_node,int p_to_port) const; + void get_data_connection_list(const StringName& p_func,List<DataConnection> *r_connection) const; + bool is_input_value_port_connected(const StringName& p_name,int p_node,int p_port) const; + bool get_input_value_port_connection_source(const StringName& p_name,int p_node,int p_port,int *r_node,int *r_port) const; + + void add_variable(const StringName& p_name,const Variant& p_default_value=Variant(),bool p_export=false); + bool has_variable(const StringName& p_name) const; + void remove_variable(const StringName& p_name); + void set_variable_default_value(const StringName& p_name,const Variant& p_value); + Variant get_variable_default_value(const StringName& p_name) const; + void set_variable_info(const StringName& p_name,const PropertyInfo& p_info); + PropertyInfo get_variable_info(const StringName& p_name) const; + void set_variable_export(const StringName& p_name,bool p_export); + bool get_variable_export(const StringName& p_name) const; + void get_variable_list(List<StringName> *r_variables) const; + void rename_variable(const StringName& p_name,const StringName& p_new_name); + + + void add_custom_signal(const StringName& p_name); + bool has_custom_signal(const StringName& p_name) const; + void custom_signal_add_argument(const StringName& p_func,Variant::Type p_type,const String& p_name,int p_index=-1); + void custom_signal_set_argument_type(const StringName& p_func,int p_argidx,Variant::Type p_type); + Variant::Type custom_signal_get_argument_type(const StringName& p_func,int p_argidx) const; + void custom_signal_set_argument_name(const StringName& p_func,int p_argidx,const String& p_name); + String custom_signal_get_argument_name(const StringName& p_func,int p_argidx) const; + void custom_signal_remove_argument(const StringName& p_func,int p_argidx); + int custom_signal_get_argument_count(const StringName& p_func) const; + void custom_signal_swap_argument(const StringName& p_func,int p_argidx,int p_with_argidx); + void remove_custom_signal(const StringName& p_name); + void rename_custom_signal(const StringName& p_name,const StringName& p_new_name); + + void get_custom_signal_list(List<StringName> *r_custom_signals) const; + + int get_available_id() const; + + void set_instance_base_type(const StringName& p_type); + + virtual bool can_instance() const; + + virtual Ref<Script> get_base_script() const; + virtual StringName get_instance_base_type() const; + virtual ScriptInstance* instance_create(Object *p_this); + virtual bool instance_has(const Object *p_this) const; + + + virtual bool has_source_code() const; + virtual String get_source_code() const; + virtual void set_source_code(const String& p_code); + virtual Error reload(bool p_keep_state=false); + + virtual bool is_tool() const; + + virtual String get_node_type() const; + + virtual ScriptLanguage *get_language() const; + + virtual bool has_script_signal(const StringName& p_signal) const; + virtual void get_script_signal_list(List<MethodInfo> *r_signals) const; + + virtual bool get_property_default_value(const StringName& p_property,Variant& r_value) const; + virtual void get_script_method_list(List<MethodInfo> *p_list) const; + + virtual bool has_method(const StringName& p_method) const; + virtual MethodInfo get_method_info(const StringName& p_method) const; + + virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + +#ifdef TOOLS_ENABLED + virtual bool are_subnodes_edited() const; +#endif + + VisualScript(); + ~VisualScript(); +}; + + +class VisualScriptInstance : public ScriptInstance { + Object *owner; + Ref<VisualScript> script; + + Map<StringName,Variant> variables; //using variable path, not script + Map<int,VisualScriptNodeInstance*> instances; + + struct Function { + int node; + int max_stack; + int trash_pos; + int return_pos; + int flow_stack_size; + int pass_stack_size; + int node_count; + int argument_count; + bool valid; + }; + + Map<StringName,Function> functions; + + Vector<Variant> default_values; + int max_input_args,max_output_args; + + StringName source; + + void _dependency_step(VisualScriptNodeInstance* node, int p_pass, int *pass_stack, const Variant **input_args, Variant **output_args, Variant *variant_stack, Variant::CallError& r_error, String& error_str, VisualScriptNodeInstance **r_error_node); + Variant _call_internal(const StringName& p_method, void* p_stack,int p_stack_size, VisualScriptNodeInstance* p_node, int p_flow_stack_pos, int p_pass, bool p_resuming_yield,Variant::CallError &r_error); + + + //Map<StringName,Function> functions; +friend class VisualScriptFunctionState; //for yield +friend class VisualScriptLanguage; //for debugger +public: + virtual bool set(const StringName& p_name, const Variant& p_value); + virtual bool get(const StringName& p_name, Variant &r_ret) const; + virtual void get_property_list(List<PropertyInfo> *p_properties) const; + virtual Variant::Type get_property_type(const StringName& p_name,bool *r_is_valid=NULL) const; + + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName& p_method) const; + virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error); + virtual void notification(int p_notification); + + bool set_variable(const StringName& p_variable,const Variant& p_value) { + + Map<StringName,Variant>::Element *E=variables.find(p_variable); + if (!E) + return false; + + E->get()=p_value; + return true; + } + + bool get_variable(const StringName& p_variable,Variant* r_variable) const { + + const Map<StringName,Variant>::Element *E=variables.find(p_variable); + if (!E) + return false; + + *r_variable=E->get(); + return true; + + } + + virtual Ref<Script> get_script() const; + + _FORCE_INLINE_ VisualScript *get_script_ptr() { return script.ptr(); } + _FORCE_INLINE_ Object *get_owner_ptr() { return owner; } + + void create(const Ref<VisualScript>& p_script,Object *p_owner); + + virtual ScriptLanguage *get_language(); + + virtual RPCMode get_rpc_mode(const StringName& p_method) const; + virtual RPCMode get_rset_mode(const StringName& p_variable) const; + + VisualScriptInstance(); + ~VisualScriptInstance(); +}; + + +class VisualScriptFunctionState : public Reference { + + GDCLASS(VisualScriptFunctionState,Reference); +friend class VisualScriptInstance; + + ObjectID instance_id; + ObjectID script_id; + VisualScriptInstance *instance; + StringName function; + Vector<uint8_t> stack; + int working_mem_index; + int variant_stack_size; + VisualScriptNodeInstance *node; + int flow_stack_pos; + int pass; + + Variant _signal_callback(const Variant** p_args, int p_argcount, Variant::CallError& r_error); +protected: + static void _bind_methods(); +public: + + void connect_to_signal(Object* p_obj,const String& p_signal,Array p_binds); + bool is_valid() const; + Variant resume(Array p_args); + VisualScriptFunctionState(); + ~VisualScriptFunctionState(); +}; + + +typedef Ref<VisualScriptNode> (*VisualScriptNodeRegisterFunc)(const String& p_type); + +class VisualScriptLanguage : public ScriptLanguage { + + Map<String,VisualScriptNodeRegisterFunc> register_funcs; + + struct CallLevel { + + Variant *stack; + Variant **work_mem; + const StringName *function; + VisualScriptInstance *instance; + int *current_id; + + }; + + + int _debug_parse_err_node; + String _debug_parse_err_file; + String _debug_error; + int _debug_call_stack_pos; + int _debug_max_call_stack; + CallLevel *_call_stack; + +public: + StringName notification; + StringName _get_output_port_unsequenced; + StringName _step; + StringName _subcall; + + static VisualScriptLanguage* singleton; + + Mutex *lock; + + bool debug_break(const String& p_error,bool p_allow_continue=true); + bool debug_break_parse(const String& p_file, int p_node,const String& p_error); + + _FORCE_INLINE_ void enter_function(VisualScriptInstance *p_instance,const StringName* p_function, Variant *p_stack, Variant **p_work_mem,int *current_id) { + + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now + + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() +1 ); + + if (_debug_call_stack_pos >= _debug_max_call_stack) { + //stack overflow + _debug_error="Stack Overflow (Stack Size: "+itos(_debug_max_call_stack)+")"; + ScriptDebugger::get_singleton()->debug(this); + return; + } + + _call_stack[_debug_call_stack_pos].stack=p_stack; + _call_stack[_debug_call_stack_pos].instance=p_instance; + _call_stack[_debug_call_stack_pos].function=p_function; + _call_stack[_debug_call_stack_pos].work_mem=p_work_mem; + _call_stack[_debug_call_stack_pos].current_id=current_id; + _debug_call_stack_pos++; + } + + _FORCE_INLINE_ void exit_function() { + + if (Thread::get_main_ID()!=Thread::get_caller_ID()) + return; //no support for other threads than main for now + + if (ScriptDebugger::get_singleton()->get_lines_left()>0 && ScriptDebugger::get_singleton()->get_depth()>=0) + ScriptDebugger::get_singleton()->set_depth( ScriptDebugger::get_singleton()->get_depth() -1 ); + + if (_debug_call_stack_pos==0) { + + _debug_error="Stack Underflow (Engine Bug)"; + ScriptDebugger::get_singleton()->debug(this); + return; + } + + _debug_call_stack_pos--; + } + + ////////////////////////////////////// + + virtual String get_name() const; + + /* LANGUAGE FUNCTIONS */ + virtual void init(); + virtual String get_type() const; + virtual String get_extension() const; + virtual Error execute_file(const String& p_path) ; + virtual void finish(); + + /* EDITOR FUNCTIONS */ + virtual void get_reserved_words(List<String> *p_words) const; + virtual void get_comment_delimiters(List<String> *p_delimiters) const; + virtual void get_string_delimiters(List<String> *p_delimiters) const; + virtual Ref<Script> get_template(const String& p_class_name, const String& p_base_class_name) const; + virtual bool validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path="",List<String> *r_functions=NULL) const; + virtual Script *create_script() const; + virtual bool has_named_classes() const; + virtual int find_function(const String& p_function,const String& p_code) const; + virtual String make_function(const String& p_class,const String& p_name,const PoolStringArray& p_args) const; + virtual void auto_indent_code(String& p_code,int p_from_line,int p_to_line) const; + virtual void add_global_constant(const StringName& p_variable,const Variant& p_value); + + + /* DEBUGGER FUNCTIONS */ + + virtual String debug_get_error() const; + virtual int debug_get_stack_level_count() const; + virtual int debug_get_stack_level_line(int p_level) const; + virtual String debug_get_stack_level_function(int p_level) const; + virtual String debug_get_stack_level_source(int p_level) const; + virtual void debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual void debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1); + virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1); + + + virtual void reload_all_scripts(); + virtual void reload_tool_script(const Ref<Script>& p_script,bool p_soft_reload); + /* LOADER FUNCTIONS */ + + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual void get_public_functions(List<MethodInfo> *p_functions) const; + virtual void get_public_constants(List<Pair<String,Variant> > *p_constants) const; + + virtual void profiling_start(); + virtual void profiling_stop(); + + virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr,int p_info_max); + virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr,int p_info_max); + + void add_register_func(const String& p_name,VisualScriptNodeRegisterFunc p_func); + Ref<VisualScriptNode> create_node_from_name(const String& p_name); + void get_registered_node_names(List<String> *r_names); + + + VisualScriptLanguage(); + ~VisualScriptLanguage(); + +}; + +//aid for registering +template<class T> +static Ref<VisualScriptNode> create_node_generic(const String& p_name) { + + Ref<T> node; + node.instance(); + return node; +} + + + +#endif // VSCRIPT_H diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp new file mode 100644 index 000000000..27242384a --- /dev/null +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -0,0 +1,1264 @@ +#include "visual_script_builtin_funcs.h" +#include "math_funcs.h" +#include "object_type_db.h" +#include "reference.h" +#include "func_ref.h" +#include "os/os.h" +#include "variant_parser.h" +#include "io/marshalls.h" + +const char* VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX]={ + "sin", + "cos", + "tan", + "sinh", + "cosh", + "tanh", + "asin", + "acos", + "atan", + "atan2", + "sqrt", + "fmod", + "fposmod", + "floor", + "ceil", + "round", + "abs", + "sign", + "pow", + "log", + "exp", + "is_nan", + "is_inf", + "ease", + "decimals", + "stepify", + "lerp", + "dectime", + "randomize", + "randi", + "randf", + "rand_range", + "seed", + "rand_seed", + "deg2rad", + "rad2deg", + "linear2db", + "db2linear", + "max", + "min", + "clamp", + "nearest_po2", + "weakref", + "funcref", + "convert", + "typeof", + "type_exists", + "char", + "str", + "print", + "printerr", + "printraw", + "var2str", + "str2var", + "var2bytes", + "bytes2var", + "color_named", +}; + +VisualScriptBuiltinFunc::BuiltinFunc VisualScriptBuiltinFunc::find_function(const String& p_string) { + + for(int i=0;i<FUNC_MAX;i++) { + if (p_string==func_name[i]) + return BuiltinFunc(i); + } + + return FUNC_MAX; +} + +String VisualScriptBuiltinFunc::get_func_name(BuiltinFunc p_func) { + + ERR_FAIL_INDEX_V(p_func,FUNC_MAX,String()); + return func_name[p_func]; +} + + +int VisualScriptBuiltinFunc::get_output_sequence_port_count() const { + + return has_input_sequence_port() ? 1 : 0; +} + +bool VisualScriptBuiltinFunc::has_input_sequence_port() const{ + + switch(func) { + + case MATH_RANDOMIZE: + case TEXT_PRINT: + case TEXT_PRINTERR: + case TEXT_PRINTRAW: + return true; + default: + return false; + + } + +} + +int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) { + + + switch(p_func) { + + case MATH_RANDOMIZE: + case MATH_RAND: + case MATH_RANDF: + return 0; + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_SQRT: + case MATH_FLOOR: + case MATH_CEIL: + case MATH_ROUND: + case MATH_ABS: + case MATH_SIGN: + case MATH_LOG: + case MATH_EXP: + case MATH_ISNAN: + case MATH_ISINF: + case MATH_DECIMALS: + case MATH_SEED: + case MATH_RANDSEED: + case MATH_DEG2RAD: + case MATH_RAD2DEG: + case MATH_LINEAR2DB: + case MATH_DB2LINEAR: + case LOGIC_NEAREST_PO2: + case OBJ_WEAKREF: + case TYPE_OF: + case TEXT_CHAR: + case TEXT_STR: + case TEXT_PRINT: + case TEXT_PRINTERR: + case TEXT_PRINTRAW: + case VAR_TO_STR: + case STR_TO_VAR: + case VAR_TO_BYTES: + case BYTES_TO_VAR: + case TYPE_EXISTS: + return 1; + case MATH_ATAN2: + case MATH_FMOD: + case MATH_FPOSMOD: + case MATH_POW: + case MATH_EASE: + case MATH_STEPIFY: + case MATH_RANDOM: + case LOGIC_MAX: + case LOGIC_MIN: + case FUNC_FUNCREF: + case TYPE_CONVERT: + case COLORN: + return 2; + case MATH_LERP: + case MATH_DECTIME: + case LOGIC_CLAMP: + return 3; + case FUNC_MAX:{} + + } + return 0; +} + +int VisualScriptBuiltinFunc::get_input_value_port_count() const{ + + return get_func_argument_count(func); +} +int VisualScriptBuiltinFunc::get_output_value_port_count() const{ + + switch(func) { + case MATH_RANDOMIZE: + case TEXT_PRINT: + case TEXT_PRINTERR: + case TEXT_PRINTRAW: + case MATH_SEED: + return 0; + case MATH_RANDSEED: + return 2; + default: + return 1; + } + + return 1; +} + +String VisualScriptBuiltinFunc::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const{ + + switch(func) { + + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_ATAN2: + case MATH_SQRT: { + return PropertyInfo(Variant::REAL,"num"); + } break; + case MATH_FMOD: + case MATH_FPOSMOD: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"x"); + else + return PropertyInfo(Variant::REAL,"y"); + } break; + case MATH_FLOOR: + case MATH_CEIL: + case MATH_ROUND: + case MATH_ABS: + case MATH_SIGN: { + return PropertyInfo(Variant::REAL,"num"); + + } break; + + case MATH_POW: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"x"); + else + return PropertyInfo(Variant::REAL,"y"); + } break; + case MATH_LOG: + case MATH_EXP: + case MATH_ISNAN: + case MATH_ISINF: { + return PropertyInfo(Variant::REAL,"num"); + } break; + case MATH_EASE: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"s"); + else + return PropertyInfo(Variant::REAL,"curve"); + } break; + case MATH_DECIMALS: { + return PropertyInfo(Variant::REAL,"step"); + } break; + case MATH_STEPIFY: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"s"); + else + return PropertyInfo(Variant::REAL,"steps"); + } break; + case MATH_LERP: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"from"); + else if (p_idx==1) + return PropertyInfo(Variant::REAL,"to"); + else + return PropertyInfo(Variant::REAL,"weight"); + + } break; + case MATH_DECTIME: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"value"); + else if (p_idx==1) + return PropertyInfo(Variant::REAL,"amount"); + else + return PropertyInfo(Variant::REAL,"step"); + } break; + case MATH_RANDOMIZE: { + + } break; + case MATH_RAND: { + + } break; + case MATH_RANDF: { + + } break; + case MATH_RANDOM: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"from"); + else + return PropertyInfo(Variant::REAL,"to"); + } break; + case MATH_SEED: { + return PropertyInfo(Variant::INT,"seed"); + } break; + case MATH_RANDSEED: { + return PropertyInfo(Variant::INT,"seed"); + } break; + case MATH_DEG2RAD: { + return PropertyInfo(Variant::REAL,"deg"); + } break; + case MATH_RAD2DEG: { + return PropertyInfo(Variant::REAL,"rad"); + } break; + case MATH_LINEAR2DB: { + return PropertyInfo(Variant::REAL,"nrg"); + } break; + case MATH_DB2LINEAR: { + return PropertyInfo(Variant::REAL,"db"); + } break; + case LOGIC_MAX: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"a"); + else + return PropertyInfo(Variant::REAL,"b"); + } break; + case LOGIC_MIN: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"a"); + else + return PropertyInfo(Variant::REAL,"b"); + } break; + case LOGIC_CLAMP: { + if (p_idx==0) + return PropertyInfo(Variant::REAL,"a"); + else if (p_idx==0) + return PropertyInfo(Variant::REAL,"min"); + else + return PropertyInfo(Variant::REAL,"max"); + } break; + case LOGIC_NEAREST_PO2: { + return PropertyInfo(Variant::INT,"num"); + } break; + case OBJ_WEAKREF: { + + return PropertyInfo(Variant::OBJECT,"source"); + + } break; + case FUNC_FUNCREF: { + + if (p_idx==0) + return PropertyInfo(Variant::OBJECT,"instance"); + else + return PropertyInfo(Variant::STRING,"funcname"); + + } break; + case TYPE_CONVERT: { + + if (p_idx==0) + return PropertyInfo(Variant::NIL,"what"); + else + return PropertyInfo(Variant::STRING,"type"); + } break; + case TYPE_OF: { + return PropertyInfo(Variant::NIL,"what"); + + } break; + case TYPE_EXISTS: { + + return PropertyInfo(Variant::STRING,"type"); + + } break; + case TEXT_CHAR: { + + return PropertyInfo(Variant::INT,"ascii"); + + } break; + case TEXT_STR: { + + return PropertyInfo(Variant::NIL,"value"); + + } break; + case TEXT_PRINT: { + + return PropertyInfo(Variant::NIL,"value"); + + } break; + case TEXT_PRINTERR: { + return PropertyInfo(Variant::NIL,"value"); + + } break; + case TEXT_PRINTRAW: { + + return PropertyInfo(Variant::NIL,"value"); + + } break; + case VAR_TO_STR: { + return PropertyInfo(Variant::NIL,"var"); + + } break; + case STR_TO_VAR: { + + return PropertyInfo(Variant::STRING,"string"); + } break; + case VAR_TO_BYTES: { + return PropertyInfo(Variant::NIL,"var"); + + } break; + case BYTES_TO_VAR: { + + return PropertyInfo(Variant::POOL_BYTE_ARRAY,"bytes"); + } break; + case COLORN: { + + if (p_idx==0) + return PropertyInfo(Variant::STRING,"name"); + else + return PropertyInfo(Variant::REAL,"alpha"); + + } break; + case FUNC_MAX:{} + } + + return PropertyInfo(); +} + +PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) const{ + + Variant::Type t=Variant::NIL; + switch(func) { + + case MATH_SIN: + case MATH_COS: + case MATH_TAN: + case MATH_SINH: + case MATH_COSH: + case MATH_TANH: + case MATH_ASIN: + case MATH_ACOS: + case MATH_ATAN: + case MATH_ATAN2: + case MATH_SQRT: + case MATH_FMOD: + case MATH_FPOSMOD: + case MATH_FLOOR: + case MATH_CEIL: { + t=Variant::REAL; + } break; + case MATH_ROUND: { + t=Variant::INT; + } break; + case MATH_ABS: { + t=Variant::NIL; + } break; + case MATH_SIGN: { + t=Variant::NIL; + } break; + case MATH_POW: + case MATH_LOG: + case MATH_EXP: { + t=Variant::REAL; + } break; + case MATH_ISNAN: + case MATH_ISINF: { + t=Variant::BOOL; + } break; + case MATH_EASE: { + t=Variant::REAL; + } break; + case MATH_DECIMALS: { + t=Variant::INT; + } break; + case MATH_STEPIFY: + case MATH_LERP: + case MATH_DECTIME: { + t=Variant::REAL; + + } break; + case MATH_RANDOMIZE: { + + } break; + case MATH_RAND: { + + t=Variant::INT; + } break; + case MATH_RANDF: + case MATH_RANDOM: { + t=Variant::REAL; + } break; + case MATH_SEED: { + + } break; + case MATH_RANDSEED: { + + if (p_idx==0) + return PropertyInfo(Variant::INT,"rnd"); + else + return PropertyInfo(Variant::INT,"seed"); + } break; + case MATH_DEG2RAD: + case MATH_RAD2DEG: + case MATH_LINEAR2DB: + case MATH_DB2LINEAR: { + t=Variant::REAL; + } break; + case LOGIC_MAX: + case LOGIC_MIN: + case LOGIC_CLAMP: { + + + } break; + + case LOGIC_NEAREST_PO2: { + t=Variant::NIL; + } break; + case OBJ_WEAKREF: { + + t=Variant::OBJECT; + + } break; + case FUNC_FUNCREF: { + + t=Variant::OBJECT; + + } break; + case TYPE_CONVERT: { + + + + } break; + case TYPE_OF: { + t=Variant::INT; + + } break; + case TYPE_EXISTS: { + + t=Variant::BOOL; + + } break; + case TEXT_CHAR: + case TEXT_STR: { + + t=Variant::STRING; + + } break; + case TEXT_PRINT: { + + + } break; + case TEXT_PRINTERR: { + + } break; + case TEXT_PRINTRAW: { + + } break; + case VAR_TO_STR: { + t=Variant::STRING; + } break; + case STR_TO_VAR: { + + } break; + case VAR_TO_BYTES: { + t=Variant::POOL_BYTE_ARRAY; + + } break; + case BYTES_TO_VAR: { + + + } break; + case COLORN: { + t=Variant::COLOR; + } break; + case FUNC_MAX:{} + } + + return PropertyInfo(t,""); +} + +String VisualScriptBuiltinFunc::get_caption() const { + + return "BuiltinFunc"; +} + +String VisualScriptBuiltinFunc::get_text() const { + + return func_name[func]; +} + +void VisualScriptBuiltinFunc::set_func(BuiltinFunc p_which) { + + ERR_FAIL_INDEX(p_which,FUNC_MAX); + func=p_which; + _change_notify(); + ports_changed_notify(); +} + +VisualScriptBuiltinFunc::BuiltinFunc VisualScriptBuiltinFunc::get_func() { + return func; +} + + +#define VALIDATE_ARG_NUM(m_arg) \ + if (!p_inputs[m_arg]->is_num()) {\ + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;\ + r_error.argument=m_arg;\ + r_error.expected=Variant::REAL;\ + return;\ + } + + +void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func,const Variant** p_inputs,Variant* r_return,Variant::CallError& r_error,String& r_error_str) { + + switch(p_func) { + case VisualScriptBuiltinFunc::MATH_SIN: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::sin(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_COS: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::cos(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_TAN: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::tan(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_SINH: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::sinh(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_COSH: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::cosh(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_TANH: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::tanh(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ASIN: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::asin(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ACOS: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::acos(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ATAN: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::atan(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ATAN2: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::atan2(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_SQRT: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::sqrt(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_FMOD: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::fmod(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_FPOSMOD: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::fposmod(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_FLOOR: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::floor(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_CEIL: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::ceil(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ROUND: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::round(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ABS: { + + if (p_inputs[0]->get_type()==Variant::INT) { + + int64_t i = *p_inputs[0]; + *r_return=ABS(i); + } else if (p_inputs[0]->get_type()==Variant::REAL) { + + real_t r = *p_inputs[0]; + *r_return=Math::abs(r); + } else { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::REAL; + + } + } break; + case VisualScriptBuiltinFunc::MATH_SIGN: { + + if (p_inputs[0]->get_type()==Variant::INT) { + + int64_t i = *p_inputs[0]; + *r_return= i < 0 ? -1 : ( i > 0 ? +1 : 0); + } else if (p_inputs[0]->get_type()==Variant::REAL) { + + real_t r = *p_inputs[0]; + *r_return= r < 0.0 ? -1.0 : ( r > 0.0 ? +1.0 : 0.0); + } else { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::REAL; + + } + } break; + case VisualScriptBuiltinFunc::MATH_POW: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::pow(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_LOG: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::log(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_EXP: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::exp(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ISNAN: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::is_nan(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_ISINF: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::is_inf(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_EASE: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::ease(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_DECIMALS: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::step_decimals(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_STEPIFY: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::stepify(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_LERP: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + *r_return=Math::lerp(*p_inputs[0],*p_inputs[1],*p_inputs[2]); + } break; + case VisualScriptBuiltinFunc::MATH_DECTIME: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + *r_return=Math::dectime(*p_inputs[0],*p_inputs[1],*p_inputs[2]); + } break; + case VisualScriptBuiltinFunc::MATH_RANDOMIZE: { + Math::randomize(); + + } break; + case VisualScriptBuiltinFunc::MATH_RAND: { + *r_return=Math::rand(); + } break; + case VisualScriptBuiltinFunc::MATH_RANDF: { + *r_return=Math::randf(); + } break; + case VisualScriptBuiltinFunc::MATH_RANDOM: { + + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + *r_return=Math::random(*p_inputs[0],*p_inputs[1]); + } break; + case VisualScriptBuiltinFunc::MATH_SEED: { + + VALIDATE_ARG_NUM(0); + uint32_t seed=*p_inputs[0]; + Math::seed(seed); + + } break; + case VisualScriptBuiltinFunc::MATH_RANDSEED: { + + VALIDATE_ARG_NUM(0); + uint32_t seed=*p_inputs[0]; + int ret = Math::rand_from_seed(&seed); + Array reta; + reta.push_back(ret); + reta.push_back(seed); + *r_return=reta; + + } break; + case VisualScriptBuiltinFunc::MATH_DEG2RAD: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::deg2rad(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_RAD2DEG: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::rad2deg(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_LINEAR2DB: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::linear2db(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::MATH_DB2LINEAR: { + + VALIDATE_ARG_NUM(0); + *r_return=Math::db2linear(*p_inputs[0]); + } break; + case VisualScriptBuiltinFunc::LOGIC_MAX: { + + if (p_inputs[0]->get_type()==Variant::INT && p_inputs[1]->get_type()==Variant::INT) { + + int64_t a = *p_inputs[0]; + int64_t b = *p_inputs[1]; + *r_return=MAX(a,b); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + real_t a = *p_inputs[0]; + real_t b = *p_inputs[1]; + + *r_return=MAX(a,b); + } + + } break; + case VisualScriptBuiltinFunc::LOGIC_MIN: { + + if (p_inputs[0]->get_type()==Variant::INT && p_inputs[1]->get_type()==Variant::INT) { + + int64_t a = *p_inputs[0]; + int64_t b = *p_inputs[1]; + *r_return=MIN(a,b); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + real_t a = *p_inputs[0]; + real_t b = *p_inputs[1]; + + *r_return=MIN(a,b); + } + } break; + case VisualScriptBuiltinFunc::LOGIC_CLAMP: { + + if (p_inputs[0]->get_type()==Variant::INT && p_inputs[1]->get_type()==Variant::INT && p_inputs[2]->get_type()==Variant::INT) { + + int64_t a = *p_inputs[0]; + int64_t b = *p_inputs[1]; + int64_t c = *p_inputs[2]; + *r_return=CLAMP(a,b,c); + } else { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + VALIDATE_ARG_NUM(2); + + real_t a = *p_inputs[0]; + real_t b = *p_inputs[1]; + real_t c = *p_inputs[2]; + + *r_return=CLAMP(a,b,c); + } + } break; + case VisualScriptBuiltinFunc::LOGIC_NEAREST_PO2: { + + VALIDATE_ARG_NUM(0); + int64_t num = *p_inputs[0]; + *r_return = nearest_power_of_2(num); + } break; + case VisualScriptBuiltinFunc::OBJ_WEAKREF: { + + if (p_inputs[0]->get_type()!=Variant::OBJECT) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + + return; + + } + + if (p_inputs[0]->is_ref()) { + + REF r = *p_inputs[0]; + if (!r.is_valid()) { + + return; + } + + Ref<WeakRef> wref = memnew( WeakRef ); + wref->set_ref(r); + *r_return=wref; + } else { + Object *obj = *p_inputs[0]; + if (!obj) { + + return; + } + Ref<WeakRef> wref = memnew( WeakRef ); + wref->set_obj(obj); + *r_return=wref; + } + + + + + } break; + case VisualScriptBuiltinFunc::FUNC_FUNCREF: { + + if (p_inputs[0]->get_type()!=Variant::OBJECT) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::OBJECT; + + return; + + } + if (p_inputs[1]->get_type()!=Variant::STRING && p_inputs[1]->get_type()!=Variant::NODE_PATH) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=1; + r_error.expected=Variant::STRING; + + return; + + } + + Ref<FuncRef> fr = memnew( FuncRef); + + fr->set_instance(*p_inputs[0]); + fr->set_function(*p_inputs[1]); + + *r_return=fr; + + } break; + case VisualScriptBuiltinFunc::TYPE_CONVERT: { + + VALIDATE_ARG_NUM(1); + int type=*p_inputs[1]; + if (type<0 || type>=Variant::VARIANT_MAX) { + + r_error_str=RTR("Invalid type argument to convert(), use TYPE_* constants."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::INT; + return; + + } else { + + + *r_return=Variant::construct(Variant::Type(type),p_inputs,1,r_error); + } + } break; + case VisualScriptBuiltinFunc::TYPE_OF: { + + + *r_return = p_inputs[0]->get_type(); + + } break; + case VisualScriptBuiltinFunc::TYPE_EXISTS: { + + + *r_return = ClassDB::class_exists(*p_inputs[0]); + + } break; + case VisualScriptBuiltinFunc::TEXT_CHAR: { + + CharType result[2] = {*p_inputs[0], 0}; + + *r_return=String(result); + + } break; + case VisualScriptBuiltinFunc::TEXT_STR: { + + String str = *p_inputs[0]; + + *r_return=str; + + } break; + case VisualScriptBuiltinFunc::TEXT_PRINT: { + + String str = *p_inputs[0]; + print_line(str); + + + } break; + + case VisualScriptBuiltinFunc::TEXT_PRINTERR: { + + String str = *p_inputs[0]; + + //str+="\n"; + OS::get_singleton()->printerr("%s\n",str.utf8().get_data()); + + + } break; + case VisualScriptBuiltinFunc::TEXT_PRINTRAW: { + String str = *p_inputs[0]; + + //str+="\n"; + OS::get_singleton()->print("%s",str.utf8().get_data()); + + + } break; + case VisualScriptBuiltinFunc::VAR_TO_STR: { + + String vars; + VariantWriter::write_to_string(*p_inputs[0],vars); + *r_return=vars; + } break; + case VisualScriptBuiltinFunc::STR_TO_VAR: { + + if (p_inputs[0]->get_type()!=Variant::STRING) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::STRING; + + return; + } + + VariantParser::StreamString ss; + ss.s=*p_inputs[0]; + + String errs; + int line; + Error err = VariantParser::parse(&ss,*r_return,errs,line); + + if (err!=OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::STRING; + *r_return="Parse error at line "+itos(line)+": "+errs; + return; + } + + } break; + case VisualScriptBuiltinFunc::VAR_TO_BYTES: { + + + PoolByteArray barr; + int len; + Error err = encode_variant(*p_inputs[0],NULL,len); + if (err) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::NIL; + r_error_str="Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."; + return; + } + + barr.resize(len); + { + PoolByteArray::Write w = barr.write(); + encode_variant(*p_inputs[0],w.ptr(),len); + + } + *r_return=barr; + } break; + case VisualScriptBuiltinFunc::BYTES_TO_VAR: { + + if (p_inputs[0]->get_type()!=Variant::POOL_BYTE_ARRAY) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::POOL_BYTE_ARRAY; + + return; + } + + PoolByteArray varr=*p_inputs[0]; + Variant ret; + { + PoolByteArray::Read r=varr.read(); + Error err = decode_variant(ret,r.ptr(),varr.size(),NULL); + if (err!=OK) { + r_error_str=RTR("Not enough bytes for decoding bytes, or invalid format."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument=0; + r_error.expected=Variant::POOL_BYTE_ARRAY; + return; + } + + } + + *r_return=ret; + + } break; + case VisualScriptBuiltinFunc::COLORN: { + + VALIDATE_ARG_NUM(1); + + Color color = Color::named(*p_inputs[0]); + color.a=*p_inputs[1]; + + *r_return=String(color); + + } break; + default: {} + } + +} + + +class VisualScriptNodeInstanceBuiltinFunc : public VisualScriptNodeInstance { +public: + + VisualScriptBuiltinFunc *node; + VisualScriptInstance *instance; + + VisualScriptBuiltinFunc::BuiltinFunc func; + + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + VisualScriptBuiltinFunc::exec_func(func,p_inputs,p_outputs[0],r_error,r_error_str); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptBuiltinFunc::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceBuiltinFunc * instance = memnew(VisualScriptNodeInstanceBuiltinFunc ); + instance->node=this; + instance->instance=p_instance; + instance->func=func; + return instance; +} + + +void VisualScriptBuiltinFunc::_bind_methods() { + + ClassDB::bind_method(_MD("set_func","which"),&VisualScriptBuiltinFunc::set_func); + ClassDB::bind_method(_MD("get_func"),&VisualScriptBuiltinFunc::get_func); + + String cc; + + for(int i=0;i<FUNC_MAX;i++) { + + if (i>0) + cc+=","; + cc+=func_name[i]; + } + ADD_PROPERTY(PropertyInfo(Variant::INT,"function",PROPERTY_HINT_ENUM,cc),_SCS("set_func"),_SCS("get_func")); +} + +VisualScriptBuiltinFunc::VisualScriptBuiltinFunc() { + + func=MATH_SIN; +} + +template<VisualScriptBuiltinFunc::BuiltinFunc func> +static Ref<VisualScriptNode> create_builtin_func_node(const String& p_name) { + + Ref<VisualScriptBuiltinFunc> node; + node.instance(); + node->set_func(func); + return node; +} + +void register_visual_script_builtin_func_node() { + + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sin",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SIN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/cos",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_COS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/tan",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_TAN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sinh",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SINH>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/cosh",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_COSH>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/tanh",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_TANH>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/asin",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ASIN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/acos",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ACOS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/atan",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ATAN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/atan2",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ATAN2>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sqrt",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SQRT>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/fmod",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FMOD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/fposmod",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FPOSMOD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/floor",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_FLOOR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/ceil",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_CEIL>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/round",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ROUND>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/abs",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ABS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/sign",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SIGN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/pow",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_POW>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/log",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LOG>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/exp",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_EXP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/isnan",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ISNAN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/isinf",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_ISINF>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/ease",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_EASE>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/decimals",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DECIMALS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/stepify",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_STEPIFY>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/lerp",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LERP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/dectime",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DECTIME>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/randomize",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDOMIZE>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/rand",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RAND>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/randf",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/random",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDOM>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/seed",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_SEED>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/randseed",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RANDSEED>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/deg2rad",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DEG2RAD>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/rad2deg",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RAD2DEG>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/linear2db",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LINEAR2DB>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/db2linear",create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DB2LINEAR>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/max",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_MAX>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/min",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_MIN>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/clamp",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_CLAMP>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/nearest_po2",create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_NEAREST_PO2>); + + VisualScriptLanguage::singleton->add_register_func("functions/built_in/weakref",create_builtin_func_node<VisualScriptBuiltinFunc::OBJ_WEAKREF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/funcref",create_builtin_func_node<VisualScriptBuiltinFunc::FUNC_FUNCREF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/convert",create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_CONVERT>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/typeof",create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_OF>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/type_exists",create_builtin_func_node<VisualScriptBuiltinFunc::TYPE_EXISTS>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/char",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_CHAR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/str",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_STR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/print",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINT>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/printerr",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINTERR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/printraw",create_builtin_func_node<VisualScriptBuiltinFunc::TEXT_PRINTRAW>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/var2str",create_builtin_func_node<VisualScriptBuiltinFunc::VAR_TO_STR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/str2var",create_builtin_func_node<VisualScriptBuiltinFunc::STR_TO_VAR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/var2bytes",create_builtin_func_node<VisualScriptBuiltinFunc::VAR_TO_BYTES>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/bytes2var",create_builtin_func_node<VisualScriptBuiltinFunc::BYTES_TO_VAR>); + VisualScriptLanguage::singleton->add_register_func("functions/built_in/color_named",create_builtin_func_node<VisualScriptBuiltinFunc::COLORN>); + +} diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h new file mode 100644 index 000000000..a285517c7 --- /dev/null +++ b/modules/visual_script/visual_script_builtin_funcs.h @@ -0,0 +1,117 @@ +#ifndef VISUAL_SCRIPT_BUILTIN_FUNCS_H +#define VISUAL_SCRIPT_BUILTIN_FUNCS_H + +#include "visual_script.h" + + +class VisualScriptBuiltinFunc : public VisualScriptNode { + + GDCLASS(VisualScriptBuiltinFunc,VisualScriptNode) +public: + + enum BuiltinFunc { + MATH_SIN, + MATH_COS, + MATH_TAN, + MATH_SINH, + MATH_COSH, + MATH_TANH, + MATH_ASIN, + MATH_ACOS, + MATH_ATAN, + MATH_ATAN2, + MATH_SQRT, + MATH_FMOD, + MATH_FPOSMOD, + MATH_FLOOR, + MATH_CEIL, + MATH_ROUND, + MATH_ABS, + MATH_SIGN, + MATH_POW, + MATH_LOG, + MATH_EXP, + MATH_ISNAN, + MATH_ISINF, + MATH_EASE, + MATH_DECIMALS, + MATH_STEPIFY, + MATH_LERP, + MATH_DECTIME, + MATH_RANDOMIZE, + MATH_RAND, + MATH_RANDF, + MATH_RANDOM, + MATH_SEED, + MATH_RANDSEED, + MATH_DEG2RAD, + MATH_RAD2DEG, + MATH_LINEAR2DB, + MATH_DB2LINEAR, + LOGIC_MAX, + LOGIC_MIN, + LOGIC_CLAMP, + LOGIC_NEAREST_PO2, + OBJ_WEAKREF, + FUNC_FUNCREF, + TYPE_CONVERT, + TYPE_OF, + TYPE_EXISTS, + TEXT_CHAR, + TEXT_STR, + TEXT_PRINT, + TEXT_PRINTERR, + TEXT_PRINTRAW, + VAR_TO_STR, + STR_TO_VAR, + VAR_TO_BYTES, + BYTES_TO_VAR, + COLORN, + FUNC_MAX + }; + + static int get_func_argument_count(BuiltinFunc p_func); + static String get_func_name(BuiltinFunc p_func); + static void exec_func(BuiltinFunc p_func, const Variant** p_inputs, Variant* r_return, Variant::CallError& r_error, String& r_error_str); + static BuiltinFunc find_function(const String& p_string); + +private: + static const char* func_name[FUNC_MAX]; + BuiltinFunc func; +protected: + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_func(BuiltinFunc p_which); + BuiltinFunc get_func(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptBuiltinFunc(); +}; + +VARIANT_ENUM_CAST(VisualScriptBuiltinFunc::BuiltinFunc) + + +void register_visual_script_builtin_func_node(); + + +#endif // VISUAL_SCRIPT_BUILTIN_FUNCS_H diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp new file mode 100644 index 000000000..37ee225ae --- /dev/null +++ b/modules/visual_script/visual_script_editor.cpp @@ -0,0 +1,3531 @@ +#include "visual_script_editor.h" +#include "tools/editor/editor_node.h" +#include "visual_script_nodes.h" +#include "visual_script_flow_control.h" +#include "visual_script_func_nodes.h" +#include "visual_script_expression.h" +#include "os/input.h" +#include "tools/editor/editor_resource_preview.h" +#include "os/keyboard.h" + +#ifdef TOOLS_ENABLED +class VisualScriptEditorSignalEdit : public Object { + + GDCLASS(VisualScriptEditorSignalEdit,Object) + + StringName sig; +public: + UndoRedo *undo_redo; + Ref<VisualScript> script; + + +protected: + + static void _bind_methods() { + ClassDB::bind_method("_sig_changed",&VisualScriptEditorSignalEdit::_sig_changed); + } + + void _sig_changed() { + + _change_notify(); + } + + bool _set(const StringName& p_name, const Variant& p_value) { + + if (sig==StringName()) + return false; + + if (p_name=="argument_count") { + + int new_argc=p_value; + int argc = script->custom_signal_get_argument_count(sig); + if (argc==new_argc) + return true; + + undo_redo->create_action("Change Signal Arguments"); + + + + if (new_argc < argc) { + for(int i=new_argc;i<argc;i++) { + undo_redo->add_do_method(script.ptr(),"custom_signal_remove_argument",sig,new_argc); + undo_redo->add_undo_method(script.ptr(),"custom_signal_add_argument",sig,script->custom_signal_get_argument_name(sig,i),script->custom_signal_get_argument_type(sig,i),-1); + } + } else if (new_argc>argc) { + + for(int i=argc;i<new_argc;i++) { + + undo_redo->add_do_method(script.ptr(),"custom_signal_add_argument",sig,Variant::NIL,"arg"+itos(i+1),-1); + undo_redo->add_undo_method(script.ptr(),"custom_signal_remove_argument",sig,argc); + } + } + + undo_redo->add_do_method(this,"_sig_changed"); + undo_redo->add_undo_method(this,"_sig_changed"); + + undo_redo->commit_action(); + + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,script->custom_signal_get_argument_count(sig),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + + int old_type = script->custom_signal_get_argument_type(sig,idx); + int new_type=p_value; + undo_redo->create_action("Change Argument Type"); + undo_redo->add_do_method(script.ptr(),"custom_signal_set_argument_type",sig,idx,new_type); + undo_redo->add_undo_method(script.ptr(),"custom_signal_set_argument_type",sig,idx,old_type); + undo_redo->commit_action(); + + return true; + } + + if (what=="name") { + + String old_name = script->custom_signal_get_argument_name(sig,idx); + String new_name=p_value; + undo_redo->create_action("Change Argument name"); + undo_redo->add_do_method(script.ptr(),"custom_signal_set_argument_name",sig,idx,new_name); + undo_redo->add_undo_method(script.ptr(),"custom_signal_set_argument_name",sig,idx,old_name); + undo_redo->commit_action(); + return true; + } + + + } + + + return false; + } + + bool _get(const StringName& p_name,Variant &r_ret) const { + + if (sig==StringName()) + return false; + + if (p_name=="argument_count") { + r_ret = script->custom_signal_get_argument_count(sig); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,script->custom_signal_get_argument_count(sig),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + r_ret = script->custom_signal_get_argument_type(sig,idx); + return true; + } + if (what=="name") { + r_ret = script->custom_signal_get_argument_name(sig,idx); + return true; + } + + + + } + + return false; + } + void _get_property_list( List<PropertyInfo> *p_list) const { + + if (sig==StringName()) + return; + + p_list->push_back(PropertyInfo(Variant::INT,"argument_count",PROPERTY_HINT_RANGE,"0,256")); + String argt="Variant"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + for(int i=0;i<script->custom_signal_get_argument_count(sig);i++) { + p_list->push_back(PropertyInfo(Variant::INT,"argument/"+itos(i+1)+"/type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::STRING,"argument/"+itos(i+1)+"/name")); + } + } + +public: + + + void edit(const StringName& p_sig) { + + sig=p_sig; + _change_notify(); + } + + VisualScriptEditorSignalEdit() { undo_redo=NULL; } +}; + +class VisualScriptEditorVariableEdit : public Object { + + GDCLASS(VisualScriptEditorVariableEdit,Object) + + StringName var; +public: + UndoRedo *undo_redo; + Ref<VisualScript> script; + + +protected: + + static void _bind_methods() { + ClassDB::bind_method("_var_changed",&VisualScriptEditorVariableEdit::_var_changed); + ClassDB::bind_method("_var_value_changed",&VisualScriptEditorVariableEdit::_var_value_changed); + } + + void _var_changed() { + + _change_notify(); + } + void _var_value_changed() { + + _change_notify("value"); //so the whole tree is not redrawn, makes editing smoother in general + } + + bool _set(const StringName& p_name, const Variant& p_value) { + + if (var==StringName()) + return false; + + + + if (String(p_name)=="value") { + undo_redo->create_action("Set Variable Default Value"); + Variant current=script->get_variable_default_value(var); + undo_redo->add_do_method(script.ptr(),"set_variable_default_value",var,p_value); + undo_redo->add_undo_method(script.ptr(),"set_variable_default_value",var,current); + undo_redo->add_do_method(this,"_var_value_changed"); + undo_redo->add_undo_method(this,"_var_value_changed"); + undo_redo->commit_action(); + return true; + } + + Dictionary d = script->call("get_variable_info",var); + + if (String(p_name)=="type") { + + Dictionary dc=d.copy(); + dc["type"]=p_value; + undo_redo->create_action("Set Variable Type"); + undo_redo->add_do_method(script.ptr(),"set_variable_info",var,dc); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",var,d); + undo_redo->add_do_method(this,"_var_changed"); + undo_redo->add_undo_method(this,"_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name)=="hint") { + + Dictionary dc=d.copy(); + dc["hint"]=p_value; + undo_redo->create_action("Set Variable Type"); + undo_redo->add_do_method(script.ptr(),"set_variable_info",var,dc); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",var,d); + undo_redo->add_do_method(this,"_var_changed"); + undo_redo->add_undo_method(this,"_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name)=="hint_string") { + + Dictionary dc=d.copy(); + dc["hint_string"]=p_value; + undo_redo->create_action("Set Variable Type"); + undo_redo->add_do_method(script.ptr(),"set_variable_info",var,dc); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",var,d); + undo_redo->add_do_method(this,"_var_changed"); + undo_redo->add_undo_method(this,"_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name)=="export") { + script->set_variable_export(var,p_value); + return true; + } + + + return false; + } + + bool _get(const StringName& p_name,Variant &r_ret) const { + + if (var==StringName()) + return false; + + if (String(p_name)=="value") { + r_ret=script->get_variable_default_value(var); + return true; + } + + PropertyInfo pinfo = script->get_variable_info(var); + + if (String(p_name)=="type") { + r_ret=pinfo.type; + return true; + } + if (String(p_name)=="hint") { + r_ret=pinfo.hint; + return true; + } + if (String(p_name)=="hint_string") { + r_ret=pinfo.hint_string; + return true; + } + + if (String(p_name)=="export") { + r_ret=script->get_variable_export(var); + return true; + } + + return false; + } + void _get_property_list( List<PropertyInfo> *p_list) const { + + if (var==StringName()) + return; + + String argt="Variant"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + p_list->push_back(PropertyInfo(Variant::INT,"type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(script->get_variable_info(var).type,"value",script->get_variable_info(var).hint,script->get_variable_info(var).hint_string,PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT,"hint",PROPERTY_HINT_ENUM,"None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,BitFlags,AllFlags,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText")); + p_list->push_back(PropertyInfo(Variant::STRING,"hint_string")); + p_list->push_back(PropertyInfo(Variant::BOOL,"export")); + + } + +public: + + + void edit(const StringName& p_var) { + + var=p_var; + _change_notify(); + } + + VisualScriptEditorVariableEdit() { undo_redo=NULL; } +}; + +static Color _color_from_type(Variant::Type p_type) { + Color color; + switch(p_type) { + case Variant::NIL: color = Color::html("69ecbd"); break; + + case Variant::BOOL: color = Color::html("8da6f0"); break; + case Variant::INT: color = Color::html("7dc6ef"); break; + case Variant::REAL: color = Color::html("61daf4"); break; + case Variant::STRING: color = Color::html("6ba7ec"); break; + + case Variant::VECTOR2: color = Color::html("bd91f1"); break; + case Variant::RECT2: color = Color::html("f191a5"); break; + case Variant::VECTOR3: color = Color::html("d67dee"); break; + case Variant::TRANSFORM2D: color = Color::html("c4ec69"); break; + case Variant::PLANE: color = Color::html("f77070"); break; + case Variant::QUAT: color = Color::html("ec69a3"); break; + case Variant::RECT3: color = Color::html("ee7991"); break; + case Variant::BASIS: color = Color::html("e3ec69"); break; + case Variant::TRANSFORM: color = Color::html("f6a86e"); break; + + case Variant::COLOR: color = Color::html("9dff70"); break; + case Variant::IMAGE: color = Color::html("93f1b9"); break; + case Variant::NODE_PATH: color = Color::html("6993ec"); break; + case Variant::_RID: color = Color::html("69ec9a"); break; + case Variant::OBJECT: color = Color::html("79f3e8"); break; + case Variant::INPUT_EVENT: color = Color::html("adf18f"); break; + case Variant::DICTIONARY: color = Color::html("77edb1"); break; + + case Variant::ARRAY: color = Color::html("e0e0e0"); break; + case Variant::POOL_BYTE_ARRAY: color = Color::html("aaf4c8"); break; + case Variant::POOL_INT_ARRAY: color = Color::html("afdcf5"); break; + case Variant::POOL_REAL_ARRAY: color = Color::html("97e7f8"); break; + case Variant::POOL_STRING_ARRAY: color = Color::html("9dc4f2"); break; + case Variant::POOL_VECTOR2_ARRAY: color = Color::html("d1b3f5"); break; + case Variant::POOL_VECTOR3_ARRAY: color = Color::html("df9bf2"); break; + case Variant::POOL_COLOR_ARRAY: color = Color::html("e9ff97"); break; + + default: + color.set_hsv(p_type/float(Variant::VARIANT_MAX),0.7,0.7); + } + return color; +} + + + +void VisualScriptEditor::_update_graph_connections() { + + graph->clear_connections(); + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(edited_func,&sequence_conns); + + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_conns.front();E;E=E->next()) { + + graph->connect_node(itos(E->get().from_node),E->get().from_output,itos(E->get().to_node),0); + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(edited_func,&data_conns); + + for (List<VisualScript::DataConnection>::Element *E=data_conns.front();E;E=E->next()) { + + VisualScript::DataConnection dc=E->get(); + + + Ref<VisualScriptNode> from_node = script->get_node(edited_func,E->get().from_node); + Ref<VisualScriptNode> to_node = script->get_node(edited_func,E->get().to_node); + + if (to_node->has_input_sequence_port()) { + dc.to_port++; + } + + dc.from_port+=from_node->get_output_sequence_port_count(); + + graph->connect_node(itos(E->get().from_node),dc.from_port,itos(E->get().to_node),dc.to_port); + } + +} + + +void VisualScriptEditor::_update_graph(int p_only_id) { + + if (updating_graph) + return; + + updating_graph=true; + + //byebye all nodes + if (p_only_id>=0) { + if (graph->has_node(itos(p_only_id))) { + Node* gid = graph->get_node(itos(p_only_id)); + if (gid) + memdelete(gid); + } + } else { + + for(int i=0;i<graph->get_child_count();i++) { + + if (graph->get_child(i)->cast_to<GraphNode>()) { + memdelete(graph->get_child(i)); + i--; + } + } + } + + if (!script->has_function(edited_func)) { + graph->hide(); + select_func_text->show(); + updating_graph=false; + return; + } + + graph->show(); + select_func_text->hide(); + + Ref<Texture> type_icons[Variant::VARIANT_MAX]={ + Control::get_icon("MiniVariant","EditorIcons"), + Control::get_icon("MiniBoolean","EditorIcons"), + Control::get_icon("MiniInteger","EditorIcons"), + Control::get_icon("MiniFloat","EditorIcons"), + Control::get_icon("MiniString","EditorIcons"), + Control::get_icon("MiniVector2","EditorIcons"), + Control::get_icon("MiniRect2","EditorIcons"), + Control::get_icon("MiniVector3","EditorIcons"), + Control::get_icon("MiniTransform2D","EditorIcons"), + Control::get_icon("MiniPlane","EditorIcons"), + Control::get_icon("MiniQuat","EditorIcons"), + Control::get_icon("MiniAabb","EditorIcons"), + Control::get_icon("MiniBasis","EditorIcons"), + Control::get_icon("MiniTransform","EditorIcons"), + Control::get_icon("MiniColor","EditorIcons"), + Control::get_icon("MiniImage","EditorIcons"), + Control::get_icon("MiniPath","EditorIcons"), + Control::get_icon("MiniRid","EditorIcons"), + Control::get_icon("MiniObject","EditorIcons"), + Control::get_icon("MiniInput","EditorIcons"), + Control::get_icon("MiniDictionary","EditorIcons"), + Control::get_icon("MiniArray","EditorIcons"), + Control::get_icon("MiniRawArray","EditorIcons"), + Control::get_icon("MiniIntArray","EditorIcons"), + Control::get_icon("MiniFloatArray","EditorIcons"), + Control::get_icon("MiniStringArray","EditorIcons"), + Control::get_icon("MiniVector2Array","EditorIcons"), + Control::get_icon("MiniVector3Array","EditorIcons"), + Control::get_icon("MiniColorArray","EditorIcons") + }; + + + + Ref<Texture> seq_port = Control::get_icon("VisualShaderPort","EditorIcons"); + + List<int> ids; + script->get_node_list(edited_func,&ids); + StringName editor_icons="EditorIcons"; + + for(List<int>::Element *E=ids.front();E;E=E->next()) { + + if (p_only_id>=0 && p_only_id!=E->get()) + continue; + + Ref<VisualScriptNode> node = script->get_node(edited_func,E->get()); + Vector2 pos = script->get_node_pos(edited_func,E->get()); + + GraphNode *gnode = memnew( GraphNode ); + gnode->set_title(node->get_caption()); + if (error_line==E->get()) { + gnode->set_overlay(GraphNode::OVERLAY_POSITION); + } else if (node->is_breakpoint()) { + gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT); + } + + if (EditorSettings::get_singleton()->has("editors/visual_script/color_"+node->get_category())) { + gnode->set_modulate(EditorSettings::get_singleton()->get("editors/visual_script/color_"+node->get_category())); + } + + + + gnode->set_meta("__vnode",node); + gnode->set_name(itos(E->get())); + gnode->connect("dragged",this,"_node_moved",varray(E->get())); + gnode->connect("close_request",this,"_remove_node",varray(E->get()),CONNECT_DEFERRED); + + + if (E->get()!=script->get_function_node_id(edited_func)) { + //function can't be erased + gnode->set_show_close_button(true); + } + + + if (node->cast_to<VisualScriptExpression>()) { + + LineEdit *line_edit = memnew( LineEdit ); + line_edit->set_text(node->get_text()); + line_edit->set_expand_to_text_length(true); + line_edit->add_font_override("font",get_font("source","EditorFonts")); + gnode->add_child(line_edit); + line_edit->connect("text_changed",this,"_expression_text_changed",varray(E->get())); + } else { + Label *text = memnew( Label ); + text->set_text(node->get_text()); + gnode->add_child(text); + } + + + if (node->cast_to<VisualScriptComment>()) { + Ref<VisualScriptComment> vsc=node; + gnode->set_comment(true); + gnode->set_resizeable(true); + gnode->set_custom_minimum_size(vsc->get_size()*EDSCALE); + gnode->connect("resize_request",this,"_comment_node_resized",varray(E->get())); + + } + + + int slot_idx=0; + + bool single_seq_output = node->get_output_sequence_port_count()==1 && node->get_output_sequence_port_text(0)==String(); + gnode->set_slot(0,node->has_input_sequence_port(),TYPE_SEQUENCE,Color(1,1,1,1),single_seq_output,TYPE_SEQUENCE,Color(1,1,1,1),seq_port,seq_port); + gnode->set_offset(pos*EDSCALE); + slot_idx++; + + + int mixed_seq_ports=0; + + if (!single_seq_output) { + + if (node->has_mixed_input_and_sequence_ports()) { + mixed_seq_ports=node->get_output_sequence_port_count(); + } else { + for(int i=0;i<node->get_output_sequence_port_count();i++) { + + Label *text2 = memnew( Label ); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + gnode->add_child(text2); + gnode->set_slot(slot_idx,false,0,Color(),true,TYPE_SEQUENCE,Color(1,1,1,1),seq_port,seq_port); + slot_idx++; + } + } + } + + for(int i=0;i<MAX(node->get_output_value_port_count(),MAX(mixed_seq_ports,node->get_input_value_port_count()));i++) { + + bool left_ok=false; + Variant::Type left_type=Variant::NIL; + String left_name; + + + + if (i<node->get_input_value_port_count()) { + PropertyInfo pi = node->get_input_value_port_info(i); + left_ok=true; + left_type=pi.type; + left_name=pi.name; + } + + bool right_ok=false; + Variant::Type right_type=Variant::NIL; + String right_name; + + if (i>=mixed_seq_ports && i<node->get_output_value_port_count()+mixed_seq_ports) { + PropertyInfo pi = node->get_output_value_port_info(i-mixed_seq_ports); + right_ok=true; + right_type=pi.type; + right_name=pi.name; + } + + HBoxContainer *hbc = memnew( HBoxContainer); + + if (left_ok) { + + Ref<Texture> t; + if (left_type>=0 && left_type<Variant::VARIANT_MAX) { + t=type_icons[left_type]; + } + if (t.is_valid()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(t); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } + + hbc->add_child(memnew(Label(left_name))); + + if (left_type!=Variant::NIL && !script->is_input_value_port_connected(edited_func,E->get(),i)) { + + PropertyInfo pi = node->get_input_value_port_info(i); + Button *button = memnew( Button ); + Variant value = node->get_default_input_value(i); + if (value.get_type()!=left_type) { + //different type? for now convert + //not the same, reconvert + Variant::CallError ce; + const Variant *existingp=&value; + value = Variant::construct(left_type,&existingp,1,ce,false); + } + + if (left_type==Variant::COLOR) { + button->set_custom_minimum_size(Size2(30,0)*EDSCALE); + button->connect("draw",this,"_draw_color_over_button",varray(button,value)); + } else if (left_type==Variant::OBJECT && Ref<Resource>(value).is_valid()) { + + Ref<Resource> res = value; + Array arr; + arr.push_back(button->get_instance_ID()); + arr.push_back(String(value)); + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res,this,"_button_resource_previewed",arr); + + } else if (pi.type==Variant::INT && pi.hint==PROPERTY_HINT_ENUM){ + + button->set_text(pi.hint_string.get_slice(",",value)); + } else { + + button->set_text(value); + } + button->connect("pressed",this,"_default_value_edited",varray(button,E->get(),i)); + hbc->add_child(button); + } + } else { + Control *c = memnew(Control); + c->set_custom_minimum_size(Size2(10,0)*EDSCALE); + hbc->add_child(c); + } + + hbc->add_spacer(); + + if (i<mixed_seq_ports) { + + Label *text2 = memnew( Label ); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + hbc->add_child(text2); + } + + if (right_ok) { + + hbc->add_child(memnew(Label(right_name))); + + Ref<Texture> t; + if (right_type>=0 && right_type<Variant::VARIANT_MAX) { + t=type_icons[right_type]; + } + if (t.is_valid()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(t); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } + + } + + gnode->add_child(hbc); + + if (i<mixed_seq_ports) { + gnode->set_slot(slot_idx,left_ok,left_type,_color_from_type(left_type),true,TYPE_SEQUENCE,Color(1,1,1,1),Ref<Texture>(),seq_port); + } else { + gnode->set_slot(slot_idx,left_ok,left_type,_color_from_type(left_type),right_ok,right_type,_color_from_type(right_type)); + } + + slot_idx++; + } + + graph->add_child(gnode); + + if (gnode->is_comment()) { + graph->move_child(gnode,0); + } + } + + _update_graph_connections(); + graph->call_deferred("set_scroll_ofs",script->get_function_scroll(edited_func)*EDSCALE); //may need to adapt a bit, let it do so + updating_graph=false; + +} + +void VisualScriptEditor::_update_members() { + + + updating_members=true; + + members->clear(); + TreeItem *root = members->create_item(); + + TreeItem *functions = members->create_item(root); + functions->set_selectable(0,false); + functions->set_text(0,TTR("Functions:")); + functions->add_button(0,Control::get_icon("Override","EditorIcons"),1); + functions->add_button(0,Control::get_icon("Add","EditorIcons"),0); + functions->set_custom_bg_color(0,Control::get_color("prop_section","Editor")); + + List<StringName> func_names; + script->get_function_list(&func_names); + for (List<StringName>::Element *E=func_names.front();E;E=E->next()) { + TreeItem *ti = members->create_item(functions) ; + ti->set_text(0,E->get()); + ti->set_selectable(0,true); + ti->set_editable(0,true); + //ti->add_button(0,Control::get_icon("Edit","EditorIcons"),0); function arguments are in the node now + ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); + ti->set_metadata(0,E->get()); + if (E->get()==edited_func) { + ti->set_custom_bg_color(0,get_color("prop_category","Editor")); + ti->set_custom_color(0,Color(1,1,1,1)); + } + if (selected==E->get()) + ti->select(0); + } + + TreeItem *variables = members->create_item(root); + variables->set_selectable(0,false); + variables->set_text(0,TTR("Variables:")); + variables->add_button(0,Control::get_icon("Add","EditorIcons")); + variables->set_custom_bg_color(0,Control::get_color("prop_section","Editor")); + + Ref<Texture> type_icons[Variant::VARIANT_MAX]={ + Control::get_icon("MiniVariant","EditorIcons"), + Control::get_icon("MiniBoolean","EditorIcons"), + Control::get_icon("MiniInteger","EditorIcons"), + Control::get_icon("MiniFloat","EditorIcons"), + Control::get_icon("MiniString","EditorIcons"), + Control::get_icon("MiniVector2","EditorIcons"), + Control::get_icon("MiniRect2","EditorIcons"), + Control::get_icon("MiniVector3","EditorIcons"), + Control::get_icon("MiniMatrix32","EditorIcons"), + Control::get_icon("MiniPlane","EditorIcons"), + Control::get_icon("MiniQuat","EditorIcons"), + Control::get_icon("MiniAabb","EditorIcons"), + Control::get_icon("MiniMatrix3","EditorIcons"), + Control::get_icon("MiniTransform","EditorIcons"), + Control::get_icon("MiniColor","EditorIcons"), + Control::get_icon("MiniImage","EditorIcons"), + Control::get_icon("MiniPath","EditorIcons"), + Control::get_icon("MiniRid","EditorIcons"), + Control::get_icon("MiniObject","EditorIcons"), + Control::get_icon("MiniInput","EditorIcons"), + Control::get_icon("MiniDictionary","EditorIcons"), + Control::get_icon("MiniArray","EditorIcons"), + Control::get_icon("MiniRawArray","EditorIcons"), + Control::get_icon("MiniIntArray","EditorIcons"), + Control::get_icon("MiniFloatArray","EditorIcons"), + Control::get_icon("MiniStringArray","EditorIcons"), + Control::get_icon("MiniVector2Array","EditorIcons"), + Control::get_icon("MiniVector3Array","EditorIcons"), + Control::get_icon("MiniColorArray","EditorIcons") + }; + + List<StringName> var_names; + script->get_variable_list(&var_names); + for (List<StringName>::Element *E=var_names.front();E;E=E->next()) { + TreeItem *ti = members->create_item(variables); + + ti->set_text(0,E->get()); + Variant var = script->get_variable_default_value(E->get()); + ti->set_suffix(0,"="+String(var)); + ti->set_icon(0,type_icons[script->get_variable_info(E->get()).type]); + + ti->set_selectable(0,true); + ti->set_editable(0,true); + ti->add_button(0,Control::get_icon("Edit","EditorIcons"),0); + ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); + ti->set_metadata(0,E->get()); + if (selected==E->get()) + ti->select(0); + } + + TreeItem *_signals = members->create_item(root); + _signals->set_selectable(0,false); + _signals->set_text(0,TTR("Signals:")); + _signals->add_button(0,Control::get_icon("Add","EditorIcons")); + _signals->set_custom_bg_color(0,Control::get_color("prop_section","Editor")); + + List<StringName> signal_names; + script->get_custom_signal_list(&signal_names); + for (List<StringName>::Element *E=signal_names.front();E;E=E->next()) { + TreeItem *ti = members->create_item(_signals); + ti->set_text(0,E->get()); + ti->set_selectable(0,true); + ti->set_editable(0,true); + ti->add_button(0,Control::get_icon("Edit","EditorIcons"),0); + ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); + ti->set_metadata(0,E->get()); + if (selected==E->get()) + ti->select(0); + } + + String base_type=script->get_instance_base_type(); + String icon_type=base_type; + if (!Control::has_icon(base_type,"EditorIcons")) { + icon_type="Object"; + } + + base_type_select->set_text(base_type); + base_type_select->set_icon(Control::get_icon(icon_type,"EditorIcons")); + + updating_members=false; + +} + +void VisualScriptEditor::_member_selected() { + + if (updating_members) + return; + + TreeItem *ti=members->get_selected(); + ERR_FAIL_COND(!ti); + + + selected=ti->get_metadata(0); + //print_line("selected: "+String(selected)); + + + if (ti->get_parent()==members->get_root()->get_children()) { + + if (edited_func!=selected) { + + revert_on_drag=edited_func; + edited_func=selected; + _update_members(); + _update_graph(); + } + + return; //or crash because it will become invalid + + } + + + +} + +void VisualScriptEditor::_member_edited() { + + if (updating_members) + return; + + TreeItem *ti=members->get_edited(); + ERR_FAIL_COND(!ti); + + String name = ti->get_metadata(0); + String new_name = ti->get_text(0); + + if (name==new_name) + return; + + if (!new_name.is_valid_identifier()) { + + EditorNode::get_singleton()->show_warning(TTR("Name is not a valid identifier:")+" "+new_name); + updating_members=true; + ti->set_text(0,name); + updating_members=false; + return; + + } + + if (script->has_function(new_name) || script->has_variable(new_name) || script->has_custom_signal(new_name)) { + + EditorNode::get_singleton()->show_warning(TTR("Name already in use by another func/var/signal:")+" "+new_name); + updating_members=true; + ti->set_text(0,name); + updating_members=false; + return; + } + + TreeItem *root=members->get_root(); + + if (ti->get_parent()==root->get_children()) { + + if (edited_func==selected) { + edited_func=new_name; + } + selected=new_name; + + + _update_graph(); + + undo_redo->create_action(TTR("Rename Function")); + undo_redo->add_do_method(script.ptr(),"rename_function",name,new_name); + undo_redo->add_undo_method(script.ptr(),"rename_function",new_name,name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + + return; //or crash because it will become invalid + + } + + if (ti->get_parent()==root->get_children()->get_next()) { + + selected=new_name; + undo_redo->create_action(TTR("Rename Variable")); + undo_redo->add_do_method(script.ptr(),"rename_variable",name,new_name); + undo_redo->add_undo_method(script.ptr(),"rename_variable",new_name,name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + + return; //or crash because it will become invalid + } + + if (ti->get_parent()==root->get_children()->get_next()->get_next()) { + + selected=new_name; + undo_redo->create_action(TTR("Rename Signal")); + undo_redo->add_do_method(script.ptr(),"rename_custom_signal",name,new_name); + undo_redo->add_undo_method(script.ptr(),"rename_custom_signal",new_name,name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + + return; //or crash because it will become invalid + } +} + +void VisualScriptEditor::_override_pressed(int p_id) { + + //override a virtual function or method from base type + + ERR_FAIL_COND(!virtuals_in_menu.has(p_id)); + + VirtualInMenu vim=virtuals_in_menu[p_id]; + + String name = _validate_name(vim.name); + selected=name; + edited_func=selected; + Ref<VisualScriptFunction> func_node; + func_node.instance(); + func_node->set_name(vim.name); + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(),"add_function",name); + for(int i=0;i<vim.args.size();i++) { + func_node->add_argument(vim.args[i].first,vim.args[i].second); + } + + + undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id(),func_node); + if (vim.ret!=Variant::NIL || vim.ret_variant) { + Ref<VisualScriptReturn> ret_node; + ret_node.instance(); + ret_node->set_return_type(vim.ret); + ret_node->set_enable_return_value(true); + ret_node->set_name(vim.name); + undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id()+1,ret_node,Vector2(500,0)); + + } + + undo_redo->add_undo_method(script.ptr(),"remove_function",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + + _update_graph(); +} + +void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_button) { + + TreeItem *ti=p_item->cast_to<TreeItem>(); + + TreeItem *root=members->get_root(); + + if (ti->get_parent()==root) { + //main buttons + if (ti==root->get_children()) { + //add function, this one uses menu + + if (p_button==1) { + new_function_menu->clear(); + new_function_menu->set_size(Size2(0,0)); + int idx=0; + + virtuals_in_menu.clear(); + + List<MethodInfo> mi; + ClassDB::get_method_list(script->get_instance_base_type(),&mi); + for (List<MethodInfo>::Element *E=mi.front();E;E=E->next()) { + MethodInfo mi=E->get(); + if (mi.flags&METHOD_FLAG_VIRTUAL) { + + VirtualInMenu vim; + vim.name=mi.name; + vim.ret=mi.return_val.type; + if (mi.return_val.name!=String()) + vim.ret_variant=true; + else + vim.ret_variant=false; + + String desc; + + if (mi.return_val.type==Variant::NIL) + desc="var"; + else + desc=Variant::get_type_name(mi.return_val.type); + desc+=" "+mi.name+" ( "; + + + for(int i=0;i<mi.arguments.size();i++) { + + if (i>0) + desc+=", "; + + if (mi.arguments[i].type==Variant::NIL) + desc+="var "; + else + desc+=Variant::get_type_name(mi.arguments[i].type)+" "; + + desc+=mi.arguments[i].name; + + Pair<Variant::Type,String> p; + p.first=mi.arguments[i].type; + p.second=mi.arguments[i].name; + vim.args.push_back( p ); + + } + + desc+=" )"; + + virtuals_in_menu[idx]=vim; + + new_function_menu->add_item(desc,idx); + idx++; + } + } + + Rect2 pos = members->get_item_rect(ti); + new_function_menu->set_pos(members->get_global_pos()+pos.pos+Vector2(0,pos.size.y)); + new_function_menu->popup(); + return; + } else if (p_button==0) { + + + String name = _validate_name("new_function"); + selected=name; + edited_func=selected; + + Ref<VisualScriptFunction> func_node; + func_node.instance(); + func_node->set_name(name); + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(),"add_function",name); + undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id(),func_node); + undo_redo->add_undo_method(script.ptr(),"remove_function",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + _update_graph(); + } + + return; //or crash because it will become invalid + + } + + if (ti==root->get_children()->get_next()) { + //add variable + String name = _validate_name("new_variable"); + selected=name; + + undo_redo->create_action(TTR("Add Variable")); + undo_redo->add_do_method(script.ptr(),"add_variable",name); + undo_redo->add_undo_method(script.ptr(),"remove_variable",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + return; //or crash because it will become invalid + + } + + if (ti==root->get_children()->get_next()->get_next()) { + //add variable + String name = _validate_name("new_signal"); + selected=name; + + undo_redo->create_action(TTR("Add Signal")); + undo_redo->add_do_method(script.ptr(),"add_custom_signal",name); + undo_redo->add_undo_method(script.ptr(),"remove_custom_signal",name); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + return; //or crash because it will become invalid + + } + + } else { + + if (ti->get_parent()==root->get_children()) { + //edit/remove function + String name = ti->get_metadata(0); + + if (p_button==1) { + //delete the function + undo_redo->create_action(TTR("Remove Function")); + undo_redo->add_do_method(script.ptr(),"remove_function",name); + undo_redo->add_undo_method(script.ptr(),"add_function",name); + List<int> nodes; + script->get_node_list(name,&nodes); + for (List<int>::Element *E=nodes.front();E;E=E->next()) { + undo_redo->add_undo_method(script.ptr(),"add_node",name,E->get(),script->get_node(name,E->get()),script->get_node_pos(name,E->get())); + } + + List<VisualScript::SequenceConnection> seq_connections; + + script->get_sequence_connection_list(name,&seq_connections); + + for (List<VisualScript::SequenceConnection>::Element *E=seq_connections.front();E;E=E->next()) { + undo_redo->add_undo_method(script.ptr(),"sequence_connect",name,E->get().from_node,E->get().from_output,E->get().to_node); + } + + List<VisualScript::DataConnection> data_connections; + + script->get_data_connection_list(name,&data_connections); + + for (List<VisualScript::DataConnection>::Element *E=data_connections.front();E;E=E->next()) { + undo_redo->add_undo_method(script.ptr(),"data_connect",name,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); + } + + /* + for(int i=0;i<script->function_get_argument_count(name);i++) { + undo_redo->add_undo_method(script.ptr(),"function_add_argument",name,script->function_get_argument_name(name,i),script->function_get_argument_type(name,i)); + } + */ + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + } else if (p_button==0) { + + } + return; //or crash because it will become invalid + + } + + if (ti->get_parent()==root->get_children()->get_next()) { + //edit/remove variable + + String name = ti->get_metadata(0); + + if (p_button==1) { + + + undo_redo->create_action(TTR("Remove Variable")); + undo_redo->add_do_method(script.ptr(),"remove_variable",name); + undo_redo->add_undo_method(script.ptr(),"add_variable",name,script->get_variable_default_value(name)); + undo_redo->add_undo_method(script.ptr(),"set_variable_info",name,script->call("get_variable_info",name)); //return as dict + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + return; //or crash because it will become invalid + } else if (p_button==0) { + + variable_editor->edit(name); + edit_variable_dialog->set_title(TTR("Editing Variable:")+" "+name); + edit_variable_dialog->popup_centered_minsize(Size2(400,200)*EDSCALE); + } + + } + + if (ti->get_parent()==root->get_children()->get_next()->get_next()) { + //edit/remove variable + String name = ti->get_metadata(0); + + if (p_button==1) { + + undo_redo->create_action(TTR("Remove Signal")); + undo_redo->add_do_method(script.ptr(),"remove_custom_signal",name); + undo_redo->add_undo_method(script.ptr(),"add_custom_signal",name); + + for(int i=0;i<script->custom_signal_get_argument_count(name);i++) { + undo_redo->add_undo_method(script.ptr(),"custom_signal_add_argument",name,script->custom_signal_get_argument_name(name,i),script->custom_signal_get_argument_type(name,i)); + } + + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + } else if (p_button==0) { + + signal_editor->edit(name); + edit_signal_dialog->set_title(TTR("Editing Signal:")+" "+name); + edit_signal_dialog->popup_centered_minsize(Size2(400,300)*EDSCALE); + } + + return; //or crash because it will become invalid + + } + + + } +} + +void VisualScriptEditor::_expression_text_changed(const String& p_text,int p_id) { + + Ref<VisualScriptExpression> vse = script->get_node(edited_func,p_id); + if (!vse.is_valid()) + return; + + + updating_graph=true; + + undo_redo->create_action(TTR("Change Expression"),UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(vse.ptr(),"expression",p_text); + undo_redo->add_undo_property(vse.ptr(),"expression",vse->get("expression")); + undo_redo->add_do_method(this,"_update_graph",p_id); + undo_redo->add_undo_method(this,"_update_graph",p_id); + undo_redo->commit_action(); + + Node *node = graph->get_node(itos(p_id)); + if (node->cast_to<Control>()) + node->cast_to<Control>()->set_size(Vector2(1,1)); //shrink if text is smaller + + updating_graph=false; + +} + +void VisualScriptEditor::_available_node_doubleclicked() { + + TreeItem *item = nodes->get_selected(); + + if (!item) + return; + + String which = item->get_metadata(0); + if (which==String()) + return; + + Vector2 ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + while(true) { + bool exists=false; + List<int> existing; + script->get_node_list(edited_func,&existing); + for (List<int>::Element *E=existing.front();E;E=E->next()) { + Point2 pos = script->get_node_pos(edited_func,E->get()); + if (pos.distance_to(ofs)<15) { + ofs+=Vector2(graph->get_snap(),graph->get_snap()); + exists=true; + break; + } + } + + if (exists) + continue; + break; + + } + + + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(which); + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + +} + +void VisualScriptEditor::_update_available_nodes() { + + nodes->clear(); + + TreeItem *root = nodes->create_item(); + + Map<String,TreeItem*> path_cache; + + String filter = node_filter->get_text(); + + List<String> fnodes; + VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); + + for (List<String>::Element *E=fnodes.front();E;E=E->next()) { + + + Vector<String> path = E->get().split("/"); + + if (filter!=String() && path.size() && path[path.size()-1].findn(filter)==-1) + continue; + + String sp; + TreeItem* parent=root; + + for(int i=0;i<path.size()-1;i++) { + + if (i>0) + sp+=","; + sp+=path[i]; + if (!path_cache.has(sp)) { + TreeItem* pathn = nodes->create_item(parent); + pathn->set_selectable(0,false); + pathn->set_text(0,path[i].capitalize()); + path_cache[sp]=pathn; + parent=pathn; + if (filter==String()) { + pathn->set_collapsed(true); //should remember state + } + } else { + parent=path_cache[sp]; + } + } + + TreeItem *item = nodes->create_item(parent); + item->set_text(0,path[path.size()-1].capitalize()); + item->set_selectable(0,true); + item->set_metadata(0,E->get()); + } + +} + +String VisualScriptEditor::_validate_name(const String& p_name) const { + + String valid=p_name; + + int counter=1; + while(true) { + + bool exists = script->has_function(valid) || script->has_variable(valid) || script->has_custom_signal(valid); + + if (exists) { + counter++; + valid=p_name+"_"+itos(counter); + continue; + } + + break; + } + + return valid; +} + +void VisualScriptEditor::_on_nodes_delete() { + + + List<int> to_erase; + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_erase.push_back(gn->get_name().operator String().to_int()); + } + } + } + + if (to_erase.empty()) + return; + + undo_redo->create_action("Remove VisualScript Nodes"); + + for(List<int>::Element*F=to_erase.front();F;F=F->next()) { + + + undo_redo->add_do_method(script.ptr(),"remove_node",edited_func,F->get()); + undo_redo->add_undo_method(script.ptr(),"add_node",edited_func,F->get(),script->get_node(edited_func,F->get()),script->get_node_pos(edited_func,F->get())); + + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(edited_func,&sequence_conns); + + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_conns.front();E;E=E->next()) { + + if (E->get().from_node==F->get() || E->get().to_node==F->get()) { + undo_redo->add_undo_method(script.ptr(),"sequence_connect",edited_func,E->get().from_node,E->get().from_output,E->get().to_node); + } + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(edited_func,&data_conns); + + for (List<VisualScript::DataConnection>::Element *E=data_conns.front();E;E=E->next()) { + + if (E->get().from_node==F->get() || E->get().to_node==F->get()) { + undo_redo->add_undo_method(script.ptr(),"data_connect",edited_func,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); + } + } + + } + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); +} + + +void VisualScriptEditor::_on_nodes_duplicate() { + + + List<int> to_duplicate; + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_duplicate.push_back(gn->get_name().operator String().to_int()); + } + } + } + + if (to_duplicate.empty()) + return; + + undo_redo->create_action("Duplicate VisualScript Nodes"); + int idc=script->get_available_id()+1; + + Set<int> to_select; + + for(List<int>::Element*F=to_duplicate.front();F;F=F->next()) { + + Ref<VisualScriptNode> node = script->get_node(edited_func,F->get()); + + Ref<VisualScriptNode> dupe = node->duplicate(); + + int new_id = idc++; + to_select.insert(new_id); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,dupe,script->get_node_pos(edited_func,F->get())+Vector2(20,20)); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + + } + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(to_select.has(id)); + + } + } + + if (to_select.size()) { + EditorNode::get_singleton()->push_item(script->get_node(edited_func,to_select.front()->get()).ptr()); + } + +} + +void VisualScriptEditor::_input(const InputEvent& p_event) { + + if (p_event.type==InputEvent::MOUSE_BUTTON && !p_event.mouse_button.pressed && p_event.mouse_button.button_index==BUTTON_LEFT) { + revert_on_drag=String(); //so we can still drag functions + } +} + +Variant VisualScriptEditor::get_drag_data_fw(const Point2& p_point,Control* p_from) { + + + if (p_from==nodes) { + + TreeItem *it = nodes->get_item_at_pos(p_point); + if (!it) + return Variant(); + String type=it->get_metadata(0); + if (type==String()) + return Variant(); + + Dictionary dd; + dd["type"]="visual_script_node_drag"; + dd["node_type"]=type; + + Label *label = memnew(Label); + label->set_text(it->get_text(0)); + set_drag_preview(label); + return dd; + } + + if (p_from==members) { + + + TreeItem *it = members->get_item_at_pos(p_point); + if (!it) + return Variant(); + + String type=it->get_metadata(0); + + if (type==String()) + return Variant(); + + + Dictionary dd; + TreeItem *root=members->get_root(); + + if (it->get_parent()==root->get_children()) { + + dd["type"]="visual_script_function_drag"; + dd["function"]=type; + if (revert_on_drag!=String()) { + edited_func=revert_on_drag; //revert so function does not change + revert_on_drag=String(); + _update_graph(); + } + } else if (it->get_parent()==root->get_children()->get_next()) { + + dd["type"]="visual_script_variable_drag"; + dd["variable"]=type; + } else if (it->get_parent()==root->get_children()->get_next()->get_next()) { + + dd["type"]="visual_script_signal_drag"; + dd["signal"]=type; + + } else { + return Variant(); + } + + + + + + + Label *label = memnew(Label); + label->set_text(it->get_text(0)); + set_drag_preview(label); + return dd; + } + return Variant(); +} + +bool VisualScriptEditor::can_drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from) const{ + + if (p_from==graph) { + + + Dictionary d = p_data; + if (d.has("type") && + ( + String(d["type"])=="visual_script_node_drag" || + String(d["type"])=="visual_script_function_drag" || + String(d["type"])=="visual_script_variable_drag" || + String(d["type"])=="visual_script_signal_drag" || + String(d["type"])=="obj_property" || + String(d["type"])=="resource" || + String(d["type"])=="files" || + String(d["type"])=="nodes" + ) ) { + + + if (String(d["type"])=="obj_property") { + +#ifdef OSX_ENABLED + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Meta to drop a Getter. Hold Shift to drop a generic signature.")); +#else + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature.")); +#endif + } + + if (String(d["type"])=="nodes") { + +#ifdef OSX_ENABLED + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Meta to drop a simple reference to the node.")); +#else + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Ctrl to drop a simple reference to the node.")); +#endif + } + + if (String(d["type"])=="visual_script_variable_drag") { + +#ifdef OSX_ENABLED + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Meta to drop a Variable Setter.")); +#else + const_cast<VisualScriptEditor*>(this)->_show_hint(TTR("Hold Ctrl to drop a Variable Setter.")); +#endif + } + + return true; + + } + + + + } + + + return false; +} + +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif + + + +void VisualScriptEditor::drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from){ + + if (p_from==graph) { + + Dictionary d = p_data; + if (d.has("type") && String(d["type"])=="visual_script_node_drag") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + + ofs/=EDSCALE; + + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(d["node_type"]); + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="visual_script_variable_drag") { + +#ifdef OSX_ENABLED + bool use_set = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool use_set = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptNode> vnode; + if (use_set) { + Ref<VisualScriptVariableSet> vnodes; + vnodes.instance(); + vnodes->set_variable(d["variable"]); + vnode=vnodes; + } else { + + Ref<VisualScriptVariableGet> vnodeg; + vnodeg.instance(); + vnodeg->set_variable(d["variable"]); + vnode=vnodeg; + } + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="visual_script_function_drag") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptFunctionCall> vnode; + vnode.instance(); + vnode->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SELF); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_do_method(vnode.ptr(),"set_base_type",script->get_instance_base_type()); + undo_redo->add_do_method(vnode.ptr(),"set_function",d["function"]); + + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + + } + + + if (d.has("type") && String(d["type"])=="visual_script_signal_drag") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptEmitSignal> vnode; + vnode.instance(); + vnode->set_signal(d["signal"]); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="resource") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Ref<VisualScriptPreload> prnode; + prnode.instance(); + prnode->set_preload(d["resource"]); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Preload Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,prnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + Node* node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (d.has("type") && String(d["type"])=="files") { + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; + + Array files = d["files"]; + + List<int> new_ids; + int new_id = script->get_available_id(); + + if (files.size()) { + undo_redo->create_action(TTR("Add Preload Node")); + + for(int i=0;i<files.size();i++) { + + Ref<Resource> res = ResourceLoader::load(files[i]); + if (!res.is_valid()) + continue; + + Ref<VisualScriptPreload> prnode; + prnode.instance(); + prnode->set_preload(res); + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,prnode,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + new_ids.push_back(new_id); + new_id++; + ofs+=Vector2(20,20)*EDSCALE; + } + + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + } + + for(List<int>::Element *E=new_ids.front();E;E=E->next()) { + + Node* node = graph->get_node(itos(E->get())); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + } + + if (d.has("type") && String(d["type"])=="nodes") { + + Node* sn = _find_script_node(get_tree()->get_edited_scene_root(),get_tree()->get_edited_scene_root(),script); + + + if (!sn) { + EditorNode::get_singleton()->show_warning("Can't drop nodes because script '"+get_name()+"' is not used in this scene."); + return; + } + + +#ifdef OSX_ENABLED + bool use_node = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool use_node = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + + + Array nodes = d["nodes"]; + + Vector2 ofs = graph->get_scroll_ofs() + p_point; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + ofs/=EDSCALE; + + undo_redo->create_action(TTR("Add Node(s) From Tree")); + int base_id = script->get_available_id(); + + if (nodes.size()>1) { + use_node=true; + } + + for(int i=0;i<nodes.size();i++) { + + NodePath np = nodes[i]; + Node *node = get_node(np); + if (!node) { + continue; + } + + Ref<VisualScriptNode> n; + + if (use_node) { + Ref<VisualScriptSceneNode> scene_node; + scene_node.instance(); + scene_node->set_node_path(sn->get_path_to(node)); + n=scene_node; + + + } else { + Ref<VisualScriptFunctionCall> call; + call.instance(); + call->set_call_mode(VisualScriptFunctionCall::CALL_MODE_NODE_PATH); + call->set_base_path(sn->get_path_to(node));; + call->set_base_type(node->get_class()); + n=call; + + method_select->select_method_from_instance(node); + selecting_method_id=base_id; + + } + + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,base_id,n,ofs); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,base_id); + + base_id++; + ofs+=Vector2(25,25); + } + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + + } + + if (d.has("type") && String(d["type"])=="obj_property") { + + Node* sn = _find_script_node(get_tree()->get_edited_scene_root(),get_tree()->get_edited_scene_root(),script); + + + if (!sn && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + EditorNode::get_singleton()->show_warning("Can't drop properties because script '"+get_name()+"' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."); + return; + } + + Object *obj=d["object"]; + + if (!obj) + return; + + Node *node = obj->cast_to<Node>(); + Vector2 ofs = graph->get_scroll_ofs() + p_point; + + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + + ofs/=EDSCALE; +#ifdef OSX_ENABLED + bool use_get = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool use_get = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + + if (!node || Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + + + if (use_get) + undo_redo->create_action(TTR("Add Getter Property")); + else + undo_redo->create_action(TTR("Add Setter Property")); + + int base_id = script->get_available_id(); + + Ref<VisualScriptNode> vnode; + + if (!use_get) { + + Ref<VisualScriptPropertySet> pset; + pset.instance(); + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + pset->set_base_type(obj->get_class()); + /*if (use_value) { + pset->set_use_builtin_value(true); + pset->set_builtin_value(d["value"]); + }*/ + vnode=pset; + } else { + + Ref<VisualScriptPropertyGet> pget; + pget.instance(); + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + pget->set_base_type(obj->get_class()); + + vnode=pget; + + } + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,base_id,vnode,ofs); + undo_redo->add_do_method(vnode.ptr(),"set_property",d["property"]); + if (!use_get) { + undo_redo->add_do_method(vnode.ptr(),"set_default_input_value",0,d["value"]); + } + + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,base_id); + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + + } else { + + + + if (use_get) + undo_redo->create_action(TTR("Add Getter Property")); + else + undo_redo->create_action(TTR("Add Setter Property")); + + int base_id = script->get_available_id(); + + Ref<VisualScriptNode> vnode; + + if (!use_get) { + + Ref<VisualScriptPropertySet> pset; + pset.instance(); + if (sn==node) { + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); + } else { + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); + pset->set_base_path(sn->get_path_to(node)); + } + + vnode=pset; + } else { + + Ref<VisualScriptPropertyGet> pget; + pget.instance(); + if (sn==node) { + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); + } else { + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); + pget->set_base_path(sn->get_path_to(node)); + } + vnode=pget; + + } + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,base_id,vnode,ofs); + undo_redo->add_do_method(vnode.ptr(),"set_property",d["property"]); + if (!use_get) { + undo_redo->add_do_method(vnode.ptr(),"set_default_input_value",0,d["value"]); + } + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,base_id); + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + undo_redo->commit_action(); + } + + + } + + + } + + +} + + +void VisualScriptEditor::_selected_method(const String& p_method) { + + Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func,selecting_method_id); + if (!vsfc.is_valid()) + return; + vsfc->set_function(p_method); + +} + +void VisualScriptEditor::_draw_color_over_button(Object* obj,Color p_color) { + + Button *button = obj->cast_to<Button>(); + if (!button) + return; + + Ref<StyleBox> normal = get_stylebox("normal","Button" ); + button->draw_rect(Rect2(normal->get_offset(),button->get_size()-normal->get_minimum_size()),p_color); + +} + +void VisualScriptEditor::_button_resource_previewed(const String& p_path,const Ref<Texture>& p_preview,Variant p_ud) { + + + Array ud=p_ud; + ERR_FAIL_COND(ud.size()!=2); + + ObjectID id = ud[0]; + Object *obj = ObjectDB::get_instance(id); + + if (!obj) + return; + + Button *b = obj->cast_to<Button>(); + ERR_FAIL_COND(!b); + + if (p_preview.is_null()) { + b->set_text(ud[1]); + } else { + + b->set_icon(p_preview); + } + +} + +///////////////////////// + + + +void VisualScriptEditor::apply_code() { + + +} + +Ref<Script> VisualScriptEditor::get_edited_script() const{ + + return script; +} + +Vector<String> VisualScriptEditor::get_functions(){ + + return Vector<String>(); +} + +void VisualScriptEditor::set_edited_script(const Ref<Script>& p_script){ + + + script=p_script; + signal_editor->script=p_script; + signal_editor->undo_redo=undo_redo; + variable_editor->script=p_script; + variable_editor->undo_redo=undo_redo; + + + script->connect("node_ports_changed",this,"_node_ports_changed"); + + _update_members(); + _update_available_nodes(); +} + +void VisualScriptEditor::reload_text(){ + + +} + +String VisualScriptEditor::get_name(){ + + String name; + + if (script->get_path().find("local://")==-1 && script->get_path().find("::")==-1) { + name=script->get_path().get_file(); + if (is_unsaved()) { + name+="(*)"; + } + } else if (script->get_name()!="") + name=script->get_name(); + else + name=script->get_class()+"("+itos(script->get_instance_ID())+")"; + + return name; + +} + +Ref<Texture> VisualScriptEditor::get_icon(){ + + return Control::get_icon("VisualScript","EditorIcons"); +} + +bool VisualScriptEditor::is_unsaved(){ +#ifdef TOOLS_ENABLED + + return script->is_edited() || script->are_subnodes_edited(); +#else + return false; +#endif +} + +Variant VisualScriptEditor::get_edit_state(){ + + Dictionary d; + d["function"]=edited_func; + d["scroll"]=graph->get_scroll_ofs(); + d["zoom"]=graph->get_zoom(); + d["using_snap"]=graph->is_using_snap(); + d["snap"]=graph->get_snap(); + return d; +} + +void VisualScriptEditor::set_edit_state(const Variant& p_state){ + + Dictionary d = p_state; + if (d.has("function")) { + edited_func=p_state; + selected=edited_func; + + } + + _update_graph(); + _update_members(); + + if (d.has("scroll")) { + graph->set_scroll_ofs(d["scroll"]); + } + if (d.has("zoom")) { + graph->set_zoom(d["zoom"]); + } + if (d.has("snap")) { + graph->set_snap(d["snap"]); + } + if (d.has("snap_enabled")) { + graph->set_use_snap(d["snap_enabled"]); + } + +} + + +void VisualScriptEditor::_center_on_node(int p_id) { + + Node *n = graph->get_node(itos(p_id)); + if (!n) + return; + GraphNode *gn = n->cast_to<GraphNode>(); + if (gn) { + gn->set_selected(true); + Vector2 new_scroll = gn->get_offset() - graph->get_size()*0.5 + gn->get_size()*0.5; + graph->set_scroll_ofs( new_scroll ); + script->set_function_scroll(edited_func,new_scroll/EDSCALE); + script->set_edited(true); //so it's saved + + } +} + +void VisualScriptEditor::goto_line(int p_line, bool p_with_error){ + + p_line+=1; //add one because script lines begin from 0. + + if (p_with_error) + error_line=p_line; + + List<StringName> functions; + script->get_function_list(&functions); + for (List<StringName>::Element *E=functions.front();E;E=E->next()) { + + if (script->has_node(E->get(),p_line)) { + + edited_func=E->get(); + selected=edited_func; + _update_graph(); + _update_members(); + + call_deferred("_center_on_node",p_line); //editor might be just created and size might not exist yet + + return; + } + } +} + +void VisualScriptEditor::trim_trailing_whitespace(){ + + +} + +void VisualScriptEditor::ensure_focus(){ + + graph->grab_focus(); +} + +void VisualScriptEditor::tag_saved_version(){ + + +} + +void VisualScriptEditor::reload(bool p_soft){ + + +} + +void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints){ + + List<StringName> functions; + script->get_function_list(&functions); + for (List<StringName>::Element *E=functions.front();E;E=E->next()) { + + List<int> nodes; + script->get_node_list(E->get(),&nodes); + for (List<int>::Element *F=nodes.front();F;F=F->next()) { + + Ref<VisualScriptNode> vsn = script->get_node(E->get(),F->get()); + if (vsn->is_breakpoint()) { + p_breakpoints->push_back(F->get()-1); //subtract 1 because breakpoints in text start from zero + } + } + } +} + +bool VisualScriptEditor::goto_method(const String& p_method){ + + if (!script->has_function(p_method)) + return false; + + edited_func=p_method; + selected=edited_func; + _update_members(); + _update_graph(); + return true; +} + +void VisualScriptEditor::add_callback(const String& p_function,PoolStringArray p_args){ + + if (script->has_function(p_function)) { + edited_func=p_function; + selected=edited_func; + _update_members(); + _update_graph(); + return; + } + + Ref<VisualScriptFunction> func; + func.instance(); + for(int i=0;i<p_args.size();i++) { + + String name = p_args[i]; + Variant::Type type=Variant::NIL; + + if (name.find(":")!=-1) { + String tt = name.get_slice(":",1); + name=name.get_slice(":",0); + for(int j=0;j<Variant::VARIANT_MAX;j++) { + + String tname = Variant::get_type_name(Variant::Type(j)); + if (tname==tt) { + type=Variant::Type(j); + break; + } + } + } + + func->add_argument(type,name); + } + + func->set_name(p_function); + script->add_function(p_function); + script->add_node(p_function,script->get_available_id(),func); + + edited_func=p_function; + selected=edited_func; + _update_members(); + _update_graph(); + graph->call_deferred("set_scroll_ofs",script->get_function_scroll(edited_func)); //for first time it might need to be later + + //undo_redo->clear_history(); + +} + +void VisualScriptEditor::update_settings(){ + + _update_graph(); +} + +void VisualScriptEditor::set_debugger_active(bool p_active) { + if (!p_active) { + error_line=-1; + _update_graph(); //clear line break + } +} + +void VisualScriptEditor::set_tooltip_request_func(String p_method,Object* p_obj){ + + +} + +Control *VisualScriptEditor::get_edit_menu(){ + + return edit_menu; +} + +void VisualScriptEditor::_change_base_type() { + + select_base_type->popup(true); +} + +void VisualScriptEditor::_change_base_type_callback() { + + String bt = select_base_type->get_selected_type(); + + ERR_FAIL_COND(bt==String()); + undo_redo->create_action("Change Base Type"); + undo_redo->add_do_method(script.ptr(),"set_instance_base_type",bt); + undo_redo->add_undo_method(script.ptr(),"set_instance_base_type",script->get_instance_base_type()); + undo_redo->add_do_method(this,"_update_members"); + undo_redo->add_undo_method(this,"_update_members"); + undo_redo->commit_action(); + +} + +void VisualScriptEditor::_node_selected(Node* p_node) { + + Ref<VisualScriptNode> vnode = p_node->get_meta("__vnode"); + if (vnode.is_null()) + return; + + EditorNode::get_singleton()->push_item(vnode.ptr()); //edit node in inspector +} + +static bool _get_out_slot(const Ref<VisualScriptNode>& p_node,int p_slot,int& r_real_slot,bool& r_sequence) { + + if (p_slot<p_node->get_output_sequence_port_count()) { + r_sequence=true; + r_real_slot=p_slot; + + return true; + } + + r_real_slot=p_slot-p_node->get_output_sequence_port_count(); + r_sequence=false; + + return (r_real_slot<p_node->get_output_value_port_count()); + +} + +static bool _get_in_slot(const Ref<VisualScriptNode>& p_node,int p_slot,int& r_real_slot,bool& r_sequence) { + + if (p_slot==0 && p_node->has_input_sequence_port()) { + r_sequence=true; + r_real_slot=0; + return true; + } + + + r_real_slot=p_slot-(p_node->has_input_sequence_port()?1:0); + r_sequence=false; + + return r_real_slot<p_node->get_input_value_port_count(); + +} + + +void VisualScriptEditor::_begin_node_move() { + + undo_redo->create_action("Move Node(s)"); +} + +void VisualScriptEditor::_end_node_move() { + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_move_node(String func,int p_id,const Vector2& p_to) { + + + + if (func==String(edited_func)) { + Node* node = graph->get_node(itos(p_id)); + if (node && node->cast_to<GraphNode>()) + node->cast_to<GraphNode>()->set_offset(p_to); + } + script->set_node_pos(edited_func,p_id,p_to/EDSCALE); +} + +void VisualScriptEditor::_node_moved(Vector2 p_from,Vector2 p_to, int p_id) { + + undo_redo->add_do_method(this,"_move_node",String(edited_func),p_id,p_to); + undo_redo->add_undo_method(this,"_move_node",String(edited_func),p_id,p_from); +} + +void VisualScriptEditor::_remove_node(int p_id) { + + + undo_redo->create_action("Remove VisualScript Node"); + + undo_redo->add_do_method(script.ptr(),"remove_node",edited_func,p_id); + undo_redo->add_undo_method(script.ptr(),"add_node",edited_func,p_id,script->get_node(edited_func,p_id),script->get_node_pos(edited_func,p_id)); + + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(edited_func,&sequence_conns); + + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_conns.front();E;E=E->next()) { + + if (E->get().from_node==p_id || E->get().to_node==p_id) { + undo_redo->add_undo_method(script.ptr(),"sequence_connect",edited_func,E->get().from_node,E->get().from_output,E->get().to_node); + } + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(edited_func,&data_conns); + + for (List<VisualScript::DataConnection>::Element *E=data_conns.front();E;E=E->next()) { + + if (E->get().from_node==p_id || E->get().to_node==p_id) { + undo_redo->add_undo_method(script.ptr(),"data_connect",edited_func,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); + } + } + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); +} + + +void VisualScriptEditor::_node_ports_changed(const String& p_func,int p_id) { + + if (p_func!=String(edited_func)) + return; + + _update_graph(p_id); +} + +void VisualScriptEditor::_graph_connected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot) { + + Ref<VisualScriptNode> from_node = script->get_node(edited_func,p_from.to_int()); + ERR_FAIL_COND(!from_node.is_valid()); + + bool from_seq; + int from_port; + + if (!_get_out_slot(from_node,p_from_slot,from_port,from_seq)) + return; //can't connect this, it' s invalid + + Ref<VisualScriptNode> to_node = script->get_node(edited_func,p_to.to_int()); + ERR_FAIL_COND(!to_node.is_valid()); + + bool to_seq; + int to_port; + + if (!_get_in_slot(to_node,p_to_slot,to_port,to_seq)) + return; //can't connect this, it' s invalid + + + ERR_FAIL_COND(from_seq!=to_seq); + + + undo_redo->create_action("Connect Nodes"); + + if (from_seq) { + undo_redo->add_do_method(script.ptr(),"sequence_connect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + undo_redo->add_undo_method(script.ptr(),"sequence_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + } else { + undo_redo->add_do_method(script.ptr(),"data_connect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + undo_redo->add_undo_method(script.ptr(),"data_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + //update nodes in sgraph + undo_redo->add_do_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_do_method(this,"_update_graph",p_to.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_to.to_int()); + } + + undo_redo->add_do_method(this,"_update_graph_connections"); + undo_redo->add_undo_method(this,"_update_graph_connections"); + + undo_redo->commit_action(); + +} + +void VisualScriptEditor::_graph_disconnected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot){ + + Ref<VisualScriptNode> from_node = script->get_node(edited_func,p_from.to_int()); + ERR_FAIL_COND(!from_node.is_valid()); + + bool from_seq; + int from_port; + + if (!_get_out_slot(from_node,p_from_slot,from_port,from_seq)) + return; //can't connect this, it' s invalid + + Ref<VisualScriptNode> to_node = script->get_node(edited_func,p_to.to_int()); + ERR_FAIL_COND(!to_node.is_valid()); + + bool to_seq; + int to_port; + + if (!_get_in_slot(to_node,p_to_slot,to_port,to_seq)) + return; //can't connect this, it' s invalid + + + ERR_FAIL_COND(from_seq!=to_seq); + + + undo_redo->create_action("Connect Nodes"); + + if (from_seq) { + undo_redo->add_do_method(script.ptr(),"sequence_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + undo_redo->add_undo_method(script.ptr(),"sequence_connect",edited_func,p_from.to_int(),from_port,p_to.to_int()); + } else { + undo_redo->add_do_method(script.ptr(),"data_disconnect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + undo_redo->add_undo_method(script.ptr(),"data_connect",edited_func,p_from.to_int(),from_port,p_to.to_int(),to_port); + //update nodes in sgraph + undo_redo->add_do_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_do_method(this,"_update_graph",p_to.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_from.to_int()); + undo_redo->add_undo_method(this,"_update_graph",p_to.to_int()); + } + undo_redo->add_do_method(this,"_update_graph_connections"); + undo_redo->add_undo_method(this,"_update_graph_connections"); + + undo_redo->commit_action(); +} + + + +void VisualScriptEditor::_graph_connect_to_empty(const String& p_from,int p_from_slot,const Vector2& p_release_pos) { + + Node* node = graph->get_node(p_from); + if (!node) + return; + + GraphNode *gn = node->cast_to<GraphNode>(); + if (!gn) + return; + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,p_from.to_int()); + if (!vsn.is_valid()) + return; + + if (p_from_slot<vsn->get_output_sequence_port_count()) { + + port_action_popup->clear(); + port_action_popup->add_item(TTR("Condition"),CREATE_COND); + port_action_popup->add_item(TTR("Sequence"),CREATE_SEQUENCE); + port_action_popup->add_item(TTR("Switch"),CREATE_SWITCH); + port_action_popup->add_item(TTR("Iterator"),CREATE_ITERATOR); + port_action_popup->add_item(TTR("While"),CREATE_WHILE); + port_action_popup->add_item(TTR("Return"),CREATE_RETURN); + + port_action_node=p_from.to_int(); + port_action_output=p_from_slot; + + } else { + port_action_popup->clear(); + port_action_popup->add_item(TTR("Call"),CREATE_CALL); + port_action_popup->add_item(TTR("Get"),CREATE_GET); + port_action_popup->add_item(TTR("Set"),CREATE_SET); + + + port_action_output=p_from_slot-vsn->get_output_sequence_port_count(); + port_action_node=p_from.to_int(); + + + } + + port_action_pos=p_release_pos; + port_action_popup->set_size(Size2(1,1)); + port_action_popup->set_pos(graph->get_global_pos()+p_release_pos); + port_action_popup->popup(); +} + +VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_node,int p_output,Set<int> &visited_nodes) { + + + VisualScriptNode::TypeGuess tg; + tg.type=Variant::NIL; + + if (visited_nodes.has(p_node)) + return tg; //no loop + + visited_nodes.insert(p_node); + + Ref<VisualScriptNode> node = script->get_node(edited_func,p_node); + + if (!node.is_valid()) { + + return tg; + } + + Vector<VisualScriptNode::TypeGuess> in_guesses; + + for(int i=0;i<node->get_input_value_port_count();i++) { + PropertyInfo pi = node->get_input_value_port_info(i); + VisualScriptNode::TypeGuess g; + g.type=pi.type; + + if (g.type==Variant::NIL || g.type==Variant::OBJECT) { + //any or object input, must further guess what this is + int from_node; + int from_port; + + if (script->get_input_value_port_connection_source(edited_func,p_node,i,&from_node,&from_port)) { + + g = _guess_output_type(from_node,from_port,visited_nodes); + } else { + Variant defval = node->get_default_input_value(i); + if (defval.get_type()==Variant::OBJECT) { + + Object *obj = defval; + + if (obj) { + + g.type=Variant::OBJECT; + g.GDCLASS=obj->get_class(); + g.script=obj->get_script(); + } + } + } + + } + + in_guesses.push_back(g); + } + + return node->guess_output_type(in_guesses.ptr(),p_output); +} + +void VisualScriptEditor::_port_action_menu(int p_option) { + + Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap,snap)); + } + ofs/=EDSCALE; + + bool seq_connect=false; + + Ref<VisualScriptNode> vnode; + Set<int> vn; + + switch(p_option) { + + case CREATE_CALL: { + + Ref<VisualScriptFunctionCall> n; + n.instance(); + vnode=n; + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node,port_action_output,vn); + + if (tg.type==Variant::OBJECT) { + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + + if (tg.GDCLASS!=StringName()) { + n->set_base_type(tg.GDCLASS); + } else { + n->set_base_type("Object"); + } + + if (tg.script.is_valid()) { + n->set_base_script(tg.script->get_path()); + new_connect_node_select->select_method_from_script(tg.script); + } else { + new_connect_node_select->select_method_from_base_type(n->get_base_type()); + } + + + } else { + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + n->set_basic_type(tg.type); + new_connect_node_select->select_method_from_basic_type(tg.type); + } + + + + } break; + case CREATE_SET: { + + Ref<VisualScriptPropertySet> n; + n.instance(); + vnode=n; + + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node,port_action_output,vn); + + if (tg.type==Variant::OBJECT) { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + + if (tg.GDCLASS!=StringName()) { + n->set_base_type(tg.GDCLASS); + } else { + n->set_base_type("Object"); + } + + if (tg.script.is_valid()) { + n->set_base_script(tg.script->get_path()); + new_connect_node_select->select_property_from_script(tg.script); + } else { + new_connect_node_select->select_property_from_base_type(n->get_base_type()); + } + + + } else { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); + n->set_basic_type(tg.type); + new_connect_node_select->select_property_from_basic_type(tg.type,tg.ev_type); + } + } break; + case CREATE_GET: { + + Ref<VisualScriptPropertyGet> n; + n.instance(); + vnode=n; + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node,port_action_output,vn); + + if (tg.type==Variant::OBJECT) { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + + if (tg.GDCLASS!=StringName()) { + n->set_base_type(tg.GDCLASS); + } else { + n->set_base_type("Object"); + } + + if (tg.script.is_valid()) { + n->set_base_script(tg.script->get_path()); + new_connect_node_select->select_property_from_script(tg.script); + } else { + new_connect_node_select->select_property_from_base_type(n->get_base_type()); + } + + + } else { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); + n->set_basic_type(tg.type); + new_connect_node_select->select_property_from_basic_type(tg.type,tg.ev_type); + } + + } break; + case CREATE_COND: { + + Ref<VisualScriptCondition> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_SEQUENCE: { + + Ref<VisualScriptSequence> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_SWITCH: { + + Ref<VisualScriptSwitch> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_ITERATOR: { + + Ref<VisualScriptIterator> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_WHILE: { + + Ref<VisualScriptWhile> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + case CREATE_RETURN: { + + Ref<VisualScriptReturn> n; + n.instance(); + vnode=n; + seq_connect=true; + + } break; + + } + + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,vnode,ofs); + if (seq_connect) { + undo_redo->add_do_method(script.ptr(),"sequence_connect",edited_func,port_action_node,port_action_output,new_id); + } + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + undo_redo->add_do_method(this,"_update_graph",new_id); + undo_redo->add_undo_method(this,"_update_graph",new_id); + undo_redo->commit_action(); + + port_action_new_node=new_id; + +} + +void VisualScriptEditor::_selected_connect_node_method_or_setget(const String& p_text) { + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,port_action_new_node); + + if (vsn->cast_to<VisualScriptFunctionCall>()) { + + Ref<VisualScriptFunctionCall> vsfc = vsn; + vsfc->set_function(p_text); + script->data_connect(edited_func,port_action_node,port_action_output,port_action_new_node,0); + } + + if (vsn->cast_to<VisualScriptPropertySet>()) { + + Ref<VisualScriptPropertySet> vsp = vsn; + vsp->set_property(p_text); + script->data_connect(edited_func,port_action_node,port_action_output,port_action_new_node,0); + } + + if (vsn->cast_to<VisualScriptPropertyGet>()) { + + Ref<VisualScriptPropertyGet> vsp = vsn; + vsp->set_property(p_text); + script->data_connect(edited_func,port_action_node,port_action_output,port_action_new_node,0); + } + + _update_graph(port_action_new_node); + _update_graph_connections(); + +} + + +void VisualScriptEditor::_default_value_changed() { + + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,editing_id); + if (vsn.is_null()) + return; + + undo_redo->create_action("Change Input Value"); + undo_redo->add_do_method(vsn.ptr(),"set_default_input_value",editing_input,default_value_edit->get_variant()); + undo_redo->add_undo_method(vsn.ptr(),"set_default_input_value",editing_input,vsn->get_default_input_value(editing_input)); + + undo_redo->add_do_method(this,"_update_graph",editing_id); + undo_redo->add_undo_method(this,"_update_graph",editing_id); + undo_redo->commit_action(); + +} + +void VisualScriptEditor::_default_value_edited(Node * p_button,int p_id,int p_input_port) { + + Ref<VisualScriptNode> vsn = script->get_node(edited_func,p_id); + if (vsn.is_null()) + return; + + PropertyInfo pinfo = vsn->get_input_value_port_info(p_input_port); + Variant existing = vsn->get_default_input_value(p_input_port); + if (pinfo.type!=Variant::NIL && existing.get_type()!=pinfo.type) { + + Variant::CallError ce; + const Variant *existingp=&existing; + existing = Variant::construct(pinfo.type,&existingp,1,ce,false); + + } + + default_value_edit->set_pos(p_button->cast_to<Control>()->get_global_pos()+Vector2(0,p_button->cast_to<Control>()->get_size().y)); + default_value_edit->set_size(Size2(1,1)); + if (default_value_edit->edit(NULL,pinfo.name,pinfo.type,existing,pinfo.hint,pinfo.hint_string)) { + if (pinfo.hint==PROPERTY_HINT_MULTILINE_TEXT) + default_value_edit->popup_centered_ratio(); + else + default_value_edit->popup(); + } + + editing_id = p_id; + editing_input=p_input_port; + +} + +void VisualScriptEditor::_show_hint(const String& p_hint) { + + hint_text->set_text(p_hint); + hint_text->show(); + hint_text_timer->start(); +} + +void VisualScriptEditor::_hide_timer() { + + hint_text->hide(); +} + +void VisualScriptEditor::_node_filter_changed(const String& p_text) { + + _update_available_nodes(); +} + +void VisualScriptEditor::_notification(int p_what) { + + if (p_what==NOTIFICATION_READY) { + node_filter_icon->set_texture(Control::get_icon("Zoom","EditorIcons")); + } +} + +void VisualScriptEditor::_graph_ofs_changed(const Vector2& p_ofs) { + + if (updating_graph) + return; + + updating_graph=true; + + if (script->has_function(edited_func)) { + script->set_function_scroll(edited_func,graph->get_scroll_ofs()/EDSCALE); + script->set_edited(true); + } + updating_graph=false; +} + +void VisualScriptEditor::_comment_node_resized(const Vector2& p_new_size,int p_node) { + + if (updating_graph) + return; + + Ref<VisualScriptComment> vsc = script->get_node(edited_func,p_node); + if (vsc.is_null()) + return; + + Node *node = graph->get_node(itos(p_node)); + if (!node) + return; + GraphNode *gn = node->cast_to<GraphNode>(); + if (!gn) + return; + + updating_graph=true; + + graph->set_block_minimum_size_adjust(true); //faster resize + + undo_redo->create_action("Resize Comment",UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsc.ptr(),"set_size",p_new_size/EDSCALE); + undo_redo->add_undo_method(vsc.ptr(),"set_size",vsc->get_size()); + undo_redo->commit_action(); + + gn->set_custom_minimum_size(p_new_size); //for this time since graph update is blocked + gn->set_size(Size2(1,1)); + graph->set_block_minimum_size_adjust(false); + updating_graph=false; + + +} + +void VisualScriptEditor::_menu_option(int p_what) { + + switch(p_what) { + case EDIT_DELETE_NODES: { + _on_nodes_delete(); + } break; + case EDIT_TOGGLE_BREAKPOINT: { + + List<String> reselect; + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected()) { + int id = String(gn->get_name()).to_int(); + Ref<VisualScriptNode> vsn = script->get_node(edited_func,id); + if (vsn.is_valid()) { + vsn->set_breakpoint(!vsn->is_breakpoint()); + reselect.push_back(gn->get_name()); + } + } + } + } + + _update_graph(); + + for(List<String>::Element *E=reselect.front();E;E=E->next()) { + GraphNode *gn = graph->get_node(E->get())->cast_to<GraphNode>(); + gn->set_selected(true); + } + + } break; + case EDIT_FIND_NODE_TYPE: { + //popup disappearing grabs focus to owner, so use call deferred + node_filter->call_deferred("grab_focus"); + node_filter->call_deferred("select_all"); + } break; + case EDIT_COPY_NODES: + case EDIT_CUT_NODES: { + + if (!script->has_function(edited_func)) + break; + + clipboard->nodes.clear(); + clipboard->data_connections.clear(); + clipboard->sequence_connections.clear(); + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + if (gn->is_selected()) { + + int id = String(gn->get_name()).to_int(); + Ref<VisualScriptNode> node = script->get_node(edited_func,id); + if (node->cast_to<VisualScriptFunction>()) { + EditorNode::get_singleton()->show_warning("Can't copy the function node."); + return; + } + if (node.is_valid()) { + clipboard->nodes[id]=node->duplicate(); + clipboard->nodes_positions[id]=script->get_node_pos(edited_func,id); + } + + } + } + } + + if (clipboard->nodes.empty()) + break; + + List<VisualScript::SequenceConnection> sequence_connections; + + script->get_sequence_connection_list(edited_func,&sequence_connections); + + for (List<VisualScript::SequenceConnection>::Element *E=sequence_connections.front();E;E=E->next()) { + + if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { + + clipboard->sequence_connections.insert(E->get()); + } + } + + List<VisualScript::DataConnection> data_connections; + + script->get_data_connection_list(edited_func,&data_connections); + + for (List<VisualScript::DataConnection>::Element *E=data_connections.front();E;E=E->next()) { + + if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { + + clipboard->data_connections.insert(E->get()); + } + } + + if (p_what==EDIT_CUT_NODES) { + _on_nodes_delete(); // oh yeah, also delete on cut + } + + + } break; + case EDIT_PASTE_NODES: { + if (!script->has_function(edited_func)) + break; + + if (clipboard->nodes.empty()) { + EditorNode::get_singleton()->show_warning("Clipboard is empty!"); + break; + } + + Map<int,int> remap; + + undo_redo->create_action("Paste VisualScript Nodes"); + int idc=script->get_available_id()+1; + + Set<int> to_select; + + Set<Vector2> existing_positions; + + { + List<int> nodes; + script->get_node_list(edited_func,&nodes); + for (List<int>::Element *E=nodes.front();E;E=E->next()) { + Vector2 pos = script->get_node_pos(edited_func,E->get()).snapped(Vector2(2,2)); + existing_positions.insert(pos); + } + } + + for (Map<int,Ref<VisualScriptNode> >::Element *E=clipboard->nodes.front();E;E=E->next()) { + + + Ref<VisualScriptNode> node = E->get()->duplicate(); + + int new_id = idc++; + to_select.insert(new_id); + + remap[E->key()]=new_id; + + Vector2 paste_pos = clipboard->nodes_positions[E->key()]; + + while(existing_positions.has(paste_pos.snapped(Vector2(2,2)))) { + paste_pos+=Vector2(20,20)*EDSCALE; + } + + + undo_redo->add_do_method(script.ptr(),"add_node",edited_func,new_id,node,paste_pos); + undo_redo->add_undo_method(script.ptr(),"remove_node",edited_func,new_id); + + } + + for (Set<VisualScript::SequenceConnection>::Element *E=clipboard->sequence_connections.front();E;E=E->next()) { + + + undo_redo->add_do_method(script.ptr(),"sequence_connect",edited_func,remap[E->get().from_node],E->get().from_output,remap[E->get().to_node]); + undo_redo->add_undo_method(script.ptr(),"sequence_disconnect",edited_func,remap[E->get().from_node],E->get().from_output,remap[E->get().to_node]); + + } + + for (Set<VisualScript::DataConnection>::Element *E=clipboard->data_connections.front();E;E=E->next()) { + + + undo_redo->add_do_method(script.ptr(),"data_connect",edited_func,remap[E->get().from_node],E->get().from_port,remap[E->get().to_node],E->get().to_port); + undo_redo->add_undo_method(script.ptr(),"data_disconnect",edited_func,remap[E->get().from_node],E->get().from_port,remap[E->get().to_node],E->get().to_port); + + } + + undo_redo->add_do_method(this,"_update_graph"); + undo_redo->add_undo_method(this,"_update_graph"); + + undo_redo->commit_action(); + + for(int i=0;i<graph->get_child_count();i++) { + GraphNode *gn = graph->get_child(i)->cast_to<GraphNode>(); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(to_select.has(id)); + + } + } + } break; + + + } +} + +void VisualScriptEditor::_bind_methods() { + + ClassDB::bind_method("_member_button",&VisualScriptEditor::_member_button); + ClassDB::bind_method("_member_edited",&VisualScriptEditor::_member_edited); + ClassDB::bind_method("_member_selected",&VisualScriptEditor::_member_selected); + ClassDB::bind_method("_update_members",&VisualScriptEditor::_update_members); + ClassDB::bind_method("_change_base_type",&VisualScriptEditor::_change_base_type); + ClassDB::bind_method("_change_base_type_callback",&VisualScriptEditor::_change_base_type_callback); + ClassDB::bind_method("_override_pressed",&VisualScriptEditor::_override_pressed); + ClassDB::bind_method("_node_selected",&VisualScriptEditor::_node_selected); + ClassDB::bind_method("_node_moved",&VisualScriptEditor::_node_moved); + ClassDB::bind_method("_move_node",&VisualScriptEditor::_move_node); + ClassDB::bind_method("_begin_node_move",&VisualScriptEditor::_begin_node_move); + ClassDB::bind_method("_end_node_move",&VisualScriptEditor::_end_node_move); + ClassDB::bind_method("_remove_node",&VisualScriptEditor::_remove_node); + ClassDB::bind_method("_update_graph",&VisualScriptEditor::_update_graph,DEFVAL(-1)); + ClassDB::bind_method("_node_ports_changed",&VisualScriptEditor::_node_ports_changed); + ClassDB::bind_method("_available_node_doubleclicked",&VisualScriptEditor::_available_node_doubleclicked); + ClassDB::bind_method("_default_value_edited",&VisualScriptEditor::_default_value_edited); + ClassDB::bind_method("_default_value_changed",&VisualScriptEditor::_default_value_changed); + ClassDB::bind_method("_menu_option",&VisualScriptEditor::_menu_option); + ClassDB::bind_method("_graph_ofs_changed",&VisualScriptEditor::_graph_ofs_changed); + ClassDB::bind_method("_center_on_node",&VisualScriptEditor::_center_on_node); + ClassDB::bind_method("_comment_node_resized",&VisualScriptEditor::_comment_node_resized); + ClassDB::bind_method("_button_resource_previewed",&VisualScriptEditor::_button_resource_previewed); + ClassDB::bind_method("_port_action_menu",&VisualScriptEditor::_port_action_menu); + ClassDB::bind_method("_selected_connect_node_method_or_setget",&VisualScriptEditor::_selected_connect_node_method_or_setget); + ClassDB::bind_method("_expression_text_changed",&VisualScriptEditor::_expression_text_changed); + + + + + ClassDB::bind_method("get_drag_data_fw",&VisualScriptEditor::get_drag_data_fw); + ClassDB::bind_method("can_drop_data_fw",&VisualScriptEditor::can_drop_data_fw); + ClassDB::bind_method("drop_data_fw",&VisualScriptEditor::drop_data_fw); + + ClassDB::bind_method("_input",&VisualScriptEditor::_input); + ClassDB::bind_method("_on_nodes_delete",&VisualScriptEditor::_on_nodes_delete); + ClassDB::bind_method("_on_nodes_duplicate",&VisualScriptEditor::_on_nodes_duplicate); + + ClassDB::bind_method("_hide_timer",&VisualScriptEditor::_hide_timer); + + ClassDB::bind_method("_graph_connected",&VisualScriptEditor::_graph_connected); + ClassDB::bind_method("_graph_disconnected",&VisualScriptEditor::_graph_disconnected); + ClassDB::bind_method("_graph_connect_to_empty",&VisualScriptEditor::_graph_connect_to_empty); + + ClassDB::bind_method("_update_graph_connections",&VisualScriptEditor::_update_graph_connections); + ClassDB::bind_method("_node_filter_changed",&VisualScriptEditor::_node_filter_changed); + + ClassDB::bind_method("_selected_method",&VisualScriptEditor::_selected_method); + ClassDB::bind_method("_draw_color_over_button",&VisualScriptEditor::_draw_color_over_button); + + + + +} + + + +VisualScriptEditor::VisualScriptEditor() { + + if (!clipboard) { + clipboard = memnew( Clipboard ); + } + updating_graph=false; + + edit_menu = memnew( MenuButton ); + edit_menu->set_text(TTR("Edit")); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/delete_selected"), EDIT_DELETE_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/toggle_breakpoint"), EDIT_TOGGLE_BREAKPOINT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/find_node_type"), EDIT_FIND_NODE_TYPE); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/copy_nodes"), EDIT_COPY_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/cut_nodes"), EDIT_CUT_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/paste_nodes"), EDIT_PASTE_NODES); + + edit_menu->get_popup()->connect("id_pressed",this,"_menu_option"); + + main_hsplit = memnew( HSplitContainer ); + add_child(main_hsplit); + main_hsplit->set_area_as_parent_rect(); + + left_vsplit = memnew( VSplitContainer ); + main_hsplit->add_child(left_vsplit); + + VBoxContainer *left_vb = memnew( VBoxContainer ); + left_vsplit->add_child(left_vb); + left_vb->set_v_size_flags(SIZE_EXPAND_FILL); + left_vb->set_custom_minimum_size(Size2(230,1)*EDSCALE); + + base_type_select = memnew( Button ); + left_vb->add_margin_child(TTR("Base Type:"),base_type_select); + base_type_select->connect("pressed",this,"_change_base_type"); + + members = memnew( Tree ); + left_vb->add_margin_child(TTR("Members:"),members,true); + members->set_hide_root(true); + members->connect("button_pressed",this,"_member_button"); + members->connect("item_edited",this,"_member_edited"); + members->connect("cell_selected",this,"_member_selected",varray(),CONNECT_DEFERRED); + members->set_single_select_cell_editing_only_when_already_selected(true); + members->set_hide_folding(true); + members->set_drag_forwarding(this); + + + VBoxContainer *left_vb2 = memnew( VBoxContainer ); + left_vsplit->add_child(left_vb2); + left_vb2->set_v_size_flags(SIZE_EXPAND_FILL); + + + VBoxContainer *vbc_nodes = memnew( VBoxContainer ); + HBoxContainer *hbc_nodes = memnew( HBoxContainer ); + node_filter = memnew (LineEdit); + node_filter->connect("text_changed",this,"_node_filter_changed"); + hbc_nodes->add_child(node_filter); + node_filter->set_h_size_flags(SIZE_EXPAND_FILL); + node_filter_icon = memnew( TextureRect ); + node_filter_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc_nodes->add_child(node_filter_icon); + vbc_nodes->add_child(hbc_nodes); + + nodes = memnew( Tree ); + vbc_nodes->add_child(nodes); + nodes->set_v_size_flags(SIZE_EXPAND_FILL); + + left_vb2->add_margin_child(TTR("Available Nodes:"),vbc_nodes,true); + + nodes->set_hide_root(true); + nodes->connect("item_activated",this,"_available_node_doubleclicked"); + nodes->set_drag_forwarding(this); + + graph = memnew( GraphEdit ); + main_hsplit->add_child(graph); + graph->set_h_size_flags(SIZE_EXPAND_FILL); + graph->connect("node_selected",this,"_node_selected"); + graph->connect("_begin_node_move",this,"_begin_node_move"); + graph->connect("_end_node_move",this,"_end_node_move"); + graph->connect("delete_nodes_request",this,"_on_nodes_delete"); + graph->connect("duplicate_nodes_request",this,"_on_nodes_duplicate"); + graph->set_drag_forwarding(this); + graph->hide(); + graph->connect("scroll_offset_changed",this,"_graph_ofs_changed"); + + select_func_text = memnew( Label ); + select_func_text->set_text(TTR("Select or create a function to edit graph")); + select_func_text->set_align(Label::ALIGN_CENTER); + select_func_text->set_valign(Label::VALIGN_CENTER); + select_func_text->set_h_size_flags(SIZE_EXPAND_FILL); + main_hsplit->add_child(select_func_text); + + + hint_text = memnew( Label ); + hint_text->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,100); + hint_text->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0); + hint_text->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0); + hint_text->set_align(Label::ALIGN_CENTER); + hint_text->set_valign(Label::VALIGN_CENTER); + graph->add_child(hint_text); + + hint_text_timer = memnew( Timer ); + hint_text_timer->set_wait_time(4); + hint_text_timer->connect("timeout",this,"_hide_timer"); + add_child(hint_text_timer); + + //allowed casts (connections) + for(int i=0;i<Variant::VARIANT_MAX;i++) { + graph->add_valid_connection_type(Variant::NIL,i); + graph->add_valid_connection_type(i,Variant::NIL); + for(int j=0;j<Variant::VARIANT_MAX;j++) { + if (Variant::can_convert(Variant::Type(i),Variant::Type(j))) { + graph->add_valid_connection_type(i,j); + } + } + + graph->add_valid_right_disconnect_type(i); + } + + graph->add_valid_left_disconnect_type(TYPE_SEQUENCE); + + graph->connect("connection_request",this,"_graph_connected"); + graph->connect("disconnection_request",this,"_graph_disconnected"); + graph->connect("connection_to_empty",this,"_graph_connect_to_empty"); + + edit_signal_dialog = memnew( AcceptDialog ); + edit_signal_dialog->get_ok()->set_text(TTR("Close")); + add_child(edit_signal_dialog); + edit_signal_dialog->set_title(TTR("Edit Signal Arguments:")); + + signal_editor = memnew( VisualScriptEditorSignalEdit ); + edit_signal_edit = memnew( PropertyEditor ); + edit_signal_edit->hide_top_label(); + edit_signal_dialog->add_child(edit_signal_edit); + + edit_signal_edit->edit(signal_editor); + + edit_variable_dialog = memnew( AcceptDialog ); + edit_variable_dialog->get_ok()->set_text(TTR("Close")); + add_child(edit_variable_dialog); + edit_variable_dialog->set_title(TTR("Edit Variable:")); + + variable_editor = memnew( VisualScriptEditorVariableEdit ); + edit_variable_edit = memnew( PropertyEditor ); + edit_variable_edit->hide_top_label(); + edit_variable_dialog->add_child(edit_variable_edit); + + edit_variable_edit->edit(variable_editor); + + select_base_type=memnew(CreateDialog); + select_base_type->set_base_type("Object"); //anything goes + select_base_type->connect("create",this,"_change_base_type_callback"); + select_base_type->get_ok()->set_text(TTR("Change")); + add_child(select_base_type); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + new_function_menu = memnew( PopupMenu ); + new_function_menu->connect("id_pressed",this,"_override_pressed"); + add_child(new_function_menu); + updating_members=false; + + set_process_input(true); //for revert on drag + set_process_unhandled_input(true); //for revert on drag + + default_value_edit= memnew( CustomPropertyEditor); + add_child(default_value_edit); + default_value_edit->connect("variant_changed",this,"_default_value_changed"); + + method_select = memnew( PropertySelector ); + add_child(method_select); + method_select->connect("selected",this,"_selected_method"); + error_line=-1; + + new_connect_node_select = memnew( PropertySelector ); + add_child(new_connect_node_select); + new_connect_node_select->connect("selected",this,"_selected_connect_node_method_or_setget"); + + port_action_popup = memnew( PopupMenu ); + add_child(port_action_popup); + port_action_popup->connect("id_pressed",this,"_port_action_menu"); + + +} + +VisualScriptEditor::~VisualScriptEditor() { + + undo_redo->clear_history(); //avoid crashes + memdelete(signal_editor); + memdelete(variable_editor); +} + +static ScriptEditorBase * create_editor(const Ref<Script>& p_script) { + + if (p_script->cast_to<VisualScript>()) { + return memnew( VisualScriptEditor ); + } + + return NULL; +} + + +VisualScriptEditor::Clipboard *VisualScriptEditor::clipboard=NULL; + +void VisualScriptEditor::free_clipboard() { + if (clipboard) + memdelete(clipboard); +} + +static void register_editor_callback() { + + ScriptEditor::register_create_script_editor_function(create_editor); + EditorSettings::get_singleton()->set("editors/visual_script/color_functions",Color(1,0.9,0.9)); + EditorSettings::get_singleton()->set("editors/visual_script/color_data",Color(0.9,1.0,0.9)); + EditorSettings::get_singleton()->set("editors/visual_script/color_operators",Color(0.9,0.9,1.0)); + EditorSettings::get_singleton()->set("editors/visual_script/color_flow_control",Color(1.0,1.0,0.8)); + EditorSettings::get_singleton()->set("editors/visual_script/color_custom",Color(0.8,1.0,1.0)); + EditorSettings::get_singleton()->set("editors/visual_script/color_constants",Color(1.0,0.8,1.0)); + + + ED_SHORTCUT("visual_script_editor/delete_selected", TTR("Delete Selected")); + ED_SHORTCUT("visual_script_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9); + ED_SHORTCUT("visual_script_editor/find_node_type", TTR("Find Node Type"), KEY_MASK_CMD+KEY_F); + ED_SHORTCUT("visual_script_editor/copy_nodes", TTR("Copy Nodes"), KEY_MASK_CMD+KEY_C); + ED_SHORTCUT("visual_script_editor/cut_nodes", TTR("Cut Nodes"), KEY_MASK_CMD+KEY_X); + ED_SHORTCUT("visual_script_editor/paste_nodes", TTR("Paste Nodes"), KEY_MASK_CMD+KEY_V); + +} + +void VisualScriptEditor::register_editor() { + + //too early to register stuff here, request a callback + EditorNode::add_plugin_init_callback(register_editor_callback); + + + +} + +#endif diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h new file mode 100644 index 000000000..1dc62b3e6 --- /dev/null +++ b/modules/visual_script/visual_script_editor.h @@ -0,0 +1,244 @@ +#ifndef VisualSCRIPT_EDITOR_H +#define VisualSCRIPT_EDITOR_H + +#include "tools/editor/plugins/script_editor_plugin.h" +#include "visual_script.h" +#include "tools/editor/property_editor.h" +#include "scene/gui/graph_edit.h" +#include "tools/editor/create_dialog.h" +#include "tools/editor/property_selector.h" +class VisualScriptEditorSignalEdit; +class VisualScriptEditorVariableEdit; + + +#ifdef TOOLS_ENABLED + + +class VisualScriptEditor : public ScriptEditorBase { + GDCLASS(VisualScriptEditor,ScriptEditorBase) + + enum { + TYPE_SEQUENCE=1000, + INDEX_BASE_SEQUENCE=1024 + + + }; + + enum { + EDIT_DELETE_NODES, + EDIT_TOGGLE_BREAKPOINT, + EDIT_FIND_NODE_TYPE, + EDIT_COPY_NODES, + EDIT_CUT_NODES, + EDIT_PASTE_NODES, + }; + + enum PortAction { + + CREATE_CALL, + CREATE_SET, + CREATE_GET, + CREATE_COND, + CREATE_SEQUENCE, + CREATE_SWITCH, + CREATE_ITERATOR, + CREATE_WHILE, + CREATE_RETURN, + }; + + MenuButton *edit_menu; + + Ref<VisualScript> script; + + Button *base_type_select; + + HSplitContainer *main_hsplit; + VSplitContainer *left_vsplit; + + GraphEdit *graph; + + LineEdit *node_filter; + TextureRect *node_filter_icon; + + VisualScriptEditorSignalEdit *signal_editor; + + AcceptDialog *edit_signal_dialog; + PropertyEditor *edit_signal_edit; + + PropertySelector *method_select; + PropertySelector *new_connect_node_select; + + VisualScriptEditorVariableEdit *variable_editor; + + AcceptDialog *edit_variable_dialog; + PropertyEditor *edit_variable_edit; + + CustomPropertyEditor *default_value_edit; + + UndoRedo *undo_redo; + + Tree *members; + Tree *nodes; + + Label *hint_text; + Timer *hint_text_timer; + + Label *select_func_text; + + bool updating_graph; + + void _show_hint(const String& p_hint); + void _hide_timer(); + + CreateDialog *select_base_type; + + struct VirtualInMenu { + String name; + Variant::Type ret; + bool ret_variant; + Vector< Pair<Variant::Type,String> > args; + }; + + Map<int,VirtualInMenu> virtuals_in_menu; + + PopupMenu *new_function_menu; + + + StringName edited_func; + + void _update_graph_connections(); + void _update_graph(int p_only_id=-1); + + bool updating_members; + + void _update_members(); + + StringName selected; + + String _validate_name(const String& p_name) const; + + struct Clipboard { + + Map<int,Ref<VisualScriptNode> > nodes; + Map<int,Vector2 > nodes_positions; + + Set<VisualScript::SequenceConnection> sequence_connections; + Set<VisualScript::DataConnection> data_connections; + }; + + static Clipboard *clipboard; + + PopupMenu *port_action_popup; + + PortAction port_action; + int port_action_node; + int port_action_output; + Vector2 port_action_pos; + int port_action_new_node; + void _port_action_menu(int p_option); + void _selected_connect_node_method_or_setget(const String& p_text); + + + int error_line; + + void _node_selected(Node* p_node); + void _center_on_node(int p_id); + + void _node_filter_changed(const String& p_text); + void _change_base_type_callback(); + void _change_base_type(); + void _member_selected(); + void _member_edited(); + void _override_pressed(int p_id); + + void _begin_node_move(); + void _end_node_move(); + void _move_node(String func,int p_id,const Vector2& p_to); + + void _node_moved(Vector2 p_from,Vector2 p_to, int p_id); + void _remove_node(int p_id); + void _graph_connected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot); + void _graph_disconnected(const String& p_from,int p_from_slot,const String& p_to,int p_to_slot); + void _graph_connect_to_empty(const String& p_from,int p_from_slot,const Vector2& p_release_pos); + + void _node_ports_changed(const String& p_func,int p_id); + void _available_node_doubleclicked(); + + void _update_available_nodes(); + + void _member_button(Object *p_item, int p_column, int p_button); + + void _expression_text_changed(const String& p_text,int p_id); + + + String revert_on_drag; + + void _input(const InputEvent& p_event); + void _on_nodes_delete(); + void _on_nodes_duplicate(); + + Variant get_drag_data_fw(const Point2& p_point,Control* p_from); + bool can_drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from) const; + void drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from); + + + int editing_id; + int editing_input; + + void _default_value_changed(); + void _default_value_edited(Node * p_button,int p_id,int p_input_port); + + void _menu_option(int p_what); + + void _graph_ofs_changed(const Vector2& p_ofs); + void _comment_node_resized(const Vector2& p_new_size,int p_node); + + int selecting_method_id; + void _selected_method(const String& p_method); + + void _draw_color_over_button(Object* obj,Color p_color); + void _button_resource_previewed(const String& p_path,const Ref<Texture>& p_preview,Variant p_ud); + + VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node,int p_port_action_output,Set<int> &visited_nodes); + + +protected: + + void _notification(int p_what); + static void _bind_methods(); +public: + + virtual void apply_code(); + virtual Ref<Script> get_edited_script() const; + virtual Vector<String> get_functions(); + virtual void set_edited_script(const Ref<Script>& p_script); + virtual void reload_text(); + virtual String get_name(); + virtual Ref<Texture> get_icon(); + virtual bool is_unsaved(); + virtual Variant get_edit_state(); + virtual void set_edit_state(const Variant& p_state); + virtual void goto_line(int p_line,bool p_with_error=false); + virtual void trim_trailing_whitespace(); + virtual void ensure_focus(); + virtual void tag_saved_version(); + virtual void reload(bool p_soft); + virtual void get_breakpoints(List<int> *p_breakpoints); + virtual bool goto_method(const String& p_method); + virtual void add_callback(const String& p_function,PoolStringArray p_args); + virtual void update_settings(); + virtual void set_debugger_active(bool p_active); + virtual void set_tooltip_request_func(String p_method,Object* p_obj); + virtual Control *get_edit_menu(); + virtual bool can_lose_focus_on_node_selection() { return false; } + + static void register_editor(); + + static void free_clipboard(); + + VisualScriptEditor(); + ~VisualScriptEditor(); +}; +#endif + +#endif // VisualSCRIPT_EDITOR_H diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp new file mode 100644 index 000000000..cc3b5f217 --- /dev/null +++ b/modules/visual_script/visual_script_expression.cpp @@ -0,0 +1,1523 @@ +#include "visual_script_expression.h" + + +bool VisualScriptExpression::_set(const StringName& p_name, const Variant& p_value) { + + if (String(p_name)=="expression") { + expression=p_value; + expression_dirty=true; + ports_changed_notify(); + return true; + } + + if (String(p_name)=="out_type") { + output_type=Variant::Type(int(p_value)); + expression_dirty=true; + ports_changed_notify(); + return true; + } + if (String(p_name)=="sequenced") { + sequenced=p_value; + ports_changed_notify(); + return true; + } + + if (String(p_name)=="input_count") { + + int from=inputs.size(); + inputs.resize(int(p_value)); + for(int i=from;i<inputs.size();i++) { + inputs[i].name=String::chr('a'+i); + if (from==0) { + inputs[i].type=output_type; + } else { + inputs[i].type=inputs[from-1].type; + } + } + expression_dirty=true; + ports_changed_notify(); + _change_notify(); + return true; + } + + if (String(p_name).begins_with("input/")) { + + int idx=String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,inputs.size(),false); + + String what=String(p_name).get_slice("/",2); + + if (what=="type") { + + inputs[idx].type=Variant::Type(int(p_value)); + } else if (what=="name") { + + inputs[idx].name=p_value; + } else { + return false; + } + + expression_dirty=true; + ports_changed_notify(); + return true; + } + + + return false; + +} + +bool VisualScriptExpression::_get(const StringName& p_name,Variant &r_ret) const { + + if (String(p_name)=="expression") { + r_ret=expression; + return true; + } + + if (String(p_name)=="out_type") { + r_ret=output_type; + return true; + } + + if (String(p_name)=="sequenced") { + r_ret=sequenced; + return true; + } + + if (String(p_name)=="input_count") { + r_ret=inputs.size(); + return true; + } + + if (String(p_name).begins_with("input/")) { + + int idx=String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,inputs.size(),false); + + String what=String(p_name).get_slice("/",2); + + if (what=="type") { + + r_ret=inputs[idx].type; + } else if (what=="name") { + + r_ret=inputs[idx].name; + } else { + return false; + } + + return true; + } + + + return false; +} +void VisualScriptExpression::_get_property_list( List<PropertyInfo> *p_list) const { + + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + p_list->push_back(PropertyInfo(Variant::STRING,"expression",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::INT,"out_type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::INT,"input_count",PROPERTY_HINT_RANGE,"0,64,1")); + p_list->push_back(PropertyInfo(Variant::BOOL,"sequenced")); + + for(int i=0;i<inputs.size();i++) { + + p_list->push_back(PropertyInfo(Variant::INT,"input/"+itos(i)+"/type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::STRING,"input/"+itos(i)+"/name")); + } +} + +int VisualScriptExpression::get_output_sequence_port_count() const { + + return sequenced?1:0; +} +bool VisualScriptExpression::has_input_sequence_port() const{ + + return sequenced; +} + + +String VisualScriptExpression::get_output_sequence_port_text(int p_port) const{ + + return String(); +} + + +int VisualScriptExpression::get_input_value_port_count() const{ + + return inputs.size(); + +} +int VisualScriptExpression::get_output_value_port_count() const{ + + return 1; +} + + +PropertyInfo VisualScriptExpression::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(inputs[p_idx].type,inputs[p_idx].name); +} +PropertyInfo VisualScriptExpression::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(output_type,"result"); +} + +String VisualScriptExpression::get_caption() const{ + + return "Expression"; +} +String VisualScriptExpression::get_text() const{ + + return expression; +} + + +Error VisualScriptExpression::_get_token(Token& r_token) { + + while (true) { +#define GET_CHAR() (str_ofs>=expression.length()?0:expression[str_ofs++]) + + CharType cchar = GET_CHAR(); + if (cchar==0) { + r_token.type=TK_EOF; + return OK; + } + + + switch(cchar) { + + case 0: { + r_token.type=TK_EOF; + return OK; + } break; + case '{': { + + r_token.type=TK_CURLY_BRACKET_OPEN; + return OK; + }; + case '}': { + + r_token.type=TK_CURLY_BRACKET_CLOSE; + return OK; + }; + case '[': { + + r_token.type=TK_BRACKET_OPEN; + return OK; + }; + case ']': { + + r_token.type=TK_BRACKET_CLOSE; + return OK; + }; + case '(': { + + r_token.type=TK_PARENTHESIS_OPEN; + return OK; + }; + case ')': { + + r_token.type=TK_PARENTHESIS_CLOSE; + return OK; + }; + case ',': { + + r_token.type=TK_COMMA; + return OK; + }; + case ':': { + + r_token.type=TK_COLON; + return OK; + }; + case '.': { + + r_token.type=TK_PERIOD; + return OK; + }; + case '=': { + + cchar=GET_CHAR(); + if (cchar=='=') { + r_token.type=TK_OP_EQUAL; + } else { + _set_error("Expected '='"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + return OK; + }; + case '!': { + + if (expression[str_ofs]=='=') { + r_token.type=TK_OP_NOT_EQUAL; + str_ofs++; + } else { + r_token.type=TK_OP_NOT; + } + return OK; + }; + case '>': { + + if (expression[str_ofs]=='=') { + r_token.type=TK_OP_GREATER_EQUAL; + str_ofs++; + } else if (expression[str_ofs]=='>') { + r_token.type=TK_OP_SHIFT_RIGHT; + str_ofs++; + } else { + r_token.type=TK_OP_GREATER; + } + return OK; + }; + case '<': { + + if (expression[str_ofs]=='=') { + r_token.type=TK_OP_LESS_EQUAL; + str_ofs++; + } else if (expression[str_ofs]=='<') { + r_token.type=TK_OP_SHIFT_LEFT; + str_ofs++; + } else { + r_token.type=TK_OP_LESS; + } + return OK; + }; + case '+': { + r_token.type=TK_OP_ADD; + return OK; + }; + case '-': { + r_token.type=TK_OP_SUB; + return OK; + }; + case '/': { + r_token.type=TK_OP_DIV; + return OK; + }; + case '*': { + r_token.type=TK_OP_MUL; + return OK; + }; + case '%': { + r_token.type=TK_OP_MOD; + return OK; + }; + case '&': { + + if (expression[str_ofs]=='&') { + r_token.type=TK_OP_AND; + str_ofs++; + } else { + r_token.type=TK_OP_BIT_AND; + } + return OK; + }; + case '|': { + + if (expression[str_ofs]=='|') { + r_token.type=TK_OP_OR; + str_ofs++; + } else { + r_token.type=TK_OP_BIT_OR; + } + return OK; + }; + case '^': { + + r_token.type=TK_OP_BIT_XOR; + + return OK; + }; + case '~': { + + r_token.type=TK_OP_BIT_INVERT; + + return OK; + }; + case '"': { + + + String str; + while(true) { + + CharType ch=GET_CHAR(); + + if (ch==0) { + _set_error("Unterminated String"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } else if (ch=='"') { + break; + } else if (ch=='\\') { + //escaped characters... + + CharType next = GET_CHAR(); + if (next==0) { + _set_error("Unterminated String"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + CharType res=0; + + switch(next) { + + case 'b': res=8; break; + case 't': res=9; break; + case 'n': res=10; break; + case 'f': res=12; break; + case 'r': res=13; break; + case 'u': { + //hexnumbarh - oct is deprecated + + + for(int j=0;j<4;j++) { + CharType c = GET_CHAR(); + + if (c==0) { + _set_error("Unterminated String"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + if (!((c>='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F'))) { + + _set_error("Malformed hex constant in string"); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + CharType v; + if (c>='0' && c<='9') { + v=c-'0'; + } else if (c>='a' && c<='f') { + v=c-'a'; + v+=10; + } else if (c>='A' && c<='F') { + v=c-'A'; + v+=10; + } else { + ERR_PRINT("BUG"); + v=0; + } + + res<<=4; + res|=v; + + + } + + + + } break; + //case '\"': res='\"'; break; + //case '\\': res='\\'; break; + //case '/': res='/'; break; + default: { + res = next; + //r_err_str="Invalid escape sequence"; + //return ERR_PARSE_ERROR; + } break; + } + + str+=res; + + } else { + str+=ch; + } + } + + r_token.type=TK_CONSTANT; + r_token.value=str; + return OK; + + } break; + default: { + + if (cchar<=32) { + break; + } + + if (cchar=='-' || (cchar>='0' && cchar<='9')) { + //a number + + + String num; +#define READING_SIGN 0 +#define READING_INT 1 +#define READING_DEC 2 +#define READING_EXP 3 +#define READING_DONE 4 + int reading=READING_INT; + + if (cchar=='-') { + num+='-'; + cchar=GET_CHAR(); + + } + + + + CharType c = cchar; + bool exp_sign=false; + bool exp_beg=false; + bool is_float=false; + + while(true) { + + switch(reading) { + case READING_INT: { + + if (c>='0' && c<='9') { + //pass + } else if (c=='.') { + reading=READING_DEC; + is_float=true; + } else if (c=='e') { + reading=READING_EXP; + } else { + reading=READING_DONE; + } + + } break; + case READING_DEC: { + + if (c>='0' && c<='9') { + + } else if (c=='e') { + reading=READING_EXP; + + } else { + reading=READING_DONE; + } + + } break; + case READING_EXP: { + + if (c>='0' && c<='9') { + exp_beg=true; + + } else if ((c=='-' || c=='+') && !exp_sign && !exp_beg) { + if (c=='-') + is_float=true; + exp_sign=true; + + } else { + reading=READING_DONE; + } + } break; + } + + if (reading==READING_DONE) + break; + num+=String::chr(c); + c = GET_CHAR(); + + + } + + str_ofs--; + + r_token.type=TK_CONSTANT; + + if (is_float) + r_token.value=num.to_double(); + else + r_token.value=num.to_int(); + return OK; + + } else if ((cchar>='A' && cchar<='Z') || (cchar>='a' && cchar<='z') || cchar=='_') { + + String id; + bool first=true; + + while((cchar>='A' && cchar<='Z') || (cchar>='a' && cchar<='z') || cchar=='_' || (!first && cchar>='0' && cchar<='9')) { + + id+=String::chr(cchar); + cchar=GET_CHAR(); + first=false; + } + + str_ofs--; //go back one + + if (id=="in") { + r_token.type=TK_OP_IN; + } else if (id=="null") { + r_token.type=TK_CONSTANT; + r_token.value=Variant(); + } else if (id=="true") { + r_token.type=TK_CONSTANT; + r_token.value=true; + } else if (id=="false") { + r_token.type=TK_CONSTANT; + r_token.value=false; + } else if (id=="PI") { + r_token.type=TK_CONSTANT; + r_token.value=Math_PI; + } else if (id=="not") { + r_token.type=TK_OP_NOT; + } else if (id=="or") { + r_token.type=TK_OP_OR; + } else if (id=="and") { + r_token.type=TK_OP_AND; + } else if (id=="self") { + r_token.type=TK_SELF; + } else { + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (id==Variant::get_type_name(Variant::Type(i))) { + r_token.type=TK_BASIC_TYPE; + r_token.value=i; + return OK; + break; + } + } + + VisualScriptBuiltinFunc::BuiltinFunc bifunc = VisualScriptBuiltinFunc::find_function(id); + if (bifunc!=VisualScriptBuiltinFunc::FUNC_MAX) { + r_token.type=TK_BUILTIN_FUNC; + r_token.value=bifunc; + return OK; + } + + r_token.type=TK_IDENTIFIER; + r_token.value=id; + } + + return OK; + } else { + _set_error("Unexpected character."); + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; + } + } + } + } + + r_token.type=TK_ERROR; + return ERR_PARSE_ERROR; +} + +const char* VisualScriptExpression::token_name[TK_MAX]={ +"CURLY BRACKET OPEN", +"CURLY BRACKET CLOSE", +"BRACKET OPEN", +"BRACKET CLOSE", +"PARENTHESIS OPEN", +"PARENTHESIS CLOSE", +"IDENTIFIER", +"BUILTIN FUNC", +"SELF", +"CONSTANT", +"BASIC TYPE", +"COLON", +"COMMA", +"PERIOD", +"OP IN", +"OP EQUAL", +"OP NOT EQUAL", +"OP LESS", +"OP LESS EQUAL", +"OP GREATER", +"OP GREATER EQUAL", +"OP AND", +"OP OR", +"OP NOT", +"OP ADD", +"OP SUB", +"OP MUL", +"OP DIV", +"OP MOD", +"OP SHIFT LEFT", +"OP SHIFT RIGHT", +"OP BIT AND", +"OP BIT OR", +"OP BIT XOR", +"OP BIT INVERT", +"EOF", +"ERROR" +}; + +VisualScriptExpression::ENode* VisualScriptExpression::_parse_expression() { + + + Vector<Expression> expression; + + while(true) { + //keep appending stuff to expression + ENode*expr=NULL; + + Token tk; + _get_token(tk); + if (error_set) + return NULL; + + + + switch(tk.type) { + case TK_CURLY_BRACKET_OPEN: { + //a dictionary + DictionaryNode *dn = alloc_node<DictionaryNode>(); + + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_CURLY_BRACKET_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + dn->dict.push_back(expr); + + _get_token(tk); + if (tk.type!=TK_COLON) { + _set_error("Expected ':'"); + return NULL; + } + + expr=_parse_expression(); + if (!expr) + return NULL; + + dn->dict.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_CURLY_BRACKET_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or '}'"); + } + } + + expr=dn; + } break; + case TK_BRACKET_OPEN: { + //an array + + ArrayNode *an = alloc_node<ArrayNode>(); + + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_BRACKET_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + an->array.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_BRACKET_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ']'"); + } + } + + expr=an; + } break; + case TK_PARENTHESIS_OPEN: { + //a suexpression + ENode* e=_parse_expression(); + if (error_set) + return NULL; + _get_token(tk); + if (tk.type!=TK_PARENTHESIS_CLOSE) { + _set_error("Expected ')'"); + return NULL; + } + + expr=e; + + } break; + case TK_IDENTIFIER: { + + String what = tk.value; + int index=-1; + for(int i=0;i<inputs.size();i++) { + if (what==inputs[i].name) { + index=i; + break; + } + } + + if (index!=-1) { + InputNode *input = alloc_node<InputNode>(); + input->index=index; + expr=input; + } else { + _set_error("Invalid input identifier '"+what+"'. For script variables, use self (locals are for inputs)."+what); + return NULL; + } + } break; + case TK_SELF: { + + SelfNode *self = alloc_node<SelfNode>(); + expr=self; + } break; + case TK_CONSTANT: { + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->value=tk.value; + expr=constant; + } break; + case TK_BASIC_TYPE: { + //constructor.. + + Variant::Type bt = Variant::Type(int(tk.value)); + _get_token(tk); + if (tk.type!=TK_PARENTHESIS_OPEN) { + _set_error("Expected '('"); + return NULL; + } + + ConstructorNode *constructor = alloc_node<ConstructorNode>(); + constructor->data_type=bt; + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + + constructor->arguments.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_PARENTHESIS_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ')'"); + } + } + + expr=constructor; + + } break; + case TK_BUILTIN_FUNC: { + //builtin function + + Variant::Type bt = Variant::Type(int(tk.value)); + _get_token(tk); + if (tk.type!=TK_PARENTHESIS_OPEN) { + _set_error("Expected '('"); + return NULL; + } + + BuiltinFuncNode *bifunc = alloc_node<BuiltinFuncNode>(); + bifunc->func=VisualScriptBuiltinFunc::BuiltinFunc(int(tk.value)); + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + + bifunc->arguments.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_PARENTHESIS_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ')'"); + } + } + + int expected_args = VisualScriptBuiltinFunc::get_func_argument_count(bifunc->func); + if (bifunc->arguments.size() != expected_args) { + _set_error("Builtin func '"+VisualScriptBuiltinFunc::get_func_name(bifunc->func)+"' expects "+itos(expected_args)+" arguments."); + } + + expr=bifunc; + + } break; + case TK_OP_SUB: { + + Expression e; + e.is_op=true; + e.op=Variant::OP_NEGATE; + expression.push_back(e); + continue; + } break; + case TK_OP_NOT: { + + Expression e; + e.is_op=true; + e.op=Variant::OP_NOT; + expression.push_back(e); + continue; + } break; + + default: { + _set_error("Expected expression."); + return NULL; + } break; + + } + + //before going to operators, must check indexing! + + while(true) { + int cofs2=str_ofs; + _get_token(tk); + if (error_set) + return NULL; + + bool done=false; + + switch(tk.type) { + case TK_BRACKET_OPEN: { + //value indexing + + IndexNode *index = alloc_node<IndexNode>(); + index->base=expr; + + ENode* what=_parse_expression(); + if (!what) + return NULL; + + index->index=what; + + _get_token(tk); + if (tk.type!=TK_BRACKET_CLOSE) { + _set_error("Expected ']' at end of index."); + return NULL; + } + expr=index; + + } break; + case TK_PERIOD: { + //named indexing or function call + _get_token(tk); + if (tk.type!=TK_IDENTIFIER) { + _set_error("Expected identifier after '.'"); + return NULL; + } + + StringName identifier=tk.value; + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_OPEN) { + //function call + CallNode *func_call = alloc_node<CallNode>(); + func_call->method=identifier; + func_call->base=expr; + + while(true) { + + int cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_PARENTHESIS_CLOSE) { + break; + } + str_ofs=cofs; //revert + //parse an expression + ENode* expr=_parse_expression(); + if (!expr) + return NULL; + + func_call->arguments.push_back(expr); + + cofs=str_ofs; + _get_token(tk); + if (tk.type==TK_COMMA) { + //all good + } else if (tk.type==TK_PARENTHESIS_CLOSE) { + str_ofs=cofs; + } else { + _set_error("Expected ',' or ')'"); + } + } + + expr=func_call; + } else { + //named indexing + str_ofs=cofs; + + NamedIndexNode *index = alloc_node<NamedIndexNode>(); + index->base=expr; + index->name=identifier; + expr=index; + + } + + } break; + default: { + str_ofs=cofs2; + done=true; + } break; + } + + if (done) + break; + } + + //push expression + { + Expression e; + e.is_op=false; + e.node=expr; + expression.push_back(e); + } + + //ok finally look for an operator + + + int cofs=str_ofs; + _get_token(tk); + if (error_set) + return NULL; + + + Variant::Operator op = Variant::OP_MAX; + + switch(tk.type) { + case TK_OP_IN: op=Variant::OP_IN; break; + case TK_OP_EQUAL: op=Variant::OP_EQUAL; break; + case TK_OP_NOT_EQUAL: op=Variant::OP_NOT_EQUAL; break; + case TK_OP_LESS: op=Variant::OP_LESS; break; + case TK_OP_LESS_EQUAL: op=Variant::OP_LESS_EQUAL; break; + case TK_OP_GREATER: op=Variant::OP_GREATER; break; + case TK_OP_GREATER_EQUAL: op=Variant::OP_GREATER_EQUAL; break; + case TK_OP_AND: op=Variant::OP_AND; break; + case TK_OP_OR: op=Variant::OP_OR; break; + case TK_OP_NOT: op=Variant::OP_NOT; break; + case TK_OP_ADD: op=Variant::OP_ADD; break; + case TK_OP_SUB: op=Variant::OP_SUBSTRACT; break; + case TK_OP_MUL: op=Variant::OP_MULTIPLY; break; + case TK_OP_DIV: op=Variant::OP_DIVIDE; break; + case TK_OP_MOD: op=Variant::OP_MODULE; break; + case TK_OP_SHIFT_LEFT: op=Variant::OP_SHIFT_LEFT; break; + case TK_OP_SHIFT_RIGHT: op=Variant::OP_SHIFT_RIGHT; break; + case TK_OP_BIT_AND: op=Variant::OP_BIT_AND; break; + case TK_OP_BIT_OR: op=Variant::OP_BIT_OR; break; + case TK_OP_BIT_XOR: op=Variant::OP_BIT_XOR; break; + case TK_OP_BIT_INVERT: op=Variant::OP_BIT_NEGATE; break; + default: {}; + } + + if (op==Variant::OP_MAX) { //stop appending stuff + str_ofs=cofs; + break; + } + + //push operator and go on + { + Expression e; + e.is_op=true; + e.op=op; + expression.push_back(e); + } + } + + + /* Reduce the set set of expressions and place them in an operator tree, respecting precedence */ + + + while(expression.size()>1) { + + int next_op=-1; + int min_priority=0xFFFFF; + bool is_unary=false; + + for(int i=0;i<expression.size();i++) { + + + + if (!expression[i].is_op) { + + continue; + } + + int priority; + + bool unary=false; + + switch(expression[i].op) { + + + case Variant::OP_BIT_NEGATE: priority=0; unary=true; break; + case Variant::OP_NEGATE: priority=1; unary=true; break; + + case Variant::OP_MULTIPLY: priority=2; break; + case Variant::OP_DIVIDE: priority=2; break; + case Variant::OP_MODULE: priority=2; break; + + case Variant::OP_ADD: priority=3; break; + case Variant::OP_SUBSTRACT: priority=3; break; + + case Variant::OP_SHIFT_LEFT: priority=4; break; + case Variant::OP_SHIFT_RIGHT: priority=4; break; + + case Variant::OP_BIT_AND: priority=5; break; + case Variant::OP_BIT_XOR: priority=6; break; + case Variant::OP_BIT_OR: priority=7; break; + + case Variant::OP_LESS: priority=8; break; + case Variant::OP_LESS_EQUAL: priority=8; break; + case Variant::OP_GREATER: priority=8; break; + case Variant::OP_GREATER_EQUAL: priority=8; break; + + case Variant::OP_EQUAL: priority=8; break; + case Variant::OP_NOT_EQUAL: priority=8; break; + + case Variant::OP_IN: priority=10; break; + + case Variant::OP_NOT: priority=11; unary=true; break; + case Variant::OP_AND: priority=12; break; + case Variant::OP_OR: priority=13; break; + + + default: { + _set_error("Parser bug, invalid operator in expression: "+itos(expression[i].op)); + return NULL; + } + + } + + if (priority<min_priority) { + // < is used for left to right (default) + // <= is used for right to left + + next_op=i; + min_priority=priority; + is_unary=unary; + } + + } + + if (next_op==-1) { + + + _set_error("Yet another parser bug...."); + ERR_FAIL_COND_V(next_op==-1,NULL); + } + + + // OK! create operator.. + if (is_unary) { + + int expr_pos=next_op; + while(expression[expr_pos].is_op) { + + expr_pos++; + if (expr_pos==expression.size()) { + //can happen.. + _set_error("Unexpected end of expression.."); + return NULL; + } + } + + //consecutively do unary opeators + for(int i=expr_pos-1;i>=next_op;i--) { + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=expression[i].op; + op->nodes[0]=expression[i+1].node; + op->nodes[1]=NULL; + expression[i].is_op=false; + expression[i].node=op; + expression.remove(i+1); + } + + + } else { + + if (next_op <1 || next_op>=(expression.size()-1)) { + _set_error("Parser bug.."); + ERR_FAIL_V(NULL); + } + + OperatorNode *op = alloc_node<OperatorNode>(); + op->op=expression[next_op].op; + + if (expression[next_op-1].is_op) { + + _set_error("Parser bug.."); + ERR_FAIL_V(NULL); + } + + if (expression[next_op+1].is_op) { + // this is not invalid and can really appear + // but it becomes invalid anyway because no binary op + // can be followed by an unary op in a valid combination, + // due to how precedence works, unaries will always dissapear first + + _set_error("Unexpected two consecutive operators."); + return NULL; + } + + + op->nodes[0]=expression[next_op-1].node; //expression goes as left + op->nodes[1]=expression[next_op+1].node; //next expression goes as right + + //replace all 3 nodes by this operator and make it an expression + expression[next_op-1].node=op; + expression.remove(next_op); + expression.remove(next_op); + } + } + + return expression[0].node; +} + +bool VisualScriptExpression::_compile_expression() { + + if (!expression_dirty) + return error_set; + + if (nodes) { + memdelete(nodes); + nodes=NULL; + root=NULL; + + } + + error_str=String(); + error_set=false; + str_ofs=0; + + root=_parse_expression(); + + if (error_set) { + root=NULL; + if (nodes) { + memdelete(nodes); + } + nodes=NULL; + return true; + } + + expression_dirty=false; + return false; +} + + +class VisualScriptNodeInstanceExpression : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + VisualScriptExpression *expression; + + //virtual int get_working_memory_size() const { return 0; } + //execute by parsing the tree directly + virtual bool _execute(const Variant** p_inputs,VisualScriptExpression::ENode *p_node,Variant& r_ret,String& r_error_str,Variant::CallError &ce) { + + switch(p_node->type) { + case VisualScriptExpression::ENode::TYPE_INPUT: { + + const VisualScriptExpression::InputNode *in = static_cast<const VisualScriptExpression::InputNode*>(p_node); + r_ret=*p_inputs[in->index]; + } break; + case VisualScriptExpression::ENode::TYPE_CONSTANT: { + + const VisualScriptExpression::ConstantNode *c = static_cast<const VisualScriptExpression::ConstantNode*>(p_node); + r_ret=c->value; + + } break; + case VisualScriptExpression::ENode::TYPE_SELF: { + + r_ret=instance->get_owner_ptr(); + } break; + case VisualScriptExpression::ENode::TYPE_OPERATOR: { + + + const VisualScriptExpression::OperatorNode *op = static_cast<const VisualScriptExpression::OperatorNode*>(p_node); + + Variant a; + bool ret = _execute(p_inputs,op->nodes[0],a,r_error_str,ce); + if (ret) + return true; + + Variant b; + + if (op->nodes[1]) { + ret = _execute(p_inputs,op->nodes[1],b,r_error_str,ce); + if (ret) + return true; + } + + bool valid=true; + Variant::evaluate(op->op,a,b,r_ret,valid); + if (!valid) { + r_error_str="Invalid operands to operator "+Variant::get_operator_name(op->op)+": "+Variant::get_type_name(a.get_type())+" and "+Variant::get_type_name(b.get_type())+"."; + return true; + } + + } break; + case VisualScriptExpression::ENode::TYPE_INDEX: { + + const VisualScriptExpression::IndexNode *index = static_cast<const VisualScriptExpression::IndexNode*>(p_node); + + Variant base; + bool ret = _execute(p_inputs,index->base,base,r_error_str,ce); + if (ret) + return true; + + Variant idx; + + ret = _execute(p_inputs,index->index,idx,r_error_str,ce); + if (ret) + return true; + + bool valid; + r_ret=base.get(idx,&valid); + if (!valid) { + r_error_str="Invalid index of type "+Variant::get_type_name(idx.get_type())+" for base of type "+Variant::get_type_name(base.get_type())+"."; + return true; + } + + + + } break; + case VisualScriptExpression::ENode::TYPE_NAMED_INDEX: { + + const VisualScriptExpression::NamedIndexNode *index = static_cast<const VisualScriptExpression::NamedIndexNode*>(p_node); + + Variant base; + bool ret = _execute(p_inputs,index->base,base,r_error_str,ce); + if (ret) + return true; + + bool valid; + r_ret=base.get_named(index->name,&valid); + if (!valid) { + r_error_str="Invalid index '"+String(index->name)+"' for base of type "+Variant::get_type_name(base.get_type())+"."; + return true; + } + + } break; + case VisualScriptExpression::ENode::TYPE_ARRAY: { + const VisualScriptExpression::ArrayNode *array = static_cast<const VisualScriptExpression::ArrayNode*>(p_node); + + Array arr; + arr.resize(array->array.size()); + for (int i=0;i<array->array.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,array->array[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + } + + r_ret=arr; + + } break; + case VisualScriptExpression::ENode::TYPE_DICTIONARY: { + const VisualScriptExpression::DictionaryNode *dictionary = static_cast<const VisualScriptExpression::DictionaryNode*>(p_node); + + Dictionary d; + for (int i=0;i<dictionary->dict.size();i+=2) { + + Variant key; + bool ret = _execute(p_inputs,dictionary->dict[i+0],key,r_error_str,ce); + if (ret) + return true; + + Variant value; + ret = _execute(p_inputs,dictionary->dict[i+1],value,r_error_str,ce); + if (ret) + return true; + + d[key]=value; + } + + r_ret=d; + } break; + case VisualScriptExpression::ENode::TYPE_CONSTRUCTOR: { + + const VisualScriptExpression::ConstructorNode *constructor = static_cast<const VisualScriptExpression::ConstructorNode*>(p_node); + + Vector<Variant> arr; + Vector<const Variant*> argp; + arr.resize(constructor->arguments.size()); + argp.resize(constructor->arguments.size()); + + for (int i=0;i<constructor->arguments.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,constructor->arguments[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + argp[i]=&arr[i]; + } + + + r_ret=Variant::construct(constructor->data_type,argp.ptr(),argp.size(),ce); + + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="Invalid arguments to construct '"+Variant::get_type_name(constructor->data_type)+"'."; + return true; + } + + + } break; + case VisualScriptExpression::ENode::TYPE_BUILTIN_FUNC: { + + const VisualScriptExpression::BuiltinFuncNode *bifunc = static_cast<const VisualScriptExpression::BuiltinFuncNode*>(p_node); + + Vector<Variant> arr; + Vector<const Variant*> argp; + arr.resize(bifunc->arguments.size()); + argp.resize(bifunc->arguments.size()); + + for (int i=0;i<bifunc->arguments.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,bifunc->arguments[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + argp[i]=&arr[i]; + } + + + VisualScriptBuiltinFunc::exec_func(bifunc->func,argp.ptr(),&r_ret,ce,r_error_str); + + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="Builtin Call Failed. "+r_error_str; + return true; + } + + } break; + case VisualScriptExpression::ENode::TYPE_CALL: { + + const VisualScriptExpression::CallNode *call = static_cast<const VisualScriptExpression::CallNode*>(p_node); + + + Variant base; + bool ret = _execute(p_inputs,call->base,base,r_error_str,ce); + if (ret) + return true; + + Vector<Variant> arr; + Vector<const Variant*> argp; + arr.resize(call->arguments.size()); + argp.resize(call->arguments.size()); + + for (int i=0;i<call->arguments.size();i++) { + + Variant value; + bool ret = _execute(p_inputs,call->arguments[i],value,r_error_str,ce); + if (ret) + return true; + arr[i]=value; + argp[i]=&arr[i]; + } + + + r_ret=base.call(call->method,argp.ptr(),argp.size(),ce); + + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="On call to '"+String(call->method)+"':"; + return true; + } + + } break; + } + return false; + } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!expression->root || expression->error_set) { + r_error_str=expression->error_str; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + + bool error = _execute(p_inputs,expression->root,*p_outputs[0],r_error_str,r_error); + if (error && r_error.error==Variant::CallError::CALL_OK) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } + +#ifdef DEBUG_ENABLED + if (!error && expression->output_type!=Variant::NIL && !Variant::can_convert_strict(p_outputs[0]->get_type(),expression->output_type)) { + + r_error_str+="Can't convert expression result from "+Variant::get_type_name(p_outputs[0]->get_type())+" to "+Variant::get_type_name(expression->output_type)+"."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + + } +#endif + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptExpression::instance(VisualScriptInstance* p_instance){ + + _compile_expression(); + VisualScriptNodeInstanceExpression *instance = memnew( VisualScriptNodeInstanceExpression ); + instance->instance=p_instance; + instance->expression=this; + return instance; +} + + +VisualScriptExpression::VisualScriptExpression() +{ + output_type=Variant::NIL; + expression_dirty=true; + error_set=true; + root=NULL; + nodes=NULL; + sequenced=false; +} + +VisualScriptExpression::~VisualScriptExpression() { + + if (nodes) { + memdelete(nodes); + } +} + + +void register_visual_script_expression_node() { + + VisualScriptLanguage::singleton->add_register_func("operators/expression",create_node_generic<VisualScriptExpression>); + +} diff --git a/modules/visual_script/visual_script_expression.h b/modules/visual_script/visual_script_expression.h new file mode 100644 index 000000000..a67656a4b --- /dev/null +++ b/modules/visual_script/visual_script_expression.h @@ -0,0 +1,280 @@ +#ifndef VISUALSCRIPTEXPRESSION_H +#define VISUALSCRIPTEXPRESSION_H + +#include "visual_script.h" +#include "visual_script_builtin_funcs.h" + +class VisualScriptExpression : public VisualScriptNode { + + GDCLASS(VisualScriptExpression,VisualScriptNode) +friend class VisualScriptNodeInstanceExpression; + + struct Input { + + Variant::Type type; + String name; + + Input() { type=Variant::NIL; } + }; + + Vector<Input> inputs; + Variant::Type output_type; + + String expression; + + bool sequenced; + int str_ofs; + bool expression_dirty; + + bool _compile_expression(); + + enum TokenType { + TK_CURLY_BRACKET_OPEN, + TK_CURLY_BRACKET_CLOSE, + TK_BRACKET_OPEN, + TK_BRACKET_CLOSE, + TK_PARENTHESIS_OPEN, + TK_PARENTHESIS_CLOSE, + TK_IDENTIFIER, + TK_BUILTIN_FUNC, + TK_SELF, + TK_CONSTANT, + TK_BASIC_TYPE, + TK_COLON, + TK_COMMA, + TK_PERIOD, + TK_OP_IN, + TK_OP_EQUAL, + TK_OP_NOT_EQUAL, + TK_OP_LESS, + TK_OP_LESS_EQUAL, + TK_OP_GREATER, + TK_OP_GREATER_EQUAL, + TK_OP_AND, + TK_OP_OR, + TK_OP_NOT, + TK_OP_ADD, + TK_OP_SUB, + TK_OP_MUL, + TK_OP_DIV, + TK_OP_MOD, + TK_OP_SHIFT_LEFT, + TK_OP_SHIFT_RIGHT, + TK_OP_BIT_AND, + TK_OP_BIT_OR, + TK_OP_BIT_XOR, + TK_OP_BIT_INVERT, + TK_EOF, + TK_ERROR, + TK_MAX + }; + + static const char* token_name[TK_MAX]; + struct Token { + + TokenType type; + Variant value; + }; + + + void _set_error(const String& p_err) { + if (error_set) + return; + error_str=p_err; + error_set=true; + } + + Error _get_token(Token& r_token); + + String error_str; + bool error_set; + + + + struct ENode { + + enum Type { + TYPE_INPUT, + TYPE_CONSTANT, + TYPE_SELF, + TYPE_OPERATOR, + TYPE_INDEX, + TYPE_NAMED_INDEX, + TYPE_ARRAY, + TYPE_DICTIONARY, + TYPE_CONSTRUCTOR, + TYPE_BUILTIN_FUNC, + TYPE_CALL + }; + + ENode *next; + + Type type; + + ENode() { next=NULL; } + virtual ~ENode() { if (next) { memdelete(next); } } + }; + + struct Expression { + + bool is_op; + union { + Variant::Operator op; + ENode *node; + }; + }; + + ENode* _parse_expression(); + + struct InputNode : public ENode { + + int index; + InputNode() { + type=TYPE_INPUT; + } + }; + + + struct ConstantNode : public ENode { + + Variant value; + ConstantNode() { + type=TYPE_CONSTANT; + } + }; + + struct OperatorNode : public ENode { + + Variant::Operator op; + + ENode* nodes[2]; + + OperatorNode() { + type=TYPE_OPERATOR; + } + }; + + struct SelfNode : public ENode { + + + SelfNode() { + type=TYPE_SELF; + } + }; + + struct IndexNode : public ENode { + ENode*base; + ENode*index; + + IndexNode() { + type=TYPE_INDEX; + } + }; + + struct NamedIndexNode : public ENode { + ENode*base; + StringName name; + + NamedIndexNode() { + type=TYPE_NAMED_INDEX; + } + + }; + + struct ConstructorNode : public ENode { + Variant::Type data_type; + Vector<ENode*> arguments; + + ConstructorNode() { + type=TYPE_CONSTRUCTOR; + } + }; + + struct CallNode : public ENode { + ENode*base; + StringName method; + Vector<ENode*> arguments; + + CallNode() { + type=TYPE_CALL; + } + + }; + + struct ArrayNode : public ENode { + Vector<ENode*> array; + ArrayNode() { + type=TYPE_ARRAY; + } + + }; + + struct DictionaryNode : public ENode { + Vector<ENode*> dict; + DictionaryNode() { + type=TYPE_DICTIONARY; + } + + }; + + struct BuiltinFuncNode : public ENode { + VisualScriptBuiltinFunc::BuiltinFunc func; + Vector<ENode*> arguments; + BuiltinFuncNode() { + type=TYPE_BUILTIN_FUNC; + } + }; + + template<class T> + T* alloc_node() { + T* node = memnew(T); + node->next=nodes; + nodes=node; + return node; + } + + ENode *root; + ENode *nodes; + + + + + +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + +public: + + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptExpression(); + ~VisualScriptExpression(); +}; + + +void register_visual_script_expression_node(); + + +#endif // VISUALSCRIPTEXPRESSION_H diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp new file mode 100644 index 000000000..0e526f8a4 --- /dev/null +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -0,0 +1,1940 @@ +#include "visual_script_flow_control.h" +#include "os/keyboard.h" +#include "globals.h" + + +////////////////////////////////////////// +////////////////RETURN//////////////////// +////////////////////////////////////////// + +int VisualScriptReturn::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptReturn::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptReturn::get_input_value_port_count() const{ + + return with_value?1:0; +} +int VisualScriptReturn::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptReturn::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptReturn::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="result"; + pinfo.type=type; + return pinfo; +} +PropertyInfo VisualScriptReturn::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(); +} + +String VisualScriptReturn::get_caption() const { + + return "Return"; +} + +String VisualScriptReturn::get_text() const { + + return get_name(); +} + +void VisualScriptReturn::set_return_type(Variant::Type p_type) { + + if (type==p_type) + return; + type=p_type; + ports_changed_notify(); + +} + +Variant::Type VisualScriptReturn::get_return_type() const{ + + return type; +} + +void VisualScriptReturn::set_enable_return_value(bool p_enable) { + if (with_value==p_enable) + return; + + with_value=p_enable; + ports_changed_notify(); +} + +bool VisualScriptReturn::is_return_value_enabled() const { + + return with_value; +} + +void VisualScriptReturn::_bind_methods() { + + ClassDB::bind_method(_MD("set_return_type","type"),&VisualScriptReturn::set_return_type); + ClassDB::bind_method(_MD("get_return_type"),&VisualScriptReturn::get_return_type); + ClassDB::bind_method(_MD("set_enable_return_value","enable"),&VisualScriptReturn::set_enable_return_value); + ClassDB::bind_method(_MD("is_return_value_enabled"),&VisualScriptReturn::is_return_value_enabled); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::BOOL,"return_value/enabled"),_SCS("set_enable_return_value"),_SCS("is_return_value_enabled")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"return_value/type",PROPERTY_HINT_ENUM,argt),_SCS("set_return_type"),_SCS("get_return_type")); + +} + +class VisualScriptNodeInstanceReturn : public VisualScriptNodeInstance { +public: + + VisualScriptReturn *node; + VisualScriptInstance *instance; + bool with_value; + + virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (with_value) { + *p_working_mem = *p_inputs[0]; + } else { + *p_working_mem = Variant(); + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptReturn::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceReturn * instance = memnew(VisualScriptNodeInstanceReturn ); + instance->node=this; + instance->instance=p_instance; + instance->with_value=with_value; + return instance; +} + +VisualScriptReturn::VisualScriptReturn() { + + with_value=false; + type=Variant::NIL; +} + +template<bool with_value> +static Ref<VisualScriptNode> create_return_node(const String& p_name) { + + Ref<VisualScriptReturn> node; + node.instance(); + node->set_enable_return_value(with_value); + return node; +} + + + +////////////////////////////////////////// +////////////////CONDITION///////////////// +////////////////////////////////////////// + +int VisualScriptCondition::get_output_sequence_port_count() const { + + return 3; +} + +bool VisualScriptCondition::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptCondition::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptCondition::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptCondition::get_output_sequence_port_text(int p_port) const { + + if (p_port==0) + return "true"; + else if (p_port==1) + return "false"; + else + return "done"; +} + +PropertyInfo VisualScriptCondition::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="cond"; + pinfo.type=Variant::BOOL; + return pinfo; +} +PropertyInfo VisualScriptCondition::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(); +} + +String VisualScriptCondition::get_caption() const { + + return "Condition"; +} + +String VisualScriptCondition::get_text() const { + + return "if (cond) is: "; +} + + +void VisualScriptCondition::_bind_methods() { + + + +} + +class VisualScriptNodeInstanceCondition : public VisualScriptNodeInstance { +public: + + VisualScriptCondition *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_CONTINUE_SEQUENCE) + return 2; + else if (p_inputs[0]->operator bool()) + return 0 | STEP_FLAG_PUSH_STACK_BIT; + else + return 1 | STEP_FLAG_PUSH_STACK_BIT; + } + + +}; + +VisualScriptNodeInstance* VisualScriptCondition::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceCondition * instance = memnew(VisualScriptNodeInstanceCondition ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptCondition::VisualScriptCondition() { + +} + + + +////////////////////////////////////////// +////////////////WHILE///////////////// +////////////////////////////////////////// + +int VisualScriptWhile::get_output_sequence_port_count() const { + + return 2; +} + +bool VisualScriptWhile::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptWhile::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptWhile::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptWhile::get_output_sequence_port_text(int p_port) const { + + if (p_port==0) + return "repeat"; + else + return "exit"; +} + +PropertyInfo VisualScriptWhile::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="cond"; + pinfo.type=Variant::BOOL; + return pinfo; +} +PropertyInfo VisualScriptWhile::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(); +} + +String VisualScriptWhile::get_caption() const { + + return "While"; +} + +String VisualScriptWhile::get_text() const { + + return "while (cond): "; +} + + +void VisualScriptWhile::_bind_methods() { + + + +} + +class VisualScriptNodeInstanceWhile : public VisualScriptNodeInstance { +public: + + VisualScriptWhile *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool keep_going = p_inputs[0]->operator bool(); + + if (keep_going) + return 0|STEP_FLAG_PUSH_STACK_BIT; + else + return 1; + } + + +}; + +VisualScriptNodeInstance* VisualScriptWhile::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceWhile * instance = memnew(VisualScriptNodeInstanceWhile ); + instance->node=this; + instance->instance=p_instance; + return instance; +} +VisualScriptWhile::VisualScriptWhile() { + +} + + + +////////////////////////////////////////// +////////////////ITERATOR///////////////// +////////////////////////////////////////// + +int VisualScriptIterator::get_output_sequence_port_count() const { + + return 2; +} + +bool VisualScriptIterator::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptIterator::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptIterator::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptIterator::get_output_sequence_port_text(int p_port) const { + + if (p_port==0) + return "each"; + else + return "exit"; +} + +PropertyInfo VisualScriptIterator::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="input"; + pinfo.type=Variant::NIL; + return pinfo; +} +PropertyInfo VisualScriptIterator::get_output_value_port_info(int p_idx) const{ + PropertyInfo pinfo; + pinfo.name="elem"; + pinfo.type=Variant::NIL; + return pinfo; +} +String VisualScriptIterator::get_caption() const { + + return "Iterator"; +} + +String VisualScriptIterator::get_text() const { + + return "for (elem) in (input): "; +} + + +void VisualScriptIterator::_bind_methods() { + + + +} + +class VisualScriptNodeInstanceIterator : public VisualScriptNodeInstance { +public: + + VisualScriptIterator *node; + VisualScriptInstance *instance; + + virtual int get_working_memory_size() const { return 2; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_BEGIN_SEQUENCE) { + p_working_mem[0]=*p_inputs[0]; + bool valid; + bool can_iter = p_inputs[0]->iter_init(p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Input type not iterable: ")+Variant::get_type_name(p_inputs[0]->get_type()); + return 0; + } + + if (!can_iter) + return 1; //nothing to iterate + + + *p_outputs[0]=p_working_mem[0].iter_get( p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Iterator became invalid"); + return 0; + } + + + } else { //continue sequence + + bool valid; + bool can_iter = p_working_mem[0].iter_next(p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Iterator became invalid: ")+Variant::get_type_name(p_inputs[0]->get_type()); + return 0; + } + + if (!can_iter) + return 1; //nothing to iterate + + + *p_outputs[0]=p_working_mem[0].iter_get( p_working_mem[1],valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Iterator became invalid"); + return 0; + } + + } + + return 0|STEP_FLAG_PUSH_STACK_BIT; //go around + } + + +}; + +VisualScriptNodeInstance* VisualScriptIterator::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceIterator * instance = memnew(VisualScriptNodeInstanceIterator ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptIterator::VisualScriptIterator() { + +} + + + +////////////////////////////////////////// +////////////////SEQUENCE///////////////// +////////////////////////////////////////// + +int VisualScriptSequence::get_output_sequence_port_count() const { + + return steps; +} + +bool VisualScriptSequence::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptSequence::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSequence::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSequence::get_output_sequence_port_text(int p_port) const { + + return itos(p_port+1); +} + +PropertyInfo VisualScriptSequence::get_input_value_port_info(int p_idx) const{ + return PropertyInfo(); +} +PropertyInfo VisualScriptSequence::get_output_value_port_info(int p_idx) const{ + return PropertyInfo(Variant::INT,"current"); +} +String VisualScriptSequence::get_caption() const { + + return "Sequence"; +} + +String VisualScriptSequence::get_text() const { + + return "in order: "; +} + +void VisualScriptSequence::set_steps(int p_steps) { + + ERR_FAIL_COND(p_steps<1); + if (steps==p_steps) + return; + + steps=p_steps; + ports_changed_notify(); + +} + +int VisualScriptSequence::get_steps() const { + + return steps; +} + +void VisualScriptSequence::_bind_methods() { + + ClassDB::bind_method(_MD("set_steps","steps"),&VisualScriptSequence::set_steps); + ClassDB::bind_method(_MD("get_steps"),&VisualScriptSequence::get_steps); + + ADD_PROPERTY(PropertyInfo(Variant::INT,"steps",PROPERTY_HINT_RANGE,"1,64,1"),_SCS("set_steps"),_SCS("get_steps")); + +} + +class VisualScriptNodeInstanceSequence : public VisualScriptNodeInstance { +public: + + VisualScriptSequence *node; + VisualScriptInstance *instance; + int steps; + + virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_BEGIN_SEQUENCE) { + + p_working_mem[0]=0; + } + + int step = p_working_mem[0]; + + *p_outputs[0]=step; + + if (step+1==steps) + return step; + else { + p_working_mem[0]=step+1; + return step|STEP_FLAG_PUSH_STACK_BIT; + } + + } + + +}; + +VisualScriptNodeInstance* VisualScriptSequence::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSequence * instance = memnew(VisualScriptNodeInstanceSequence ); + instance->node=this; + instance->instance=p_instance; + instance->steps=steps; + return instance; +} +VisualScriptSequence::VisualScriptSequence() { + + steps=1; +} + + +////////////////////////////////////////// +////////////////EVENT TYPE FILTER/////////// +////////////////////////////////////////// + +int VisualScriptSwitch::get_output_sequence_port_count() const { + + return case_values.size()+1; +} + +bool VisualScriptSwitch::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptSwitch::get_input_value_port_count() const{ + + + return case_values.size()+1; +} +int VisualScriptSwitch::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptSwitch::get_output_sequence_port_text(int p_port) const { + + if (p_port==case_values.size()) + return "done"; + + return String(); +} + +PropertyInfo VisualScriptSwitch::get_input_value_port_info(int p_idx) const{ + + if (p_idx<case_values.size()) { + return PropertyInfo(case_values[p_idx].type," ="); + } else + return PropertyInfo(Variant::NIL,"input"); +} + +PropertyInfo VisualScriptSwitch::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptSwitch::get_caption() const { + + return "Switch"; +} + +String VisualScriptSwitch::get_text() const { + + return "'input' is:"; +} + + +class VisualScriptNodeInstanceSwitch : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + int case_count; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_CONTINUE_SEQUENCE) { + return case_count; //exit + } + + for(int i=0;i<case_count;i++) { + + if (*p_inputs[i]==*p_inputs[case_count]) { + return i|STEP_FLAG_PUSH_STACK_BIT; + } + } + + return case_count; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSwitch::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSwitch * instance = memnew(VisualScriptNodeInstanceSwitch ); + instance->instance=p_instance; + instance->case_count=case_values.size(); + return instance; +} + +bool VisualScriptSwitch::_set(const StringName& p_name, const Variant& p_value) { + + if (String(p_name)=="case_count") { + case_values.resize(p_value); + _change_notify(); + ports_changed_notify(); + return true; + } + + if (String(p_name).begins_with("case/")) { + + int idx = String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,case_values.size(),false); + + case_values[idx].type=Variant::Type(int(p_value)); + _change_notify(); + ports_changed_notify(); + + return true; + } + + return false; +} + +bool VisualScriptSwitch::_get(const StringName& p_name,Variant &r_ret) const { + + if (String(p_name)=="case_count") { + r_ret=case_values.size(); + return true; + } + + if (String(p_name).begins_with("case/")) { + + int idx = String(p_name).get_slice("/",1).to_int(); + ERR_FAIL_INDEX_V(idx,case_values.size(),false); + + r_ret=case_values[idx].type; + return true; + } + + return false; + +} +void VisualScriptSwitch::_get_property_list( List<PropertyInfo> *p_list) const { + + p_list->push_back(PropertyInfo(Variant::INT,"case_count",PROPERTY_HINT_RANGE,"0,128")); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + for(int i=0;i<case_values.size();i++) { + p_list->push_back(PropertyInfo(Variant::INT,"case/"+itos(i),PROPERTY_HINT_ENUM,argt)); + } +} + + +void VisualScriptSwitch::_bind_methods() { + + +} + +VisualScriptSwitch::VisualScriptSwitch() { + + +} + +////////////////////////////////////////// +////////////////EVENT ACTION FILTER/////////// +////////////////////////////////////////// + + +int VisualScriptInputFilter::get_output_sequence_port_count() const { + + return filters.size(); +} + +bool VisualScriptInputFilter::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptInputFilter::get_input_value_port_count() const{ + + + return 1; +} +int VisualScriptInputFilter::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptInputFilter::get_output_sequence_port_text(int p_port) const { + + String text; + + switch(filters[p_port].type) { + case InputEvent::NONE: { + text="None"; + } break; + case InputEvent::KEY: { + + InputEventKey k = filters[p_port].key; + + if (k.scancode==0 && k.unicode==0) { + text="No Key"; + } else { + if (k.scancode!=0) { + text="KeyCode: "+keycode_get_string(k.scancode); + } else if (k.unicode!=0) { + text="Uniode: "+String::chr(k.unicode); + } + + if (k.pressed) + text+=", Pressed"; + else + text+=", Released"; + + if (k.echo) + text+=", Echo"; + if (k.mod.alt) + text="Alt+"+text; + if (k.mod.shift) + text="Shift+"+text; + if (k.mod.control) + text="Ctrl+"+text; + if (k.mod.meta) + text="Meta+"+text; + } + + } break; + case InputEvent::MOUSE_MOTION: { + InputEventMouseMotion mm = filters[p_port].mouse_motion; + text="Mouse Motion"; + + String b = "Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight"; + + for(int i=0;i<7;i++) { + if (mm.button_mask&(1<<i)) { + text=b.get_slice(",",i)+"+"+text; + } + } + if (mm.mod.alt) + text="Alt+"+text; + if (mm.mod.shift) + text="Shift+"+text; + if (mm.mod.control) + text="Ctrl+"+text; + if (mm.mod.meta) + text="Meta+"+text; + } break; + case InputEvent::MOUSE_BUTTON: { + + InputEventMouseButton mb = filters[p_port].mouse_button; + + String b = "Any,Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight"; + + text=b.get_slice(",",mb.button_index)+" Mouse Button"; + + if (mb.pressed) + text+=", Pressed"; + else + text+=", Released"; + + if (mb.doubleclick) + text+=", DblClick"; + if (mb.mod.alt) + text="Alt+"+text; + if (mb.mod.shift) + text="Shift+"+text; + if (mb.mod.control) + text="Ctrl+"+text; + if (mb.mod.meta) + text="Meta+"+text; + + + } break; + case InputEvent::JOYPAD_MOTION: { + + InputEventJoypadMotion jm = filters[p_port].joy_motion; + + text="JoyMotion Axis "+itos(jm.axis>>1); + if (jm.axis&1) + text+=" > "+rtos(jm.axis_value); + else + text+=" < "+rtos(-jm.axis_value); + + } break; + case InputEvent::JOYPAD_BUTTON: { + InputEventJoypadButton jb = filters[p_port].joy_button; + + text="JoyButton "+itos(jb.button_index); + if (jb.pressed) + text+=", Pressed"; + else + text+=", Released"; + } break; + case InputEvent::SCREEN_TOUCH: { + InputEventScreenTouch sd = filters[p_port].screen_touch; + + text="Touch Finger "+itos(sd.index); + if (sd.pressed) + text+=", Pressed"; + else + text+=", Released"; + } break; + case InputEvent::SCREEN_DRAG: { + InputEventScreenDrag sd = filters[p_port].screen_drag; + text="Drag Finger "+itos(sd.index); + } break; + case InputEvent::ACTION: { + + + List<PropertyInfo> pinfo; + GlobalConfig::get_singleton()->get_property_list(&pinfo); + int index=1; + + text="No Action"; + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + + if (filters[p_port].action.action==index) { + text="Action "+pi.name.substr(pi.name.find("/")+1,pi.name.length()); + break; + } + index++; + } + + if (filters[p_port].action.pressed) + text+=", Pressed"; + else + text+=", Released"; + + + } break; + } + + + + return text+" - "+itos(p_port); +} + +PropertyInfo VisualScriptInputFilter::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INPUT_EVENT,"event"); +} + +PropertyInfo VisualScriptInputFilter::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INPUT_EVENT,""); +} + + +String VisualScriptInputFilter::get_caption() const { + + return "InputFilter"; +} + +String VisualScriptInputFilter::get_text() const { + + return ""; +} + + + +bool VisualScriptInputFilter::_set(const StringName& p_name, const Variant& p_value) { + + if (p_name=="filter_count") { + filters.resize(p_value); + _change_notify(); + ports_changed_notify(); + return true; + } + + + if (String(p_name).begins_with("filter_")) { + + int idx = String(p_name).replace_first("filters_","").get_slice("/",0).to_int(); + + ERR_FAIL_INDEX_V(idx,filters.size(),false); + + String what = String(p_name).get_slice("/",1); + + + if (what=="type") { + filters[idx]=InputEvent(); + filters[idx].type=InputEvent::Type(int(p_value)); + if (filters[idx].type==InputEvent::JOYPAD_MOTION) { + filters[idx].joy_motion.axis_value=0.5; //for treshold + } else if (filters[idx].type==InputEvent::KEY) { + filters[idx].key.pressed=true; //put these as true to make it more user friendly + } else if (filters[idx].type==InputEvent::MOUSE_BUTTON) { + filters[idx].mouse_button.pressed=true; + } else if (filters[idx].type==InputEvent::JOYPAD_BUTTON) { + filters[idx].joy_button.pressed=true; + } else if (filters[idx].type==InputEvent::SCREEN_TOUCH) { + filters[idx].screen_touch.pressed=true; + } else if (filters[idx].type==InputEvent::ACTION) { + filters[idx].action.pressed=true; + } + _change_notify(); + ports_changed_notify(); + + return true; + } + if (what=="device") { + filters[idx].device=p_value; + ports_changed_notify(); + return true; + } + + switch(filters[idx].type) { + + case InputEvent::KEY: { + + if (what=="scancode") { + String sc = p_value; + if (sc==String()) { + filters[idx].key.scancode=0; + } else { + filters[idx].key.scancode=find_keycode(p_value); + } + + } else if (what=="unicode") { + + String uc = p_value; + + if (uc==String()) { + filters[idx].key.unicode=0; + } else { + filters[idx].key.unicode=uc[0]; + } + + } else if (what=="pressed") { + + filters[idx].key.pressed=p_value; + } else if (what=="echo") { + + filters[idx].key.echo=p_value; + + } else if (what=="mod_alt") { + filters[idx].key.mod.alt=p_value; + + } else if (what=="mod_shift") { + filters[idx].key.mod.shift=p_value; + + } else if (what=="mod_ctrl") { + filters[idx].key.mod.control=p_value; + + } else if (what=="mod_meta") { + filters[idx].key.mod.meta=p_value; + } else { + return false; + } + ports_changed_notify(); + + return true; + } break; + case InputEvent::MOUSE_MOTION: { + + + if (what=="button_mask") { + filters[idx].mouse_motion.button_mask=p_value; + + } else if (what=="mod_alt") { + filters[idx].mouse_motion.mod.alt=p_value; + + } else if (what=="mod_shift") { + filters[idx].mouse_motion.mod.shift=p_value; + + } else if (what=="mod_ctrl") { + filters[idx].mouse_motion.mod.control=p_value; + + } else if (what=="mod_meta") { + filters[idx].mouse_motion.mod.meta=p_value; + } else { + return false; + } + + ports_changed_notify(); + return true; + + } break; + case InputEvent::MOUSE_BUTTON: { + + if (what=="button_index") { + filters[idx].mouse_button.button_index=p_value; + } else if (what=="pressed") { + filters[idx].mouse_button.pressed=p_value; + } else if (what=="doubleclicked") { + filters[idx].mouse_button.doubleclick=p_value; + + } else if (what=="mod_alt") { + filters[idx].mouse_button.mod.alt=p_value; + + } else if (what=="mod_shift") { + filters[idx].mouse_button.mod.shift=p_value; + + } else if (what=="mod_ctrl") { + filters[idx].mouse_button.mod.control=p_value; + + } else if (what=="mod_meta") { + filters[idx].mouse_button.mod.meta=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + + } break; + case InputEvent::JOYPAD_MOTION: { + + if (what=="axis") { + filters[idx].joy_motion.axis=int(p_value)<<1|filters[idx].joy_motion.axis; + } else if (what=="mode") { + filters[idx].joy_motion.axis|=int(p_value); + } else if (what=="treshold") { + filters[idx].joy_motion.axis_value=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + + + } break; + case InputEvent::JOYPAD_BUTTON: { + + if (what=="button_index") { + filters[idx].joy_button.button_index=p_value; + } else if (what=="pressed") { + filters[idx].joy_button.pressed=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + + } break; + case InputEvent::SCREEN_TOUCH: { + + if (what=="finger_index") { + filters[idx].screen_touch.index=p_value; + } else if (what=="pressed") { + filters[idx].screen_touch.pressed=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + } break; + case InputEvent::SCREEN_DRAG: { + if (what=="finger_index") { + filters[idx].screen_drag.index=p_value; + } else { + return false; + } + ports_changed_notify(); + return true; + } break; + case InputEvent::ACTION: { + + + if (what=="action_name") { + + List<PropertyInfo> pinfo; + GlobalConfig::get_singleton()->get_property_list(&pinfo); + int index=1; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + String name = pi.name.substr(pi.name.find("/")+1,pi.name.length()); + if (name==String(p_value)) { + + filters[idx].action.action=index; + ports_changed_notify(); + return true; + } + + index++; + } + + filters[idx].action.action=0; + ports_changed_notify(); + + return false; + + } else if (what=="pressed") { + + filters[idx].action.pressed=p_value; + ports_changed_notify(); + return true; + } + + + } break; + + } + } + return false; +} + +bool VisualScriptInputFilter::_get(const StringName& p_name,Variant &r_ret) const{ + + if (p_name=="filter_count") { + r_ret=filters.size(); + return true; + } + + + if (String(p_name).begins_with("filter_")) { + + int idx = String(p_name).replace_first("filters_","").get_slice("/",0).to_int(); + + ERR_FAIL_INDEX_V(idx,filters.size(),false); + + String what = String(p_name).get_slice("/",1); + + + if (what=="type") { + r_ret=filters[idx].type; + return true; + } + if (what=="device") { + r_ret=filters[idx].device; + return true; + } + + switch(filters[idx].type) { + + case InputEvent::KEY: { + + if (what=="scancode") { + if (filters[idx].key.scancode==0) + r_ret=String(); + else { + + r_ret=keycode_get_string(filters[idx].key.scancode); + } + + } else if (what=="unicode") { + + + if (filters[idx].key.unicode==0) { + r_ret=String(); + } else { + CharType str[2]={ (CharType)filters[idx].key.unicode, 0}; + r_ret=String(str); + } + + } else if (what=="pressed") { + + r_ret=filters[idx].key.pressed; + } else if (what=="echo") { + + r_ret=filters[idx].key.echo; + + } else if (what=="mod_alt") { + r_ret=filters[idx].key.mod.alt; + + } else if (what=="mod_shift") { + r_ret=filters[idx].key.mod.shift; + + } else if (what=="mod_ctrl") { + r_ret=filters[idx].key.mod.control; + + } else if (what=="mod_meta") { + r_ret=filters[idx].key.mod.meta; + } else { + return false; + } + + return true; + } break; + case InputEvent::MOUSE_MOTION: { + + + if (what=="button_mask") { + r_ret=filters[idx].mouse_motion.button_mask; + + } else if (what=="mod_alt") { + r_ret=filters[idx].mouse_motion.mod.alt; + + } else if (what=="mod_shift") { + r_ret=filters[idx].mouse_motion.mod.shift; + + } else if (what=="mod_ctrl") { + r_ret=filters[idx].mouse_motion.mod.control; + + } else if (what=="mod_meta") { + r_ret=filters[idx].mouse_motion.mod.meta; + } else { + return false; + } + + return true; + + } break; + case InputEvent::MOUSE_BUTTON: { + + if (what=="button_index") { + r_ret=filters[idx].mouse_button.button_index; + } else if (what=="pressed") { + r_ret=filters[idx].mouse_button.pressed; + } else if (what=="doubleclicked") { + r_ret=filters[idx].mouse_button.doubleclick; + + } else if (what=="mod_alt") { + r_ret=filters[idx].mouse_button.mod.alt; + + } else if (what=="mod_shift") { + r_ret=filters[idx].mouse_button.mod.shift; + + } else if (what=="mod_ctrl") { + r_ret=filters[idx].mouse_button.mod.control; + + } else if (what=="mod_meta") { + r_ret=filters[idx].mouse_button.mod.meta; + } else { + return false; + } + return true; + + } break; + case InputEvent::JOYPAD_MOTION: { + + if (what=="axis_index") { + r_ret=filters[idx].joy_motion.axis>>1; + } else if (what=="mode") { + r_ret=filters[idx].joy_motion.axis&1; + } else if (what=="treshold") { + r_ret=filters[idx].joy_motion.axis_value; + } else { + return false; + } + return true; + + + } break; + case InputEvent::JOYPAD_BUTTON: { + + if (what=="button_index") { + r_ret=filters[idx].joy_button.button_index; + } else if (what=="pressed") { + r_ret=filters[idx].joy_button.pressed; + } else { + return false; + } + return true; + + } break; + case InputEvent::SCREEN_TOUCH: { + + if (what=="finger_index") { + r_ret=filters[idx].screen_touch.index; + } else if (what=="pressed") { + r_ret=filters[idx].screen_touch.pressed; + } else { + return false; + } + return true; + } break; + case InputEvent::SCREEN_DRAG: { + if (what=="finger_index") { + r_ret=filters[idx].screen_drag.index; + } else { + return false; + } + return true; + } break; + case InputEvent::ACTION: { + + + if (what=="action_name") { + + List<PropertyInfo> pinfo; + GlobalConfig::get_singleton()->get_property_list(&pinfo); + int index=1; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + + if (filters[idx].action.action==index) { + r_ret=pi.name.substr(pi.name.find("/")+1,pi.name.length()); + return true; + } + index++; + } + + r_ret="None"; //no index + return false; + + } else if (what=="pressed") { + + r_ret=filters[idx].action.pressed; + return true; + } + + + } break; + + } + } + return false; +} + +static const char* event_type_names[InputEvent::TYPE_MAX]={ + "None", + "Key", + "MouseMotion", + "MouseButton", + "JoypadMotion", + "JoypadButton", + "ScreenTouch", + "ScreenDrag", + "Action" +}; + +void VisualScriptInputFilter::_get_property_list( List<PropertyInfo> *p_list) const { + + p_list->push_back(PropertyInfo(Variant::INT,"filter_count",PROPERTY_HINT_RANGE,"0,64")); + + String et; + for(int i=0;i<InputEvent::TYPE_MAX;i++) { + if (i>0) + et+=","; + + et+=event_type_names[i]; + } + + String kc; + String actions; + + + + for(int i=0;i<filters.size();i++) { + + String base = "filter_"+itos(i)+"/"; + p_list->push_back(PropertyInfo(Variant::INT,base+"type",PROPERTY_HINT_ENUM,et)); + p_list->push_back(PropertyInfo(Variant::INT,base+"device")); + switch(filters[i].type) { + + case InputEvent::NONE: { + + } break; + case InputEvent::KEY: { + if (kc==String()) { + int kcc = keycode_get_count(); + kc="None"; + for(int i=0;i<kcc;i++) { + kc+=","; + kc+=String(keycode_get_name_by_index(i)); + } + } + p_list->push_back(PropertyInfo(Variant::STRING,base+"scancode",PROPERTY_HINT_ENUM,kc)); + p_list->push_back(PropertyInfo(Variant::STRING,base+"unicode")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"echo")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_alt")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_shift")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_ctrl")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_meta")); + + + } break; + case InputEvent::MOUSE_MOTION: { + p_list->push_back(PropertyInfo(Variant::INT,base+"button_mask",PROPERTY_HINT_FLAGS,"Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_alt")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_shift")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_ctrl")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_meta")); + + } break; + case InputEvent::MOUSE_BUTTON: { + p_list->push_back(PropertyInfo(Variant::INT,base+"button_index",PROPERTY_HINT_ENUM,"Any,Left,Right,Middle,WheelUp,WheelDown,WheelLeft,WheelRight")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"doubleclicked")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_alt")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_shift")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_ctrl")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"mod_meta")); + + } break; + case InputEvent::JOYPAD_MOTION: { + + p_list->push_back(PropertyInfo(Variant::INT,base+"axis_index")); + p_list->push_back(PropertyInfo(Variant::INT,base+"mode",PROPERTY_HINT_ENUM,"Min,Max")); + p_list->push_back(PropertyInfo(Variant::REAL,base+"treshold",PROPERTY_HINT_RANGE,"0,1,0.01")); + } break; + case InputEvent::JOYPAD_BUTTON: { + p_list->push_back(PropertyInfo(Variant::INT,base+"button_index")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + + } break; + case InputEvent::SCREEN_TOUCH: { + p_list->push_back(PropertyInfo(Variant::INT,base+"finger_index")); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + + } break; + case InputEvent::SCREEN_DRAG: { + p_list->push_back(PropertyInfo(Variant::INT,base+"finger_index")); + } break; + case InputEvent::ACTION: { + + + + if (actions==String()) { + + actions="None"; + + List<PropertyInfo> pinfo; + GlobalConfig::get_singleton()->get_property_list(&pinfo); + Vector<String> al; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + String name = pi.name.substr(pi.name.find("/")+1,pi.name.length()); + + + al.push_back(name); + } + + for(int i=0;i<al.size();i++) { + actions+=","; + actions+=al[i]; + } + } + + p_list->push_back(PropertyInfo(Variant::STRING,base+"action_name",PROPERTY_HINT_ENUM,actions)); + p_list->push_back(PropertyInfo(Variant::BOOL,base+"pressed")); + + } break; + + } + } +} + +class VisualScriptNodeInstanceInputFilter : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + Vector<InputEvent> filters; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_inputs[0]->get_type()!=Variant::INPUT_EVENT) { + r_error_str="Input value not of type event"; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + InputEvent event = *p_inputs[0]; + + + for(int i=0;i<filters.size();i++) { + + const InputEvent &ie = filters[i]; + if (ie.type!=event.type) + continue; + + bool match=false; + + switch(ie.type) { + + case InputEvent::NONE: { + + match=true; + } break; + case InputEvent::KEY: { + + InputEventKey k = ie.key; + InputEventKey k2 = event.key; + + if (k.scancode==0 && k.unicode==0 && k2.scancode==0 && k2.unicode==0) { + match=true; + + } else { + + if ( (k.scancode!=0 && k.scancode==k2.scancode) || (k.unicode!=0 && k.unicode==k2.unicode)) { + //key valid + + if ( + k.pressed==k2.pressed && + k.echo==k2.echo && + k.mod == k2.mod + ) { + match=true; + } + } + + } + + } break; + case InputEvent::MOUSE_MOTION: { + InputEventMouseMotion mm = ie.mouse_motion; + InputEventMouseMotion mm2 = event.mouse_motion; + + if ( mm.button_mask==mm2.button_mask && + mm.mod==mm2.mod + ) { + match=true; + } + + } break; + case InputEvent::MOUSE_BUTTON: { + + InputEventMouseButton mb = ie.mouse_button; + InputEventMouseButton mb2 = event.mouse_button; + + if ( mb.button_index==mb2.button_index && + mb.pressed==mb2.pressed && + mb.doubleclick==mb2.doubleclick && + mb.mod==mb2.mod) { + match=true; + } + + + } break; + case InputEvent::JOYPAD_MOTION: { + + InputEventJoypadMotion jm = ie.joy_motion; + InputEventJoypadMotion jm2 = event.joy_motion; + + int axis = jm.axis>>1; + + if (axis==jm2.axis) { + + if (jm.axis&1) { + //greater + if (jm2.axis_value > jm.axis_value) { + match=true; + } + } else { + //less + if (jm2.axis_value < -jm.axis_value) { + match=true; + } + } + } + + + } break; + case InputEvent::JOYPAD_BUTTON: { + InputEventJoypadButton jb = ie.joy_button; + InputEventJoypadButton jb2 = event.joy_button; + + if ( jb.button_index==jb2.button_index && + jb.pressed == jb2.pressed + ) { + match=true; + } + } break; + case InputEvent::SCREEN_TOUCH: { + InputEventScreenTouch st = ie.screen_touch; + InputEventScreenTouch st2 = event.screen_touch; + + if ( st.index==st2.index && + st.pressed==st2.pressed) { + match=true; + } + + } break; + case InputEvent::SCREEN_DRAG: { + InputEventScreenDrag sd = ie.screen_drag; + InputEventScreenDrag sd2 = event.screen_drag; + + if (sd.index==sd2.index) { + match=true; + } + } break; + case InputEvent::ACTION: { + + InputEventAction ia = ie.action; + InputEventAction ia2 = event.action; + + if ( ia.action==ia2.action && + ia.pressed==ia2.pressed) { + match=true; + } + } break; + + } + + *p_outputs[0] = event; + + if (match) + return i; //go through match output + + } + + return STEP_NO_ADVANCE_BIT; //none found, don't advance + + + } + + +}; + + +VisualScriptNodeInstance* VisualScriptInputFilter::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceInputFilter * instance = memnew(VisualScriptNodeInstanceInputFilter ); + instance->instance=p_instance; + instance->filters=filters; + return instance; +} + + + + +VisualScriptInputFilter::VisualScriptInputFilter() { + + +} + + +////////////////////////////////////////// +////////////////TYPE CAST/////////// +////////////////////////////////////////// + + +int VisualScriptTypeCast::get_output_sequence_port_count() const { + + return 2; +} + +bool VisualScriptTypeCast::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptTypeCast::get_input_value_port_count() const{ + + + return 1; +} +int VisualScriptTypeCast::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptTypeCast::get_output_sequence_port_text(int p_port) const { + + return p_port==0 ? "yes" : "no"; +} + +PropertyInfo VisualScriptTypeCast::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + +PropertyInfo VisualScriptTypeCast::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,""); +} + + +String VisualScriptTypeCast::get_caption() const { + + return "TypeCast"; +} + +String VisualScriptTypeCast::get_text() const { + + if (script!=String()) + return "Is "+script.get_file()+"?"; + else + return "Is "+base_type+"?"; +} + +void VisualScriptTypeCast::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptTypeCast::get_base_type() const{ + + return base_type; +} + +void VisualScriptTypeCast::set_base_script(const String& p_path){ + + if (script==p_path) + return; + + script=p_path; + _change_notify(); + ports_changed_notify(); + +} +String VisualScriptTypeCast::get_base_script() const{ + + return script; +} + + +class VisualScriptNodeInstanceTypeCast : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName base_type; + String script; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Object *obj = *p_inputs[0]; + + *p_outputs[0]=Variant(); + + if (!obj) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Instance is null"; + return 0; + } + + if (script!=String()) { + + Ref<Script> obj_script = obj->get_script(); + if (!obj_script.is_valid()) { + return 1; //well, definitely not the script because object we got has no script. + } + + if (!ResourceCache::has(script)) { + //if the script is not in use by anyone, we can safely assume whathever we got is not casting to it. + return 1; + } + Ref<Script> cast_script = Ref<Resource>(ResourceCache::get(script)); + if (!cast_script.is_valid()) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Script path is not a script: "+script; + return 1; + } + + while(obj_script.is_valid()) { + + if (cast_script==obj_script) { + *p_outputs[0]=*p_inputs[0]; //copy + return 0; // it is the script, yey + } + + obj_script=obj_script->get_base_script(); + } + + return 1; //not found sorry + } + + if (ClassDB::is_parent_class(obj->get_class_name(),base_type)) { + *p_outputs[0]=*p_inputs[0]; //copy + return 0; + } else + return 1; + + } + + +}; + +VisualScriptNodeInstance* VisualScriptTypeCast::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceTypeCast * instance = memnew(VisualScriptNodeInstanceTypeCast ); + instance->instance=p_instance; + instance->base_type=base_type; + instance->script=script; + return instance; +} + + + +void VisualScriptTypeCast::_bind_methods() { + + ClassDB::bind_method(_MD("set_base_type","type"),&VisualScriptTypeCast::set_base_type); + ClassDB::bind_method(_MD("get_base_type"),&VisualScriptTypeCast::get_base_type); + + ClassDB::bind_method(_MD("set_base_script","path"),&VisualScriptTypeCast::set_base_script); + ClassDB::bind_method(_MD("get_base_script"),&VisualScriptTypeCast::get_base_script); + + + List<String> script_extensions; + for(int i=0;i>ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="*."+E->get(); + } + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + +} + +VisualScriptTypeCast::VisualScriptTypeCast() { + + base_type="Object"; +} + + +void register_visual_script_flow_control_nodes() { + + VisualScriptLanguage::singleton->add_register_func("flow_control/return",create_return_node<false>); + VisualScriptLanguage::singleton->add_register_func("flow_control/return_with_value",create_return_node<true>); + VisualScriptLanguage::singleton->add_register_func("flow_control/condition",create_node_generic<VisualScriptCondition>); + VisualScriptLanguage::singleton->add_register_func("flow_control/while",create_node_generic<VisualScriptWhile>); + VisualScriptLanguage::singleton->add_register_func("flow_control/iterator",create_node_generic<VisualScriptIterator>); + VisualScriptLanguage::singleton->add_register_func("flow_control/sequence",create_node_generic<VisualScriptSequence>); + VisualScriptLanguage::singleton->add_register_func("flow_control/switch",create_node_generic<VisualScriptSwitch>); + VisualScriptLanguage::singleton->add_register_func("flow_control/input_filter",create_node_generic<VisualScriptInputFilter>); + VisualScriptLanguage::singleton->add_register_func("flow_control/type_cast",create_node_generic<VisualScriptTypeCast>); + + + +} diff --git a/modules/visual_script/visual_script_flow_control.h b/modules/visual_script/visual_script_flow_control.h new file mode 100644 index 000000000..26e981cb1 --- /dev/null +++ b/modules/visual_script/visual_script_flow_control.h @@ -0,0 +1,341 @@ +#ifndef VISUAL_SCRIPT_FLOW_CONTROL_H +#define VISUAL_SCRIPT_FLOW_CONTROL_H + +#include "visual_script.h" + +class VisualScriptReturn : public VisualScriptNode { + + GDCLASS(VisualScriptReturn,VisualScriptNode) + + + Variant::Type type; + bool with_value; +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void set_return_type(Variant::Type); + Variant::Type get_return_type() const; + + void set_enable_return_value(bool p_enable); + bool is_return_value_enabled() const; + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptReturn(); +}; + + +class VisualScriptCondition : public VisualScriptNode { + + GDCLASS(VisualScriptCondition,VisualScriptNode) + + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptCondition(); +}; + + +class VisualScriptWhile : public VisualScriptNode { + + GDCLASS(VisualScriptWhile,VisualScriptNode) + + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptWhile(); +}; + + + +class VisualScriptIterator : public VisualScriptNode { + + GDCLASS(VisualScriptIterator,VisualScriptNode) + + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptIterator(); +}; + + + +class VisualScriptSequence : public VisualScriptNode { + + GDCLASS(VisualScriptSequence,VisualScriptNode) + + + int steps; + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void set_steps(int p_steps); + int get_steps() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptSequence(); +}; + + + + +class VisualScriptSwitch : public VisualScriptNode { + + GDCLASS(VisualScriptSwitch,VisualScriptNode) + + struct Case { + Variant::Type type; + Case() { type=Variant::NIL; } + }; + + Vector<Case> case_values; + +friend class VisualScriptNodeInstanceSwitch; +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + virtual bool has_mixed_input_and_sequence_ports() const { return true; } + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + VisualScriptSwitch(); +}; + + + + +class VisualScriptInputFilter : public VisualScriptNode { + + GDCLASS(VisualScriptInputFilter,VisualScriptNode) + + Vector<InputEvent> filters; + + +protected: + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + VisualScriptInputFilter(); +}; + + + + + +class VisualScriptTypeCast : public VisualScriptNode { + + GDCLASS(VisualScriptTypeCast,VisualScriptNode) + + + StringName base_type; + String script; + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + VisualScriptTypeCast(); +}; + + + + +void register_visual_script_flow_control_nodes(); + + + +#endif // VISUAL_SCRIPT_FLOW_CONTROL_H diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp new file mode 100644 index 000000000..a8d78b929 --- /dev/null +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -0,0 +1,2528 @@ +#include "visual_script_func_nodes.h" +#include "scene/main/scene_main_loop.h" +#include "os/os.h" +#include "scene/main/node.h" +#include "visual_script_nodes.h" +#include "io/resource_loader.h" +#include "globals.h" + +////////////////////////////////////////// +////////////////CALL////////////////////// +////////////////////////////////////////// + +int VisualScriptFunctionCall::get_output_sequence_port_count() const { + + if (method_cache.flags&METHOD_FLAG_CONST || call_mode==CALL_MODE_BASIC_TYPE) + return 0; + else + return 1; +} + +bool VisualScriptFunctionCall::has_input_sequence_port() const{ + + if (method_cache.flags&METHOD_FLAG_CONST || call_mode==CALL_MODE_BASIC_TYPE) + return false; + else + return true; +} +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif +Node *VisualScriptFunctionCall::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptFunctionCall::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_class(); + + } + + return base_type; +} + + +int VisualScriptFunctionCall::get_input_value_port_count() const{ + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + + Vector<StringName> names = Variant::get_method_argument_names(basic_type,function); + return names.size() + (rpc_call_mode>=RPC_RELIABLE_TO_ID?1:0) + 1; + + } else { + + MethodBind *mb = ClassDB::get_method(_get_base_type(),function); + if (mb) { + return mb->get_argument_count() + (call_mode==CALL_MODE_INSTANCE?1:0) + (rpc_call_mode>=RPC_RELIABLE_TO_ID?1:0) - use_default_args; + } + + return method_cache.arguments.size() + (call_mode==CALL_MODE_INSTANCE?1:0) + (rpc_call_mode>=RPC_RELIABLE_TO_ID?1:0) - use_default_args; + } + +} +int VisualScriptFunctionCall::get_output_value_port_count() const{ + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + bool returns=false; + Variant::get_method_return_type(basic_type,function,&returns); + return returns?1:0; + + } else { + int ret; + MethodBind *mb = ClassDB::get_method(_get_base_type(),function); + if (mb) { + ret = mb->has_return() ? 1 : 0; + } else + ret = 1; //it is assumed that script always returns something + + if (call_mode==CALL_MODE_INSTANCE) { + ret++; + } + + return ret; + } +} + +String VisualScriptFunctionCall::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptFunctionCall::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE || call_mode==CALL_MODE_BASIC_TYPE) { + if (p_idx==0) { + PropertyInfo pi; + pi.type=(call_mode==CALL_MODE_INSTANCE?Variant::OBJECT:basic_type); + pi.name=(call_mode==CALL_MODE_INSTANCE?String("instance"):Variant::get_type_name(basic_type).to_lower()); + return pi; + } else { + p_idx--; + } + } + + if (rpc_call_mode>=RPC_RELIABLE_TO_ID) { + + if (p_idx==0) { + return PropertyInfo(Variant::INT,"peer_id"); + } else { + p_idx--; + } + + } + +#ifdef DEBUG_METHODS_ENABLED + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + + Vector<StringName> names = Variant::get_method_argument_names(basic_type,function); + Vector<Variant::Type> types = Variant::get_method_argument_types(basic_type,function); + return PropertyInfo(types[p_idx],names[p_idx]); + + } else { + + MethodBind *mb = ClassDB::get_method(_get_base_type(),function); + if (mb) { + return mb->get_argument_info(p_idx); + } + + if (p_idx>=0 && p_idx < method_cache.arguments.size()) { + return method_cache.arguments[p_idx]; + } + + return PropertyInfo(); + } +#else + return PropertyInfo(); +#endif + +} + +PropertyInfo VisualScriptFunctionCall::get_output_value_port_info(int p_idx) const{ + + +#ifdef DEBUG_METHODS_ENABLED + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + + return PropertyInfo(Variant::get_method_return_type(basic_type,function),""); + } else { + + if (call_mode==CALL_MODE_INSTANCE) { + if (p_idx==0) { + return PropertyInfo(Variant::OBJECT,"pass"); + } else { + p_idx--; + } + } + + PropertyInfo ret; + + /*MethodBind *mb = ClassDB::get_method(_get_base_type(),function); + if (mb) { + + ret = mb->get_argument_info(-1); + } else {*/ + + ret = method_cache.return_val; + + //} + + if (call_mode==CALL_MODE_INSTANCE) { + ret.name="return"; + } else { + ret.name=""; + } + return ret; + + + } +#else + return PropertyInfo(); +#endif +} + + +String VisualScriptFunctionCall::get_caption() const { + + static const char*cname[5]= { + "CallSelf", + "CallNode", + "CallInstance", + "CallBasic", + "CallSingleton" + }; + + String caption = cname[call_mode]; + + if (rpc_call_mode) { + caption+=" (RPC)"; + } + + return caption; +} + +String VisualScriptFunctionCall::get_text() const { + + if (call_mode==CALL_MODE_SELF) + return " "+String(function)+"()"; + if (call_mode==CALL_MODE_SINGLETON) + return String(singleton)+":"+String(function)+"()"; + else if (call_mode==CALL_MODE_BASIC_TYPE) + return Variant::get_type_name(basic_type)+"."+String(function)+"()"; + else if (call_mode==CALL_MODE_NODE_PATH) + return " ["+String(base_path.simplified())+"]."+String(function)+"()"; + else + return " "+base_type+"."+String(function)+"()"; + +} + + +void VisualScriptFunctionCall::set_basic_type(Variant::Type p_type) { + + if (basic_type==p_type) + return; + basic_type=p_type; + + + _change_notify(); + ports_changed_notify(); +} + +Variant::Type VisualScriptFunctionCall::get_basic_type() const{ + + return basic_type; +} + +void VisualScriptFunctionCall::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptFunctionCall::get_base_type() const{ + + return base_type; +} + +void VisualScriptFunctionCall::set_base_script(const String& p_path) { + + if (base_script==p_path) + return; + + base_script=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptFunctionCall::get_base_script() const { + + return base_script; +} + +void VisualScriptFunctionCall::set_singleton(const StringName& p_path) { + + if (singleton==p_path) + return; + + singleton=p_path; + Object *obj = GlobalConfig::get_singleton()->get_singleton_object(singleton); + if (obj) { + base_type=obj->get_class(); + } + + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptFunctionCall::get_singleton() const { + + return singleton; +} + + + +void VisualScriptFunctionCall::_update_method_cache() { + StringName type; + Ref<Script> script; + + if (call_mode==CALL_MODE_NODE_PATH) { + + Node* node=_get_base_node(); + if (node) { + type=node->get_class(); + base_type=type; //cache, too + script = node->get_script(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + type=get_visual_script()->get_instance_base_type(); + base_type=type; //cache, too + script=get_visual_script(); + } + + } else if (call_mode==CALL_MODE_SINGLETON) { + + Object *obj = GlobalConfig::get_singleton()->get_singleton_object(singleton); + if (obj) { + type=obj->get_class(); + script=obj->get_script(); + } + + } else if (call_mode==CALL_MODE_INSTANCE) { + + type=base_type; + if (base_script!=String()) { + + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + script = Ref<Resource>( ResourceCache::get(base_script) ); + } else { + return; + } + } + } + + + //print_line("BASE: "+String(type)+" FUNC: "+String(function)); + MethodBind *mb = ClassDB::get_method(type,function); + if (mb) { + use_default_args=mb->get_default_argument_count(); + method_cache = MethodInfo(); + for(int i=0;i<mb->get_argument_count();i++) { +#ifdef DEBUG_METHODS_ENABLED + method_cache.arguments.push_back(mb->get_argument_info(i)); +#else + method_cache.arguments.push_back(PropertyInfo()); +#endif + } + + if (mb->is_const()) { + method_cache.flags|=METHOD_FLAG_CONST; + } + +#ifdef DEBUG_METHODS_ENABLED + + method_cache.return_val = mb->get_argument_info(-1); +#endif + + if (mb->is_vararg()) { + //for vararg just give it 10 arguments (should be enough for most use cases) + for(int i=0;i<10;i++) { + method_cache.arguments.push_back(PropertyInfo(Variant::NIL,"arg"+itos(i))); + use_default_args++; + } + } + } else if (script.is_valid() && script->has_method(function)) { + + method_cache = script->get_method_info(function); + use_default_args=method_cache.default_arguments.size(); + } +} + +void VisualScriptFunctionCall::set_function(const StringName& p_type){ + + if (function==p_type) + return; + + function=p_type; + + if (call_mode==CALL_MODE_BASIC_TYPE) { + use_default_args = Variant::get_method_default_arguments(basic_type,function).size(); + } else { + //update all caches + + _update_method_cache(); + + } + + + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptFunctionCall::get_function() const { + + + return function; +} + +void VisualScriptFunctionCall::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptFunctionCall::get_base_path() const { + + return base_path; +} + + +void VisualScriptFunctionCall::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + _change_notify(); + ports_changed_notify(); + +} +VisualScriptFunctionCall::CallMode VisualScriptFunctionCall::get_call_mode() const { + + return call_mode; +} + +void VisualScriptFunctionCall::set_use_default_args(int p_amount) { + + if (use_default_args==p_amount) + return; + + use_default_args=p_amount; + ports_changed_notify(); + + +} + +void VisualScriptFunctionCall::set_rpc_call_mode(VisualScriptFunctionCall::RPCCallMode p_mode) { + + if (rpc_call_mode==p_mode) + return; + rpc_call_mode=p_mode; + ports_changed_notify(); + _change_notify(); +} + +VisualScriptFunctionCall::RPCCallMode VisualScriptFunctionCall::get_rpc_call_mode() const{ + + return rpc_call_mode; +} + + +int VisualScriptFunctionCall::get_use_default_args() const{ + + return use_default_args; +} + + +void VisualScriptFunctionCall::set_validate(bool p_amount) { + + validate=p_amount; +} + +bool VisualScriptFunctionCall::get_validate() const { + + return validate; +} + + +void VisualScriptFunctionCall::_set_argument_cache(const Dictionary& p_cache) { + //so everything works in case all else fails + method_cache=MethodInfo::from_dict(p_cache); + +} + +Dictionary VisualScriptFunctionCall::_get_argument_cache() const { + + return method_cache; +} + +void VisualScriptFunctionCall::_validate_property(PropertyInfo& property) const { + + if (property.name=="function/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + if (property.name=="function/base_script") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=0; + } + } + + if (property.name=="function/basic_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + + if (property.name=="function/singleton") { + if (call_mode!=CALL_MODE_SINGLETON) { + property.usage=0; + } else { + List<GlobalConfig::Singleton> names; + GlobalConfig::get_singleton()->get_singletons(&names); + property.hint=PROPERTY_HINT_ENUM; + String sl; + for (List<GlobalConfig::Singleton>::Element *E=names.front();E;E=E->next()) { + if (sl!=String()) + sl+=","; + sl+=E->get().name; + } + property.hint_string=sl; + + } + } + + if (property.name=="function/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="function/function") { + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + property.hint=PROPERTY_HINT_METHOD_OF_VARIANT_TYPE; + property.hint_string=Variant::get_type_name(basic_type); + + } else if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) { + property.hint=PROPERTY_HINT_METHOD_OF_SCRIPT; + property.hint_string=itos(get_visual_script()->get_instance_ID()); + } else if (call_mode==CALL_MODE_SINGLETON) { + + Object *obj = GlobalConfig::get_singleton()->get_singleton_object(singleton); + if (obj) { + property.hint=PROPERTY_HINT_METHOD_OF_INSTANCE; + property.hint_string=itos(obj->get_instance_ID()); + } else { + + property.hint=PROPERTY_HINT_METHOD_OF_BASE_TYPE; + property.hint_string=base_type;//should be cached + } + } else if (call_mode==CALL_MODE_INSTANCE) { + property.hint=PROPERTY_HINT_METHOD_OF_BASE_TYPE; + property.hint_string=base_type; + + if (base_script!=String()) { + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + Ref<Script> script = Ref<Resource>( ResourceCache::get(base_script) ); + if (script.is_valid()) { + + property.hint=PROPERTY_HINT_METHOD_OF_SCRIPT; + property.hint_string=itos(script->get_instance_ID()); + } + } + } + + } else if (call_mode==CALL_MODE_NODE_PATH) { + Node *node = _get_base_node(); + if (node) { + property.hint=PROPERTY_HINT_METHOD_OF_INSTANCE; + property.hint_string=itos(node->get_instance_ID()); + } else { + property.hint=PROPERTY_HINT_METHOD_OF_BASE_TYPE; + property.hint_string=get_base_type(); + } + + } + + } + + if (property.name=="function/use_default_args") { + + property.hint=PROPERTY_HINT_RANGE; + + int mc=0; + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + mc = Variant::get_method_default_arguments(basic_type,function).size(); + } else { + MethodBind *mb = ClassDB::get_method(_get_base_type(),function); + if (mb) { + + mc=mb->get_default_argument_count(); + } + } + + if (mc==0) { + property.usage=0; //do not show + } else { + + property.hint_string="0,"+itos(mc)+",1"; + } + } + + if (property.name=="rpc/call_mode") { + if (call_mode==CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + +} + + +void VisualScriptFunctionCall::_bind_methods() { + + ClassDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptFunctionCall::set_base_type); + ClassDB::bind_method(_MD("get_base_type"),&VisualScriptFunctionCall::get_base_type); + + ClassDB::bind_method(_MD("set_base_script","base_script"),&VisualScriptFunctionCall::set_base_script); + ClassDB::bind_method(_MD("get_base_script"),&VisualScriptFunctionCall::get_base_script); + + ClassDB::bind_method(_MD("set_basic_type","basic_type"),&VisualScriptFunctionCall::set_basic_type); + ClassDB::bind_method(_MD("get_basic_type"),&VisualScriptFunctionCall::get_basic_type); + + ClassDB::bind_method(_MD("set_singleton","singleton"),&VisualScriptFunctionCall::set_singleton); + ClassDB::bind_method(_MD("get_singleton"),&VisualScriptFunctionCall::get_singleton); + + ClassDB::bind_method(_MD("set_function","function"),&VisualScriptFunctionCall::set_function); + ClassDB::bind_method(_MD("get_function"),&VisualScriptFunctionCall::get_function); + + ClassDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptFunctionCall::set_call_mode); + ClassDB::bind_method(_MD("get_call_mode"),&VisualScriptFunctionCall::get_call_mode); + + ClassDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptFunctionCall::set_base_path); + ClassDB::bind_method(_MD("get_base_path"),&VisualScriptFunctionCall::get_base_path); + + ClassDB::bind_method(_MD("set_use_default_args","amount"),&VisualScriptFunctionCall::set_use_default_args); + ClassDB::bind_method(_MD("get_use_default_args"),&VisualScriptFunctionCall::get_use_default_args); + + ClassDB::bind_method(_MD("_set_argument_cache","argument_cache"),&VisualScriptFunctionCall::_set_argument_cache); + ClassDB::bind_method(_MD("_get_argument_cache"),&VisualScriptFunctionCall::_get_argument_cache); + + ClassDB::bind_method(_MD("set_rpc_call_mode","mode"),&VisualScriptFunctionCall::set_rpc_call_mode); + ClassDB::bind_method(_MD("get_rpc_call_mode"),&VisualScriptFunctionCall::get_rpc_call_mode); + + ClassDB::bind_method(_MD("set_validate","enable"),&VisualScriptFunctionCall::set_validate); + ClassDB::bind_method(_MD("get_validate"),&VisualScriptFunctionCall::get_validate); + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + + List<String> script_extensions; + for(int i=0;i<ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="*."+E->get(); + } + + + + ADD_PROPERTY(PropertyInfo(Variant::INT,"function/call_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance,Basic Type,Singleton"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/singleton"),_SCS("set_singleton"),_SCS("get_singleton")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"function/basic_type",PROPERTY_HINT_ENUM,bt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"function/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY,"function/argument_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_argument_cache"),_SCS("_get_argument_cache")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"function/function"),_SCS("set_function"),_SCS("get_function")); //when set, if loaded properly, will override argument count. + ADD_PROPERTY(PropertyInfo(Variant::INT,"function/use_default_args"),_SCS("set_use_default_args"),_SCS("get_use_default_args")); + ADD_PROPERTY(PropertyInfo(Variant::BOOL,"function/validate"),_SCS("set_validate"),_SCS("get_validate")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"rpc/call_mode",PROPERTY_HINT_ENUM,"Disabled,Reliable,Unreliable,ReliableToID,UnreliableToID"),_SCS("set_rpc_call_mode"),_SCS("get_rpc_call_mode")); //when set, if loaded properly, will override argument count. + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); + BIND_CONSTANT( CALL_MODE_BASIC_TYPE ); +} + +class VisualScriptNodeInstanceFunctionCall : public VisualScriptNodeInstance { +public: + + + VisualScriptFunctionCall::CallMode call_mode; + NodePath node_path; + int input_args; + bool validate; + bool returns; + VisualScriptFunctionCall::RPCCallMode rpc_mode; + StringName function; + StringName singleton; + + VisualScriptFunctionCall *node; + VisualScriptInstance *instance; + + + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + + _FORCE_INLINE_ bool call_rpc(Object* p_base,const Variant** p_args,int p_argcount) { + + if (!p_base) + return false; + + Node * node = p_base->cast_to<Node>(); + if (!node) + return false; + + int to_id=0; + bool reliable=true; + + if (rpc_mode>=VisualScriptFunctionCall::RPC_RELIABLE_TO_ID) { + to_id = *p_args[0]; + p_args+=1; + p_argcount-=1; + if (rpc_mode==VisualScriptFunctionCall::RPC_UNRELIABLE_TO_ID) { + reliable=false; + } + } else if (rpc_mode==VisualScriptFunctionCall::RPC_UNRELIABLE) { + reliable=false; + } + + node->rpcp(to_id,!reliable,function,p_args,p_argcount); + + return true; + } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + switch(call_mode) { + + case VisualScriptFunctionCall::CALL_MODE_SELF: { + + Object *object=instance->get_owner_ptr(); + + if (rpc_mode) { + call_rpc(object,p_inputs,input_args); + } else if (returns) { + *p_outputs[0] = object->call(function,p_inputs,input_args,r_error); + } else { + object->call(function,p_inputs,input_args,r_error); + } + } break; + case VisualScriptFunctionCall::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + if (rpc_mode) { + call_rpc(node,p_inputs,input_args); + } else if (returns) { + *p_outputs[0] = another->call(function,p_inputs,input_args,r_error); + } else { + another->call(function,p_inputs,input_args,r_error); + } + + } break; + case VisualScriptFunctionCall::CALL_MODE_INSTANCE: + case VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE: { + + Variant v = *p_inputs[0]; + + if (rpc_mode) { + Object *obj = v; + if (obj) { + call_rpc(obj,p_inputs+1,input_args-1); + } + } else if (returns) { + if (call_mode==VisualScriptFunctionCall::CALL_MODE_INSTANCE) { + *p_outputs[1] = v.call(function,p_inputs+1,input_args,r_error); + } else { + *p_outputs[0] = v.call(function,p_inputs+1,input_args,r_error); + } + } else { + v.call(function,p_inputs+1,input_args,r_error); + } + + if (call_mode==VisualScriptFunctionCall::CALL_MODE_INSTANCE) { + *p_outputs[0]=*p_inputs[0]; + } + + } break; + case VisualScriptFunctionCall::CALL_MODE_SINGLETON: { + + Object *object=GlobalConfig::get_singleton()->get_singleton_object(singleton); + if (!object) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid singleton name: '"+String(singleton)+"'"; + return 0; + } + + if (rpc_mode) { + call_rpc(object,p_inputs,input_args); + } else if (returns) { + *p_outputs[0] = object->call(function,p_inputs,input_args,r_error); + } else { + object->call(function,p_inputs,input_args,r_error); + } + } break; + + } + + if (!validate) { + + //ignore call errors if validation is disabled + r_error.error=Variant::CallError::CALL_OK; + r_error_str=String(); + } + + return 0; + + } + + +}; + +VisualScriptNodeInstance* VisualScriptFunctionCall::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceFunctionCall * instance = memnew(VisualScriptNodeInstanceFunctionCall ); + instance->node=this; + instance->instance=p_instance; + instance->singleton=singleton; + instance->function=function; + instance->call_mode=call_mode; + instance->returns=get_output_value_port_count(); + instance->node_path=base_path; + instance->input_args = get_input_value_port_count() - ( (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE) ? 1: 0 ); + instance->rpc_mode=rpc_call_mode; + instance->validate=validate; + return instance; +} + + +VisualScriptFunctionCall::TypeGuess VisualScriptFunctionCall::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + if (p_output==0 && call_mode==CALL_MODE_INSTANCE) { + return p_inputs[0]; + } + + return VisualScriptNode::guess_output_type(p_inputs,p_output); + +} + +VisualScriptFunctionCall::VisualScriptFunctionCall() { + + validate=true; + call_mode=CALL_MODE_SELF; + basic_type=Variant::NIL; + use_default_args=0; + base_type="Object"; + rpc_call_mode=RPC_DISABLED; + + +} + +template<VisualScriptFunctionCall::CallMode cmode> +static Ref<VisualScriptNode> create_function_call_node(const String& p_name) { + + Ref<VisualScriptFunctionCall> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + + +////////////////////////////////////////// +////////////////SET////////////////////// +////////////////////////////////////////// + +static const char* event_type_names[InputEvent::TYPE_MAX]={ + "None", + "Key", + "MouseMotion", + "MouseButton", + "JoypadMotion", + "JoypadButton", + "ScreenTouch", + "ScreenDrag", + "Action" +}; + + +int VisualScriptPropertySet::get_output_sequence_port_count() const { + + return call_mode!=CALL_MODE_BASIC_TYPE ? 1 : 0; +} + +bool VisualScriptPropertySet::has_input_sequence_port() const{ + + return call_mode!=CALL_MODE_BASIC_TYPE ? true : false; +} + +Node *VisualScriptPropertySet::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptPropertySet::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_class(); + + } + + return base_type; +} + +int VisualScriptPropertySet::get_input_value_port_count() const{ + + int pc = (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE)?2:1; + + return pc; +} +int VisualScriptPropertySet::get_output_value_port_count() const{ + + return (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE) ? 1 : 0; +} + +String VisualScriptPropertySet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE || call_mode==CALL_MODE_BASIC_TYPE) { + if (p_idx==0) { + PropertyInfo pi; + pi.type=(call_mode==CALL_MODE_INSTANCE?Variant::OBJECT:basic_type); + pi.name=(call_mode==CALL_MODE_INSTANCE?String("instance"):Variant::get_type_name(basic_type).to_lower()); + return pi; + } else { + p_idx--; + } + } + + PropertyInfo pinfo=type_cache; + pinfo.name="value"; + return pinfo; +} + +PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) const{ + if (call_mode==CALL_MODE_BASIC_TYPE) { + return PropertyInfo(basic_type,"out"); + } else if (call_mode==CALL_MODE_INSTANCE) { + return PropertyInfo(Variant::OBJECT,"pass"); + } else { + return PropertyInfo(); + } + +} + + +String VisualScriptPropertySet::get_caption() const { + + static const char*cname[4]= { + "SelfSet", + "NodeSet", + "InstanceSet", + "BasicSet" + }; + + return cname[call_mode]; +} + +String VisualScriptPropertySet::get_text() const { + + String prop; + + if (call_mode==CALL_MODE_BASIC_TYPE) + prop=Variant::get_type_name(basic_type)+"."+property; + else if (call_mode==CALL_MODE_NODE_PATH) + prop=String(base_path)+":"+property; + else if (call_mode==CALL_MODE_SELF) + prop=property; + else if (call_mode==CALL_MODE_INSTANCE) + prop=String(base_type)+":"+property; + + return prop; + +} + +void VisualScriptPropertySet::_update_base_type() { + //cache it because this information may not be available on load + if (call_mode==CALL_MODE_NODE_PATH) { + + Node* node=_get_base_node(); + if (node) { + base_type=node->get_class(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + base_type=get_visual_script()->get_instance_base_type(); + } + } + +} +void VisualScriptPropertySet::set_basic_type(Variant::Type p_type) { + + if (basic_type==p_type) + return; + basic_type=p_type; + + + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +Variant::Type VisualScriptPropertySet::get_basic_type() const{ + + return basic_type; +} + +void VisualScriptPropertySet::set_event_type(InputEvent::Type p_type) { + + if (event_type==p_type) + return; + event_type=p_type; + if (call_mode==CALL_MODE_BASIC_TYPE) { + _update_cache(); + } + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +InputEvent::Type VisualScriptPropertySet::get_event_type() const{ + + return event_type; +} + + +void VisualScriptPropertySet::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptPropertySet::get_base_type() const{ + + return base_type; +} + + +void VisualScriptPropertySet::set_base_script(const String& p_path) { + + if (base_script==p_path) + return; + + base_script=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptPropertySet::get_base_script() const { + + return base_script; +} + + +void VisualScriptPropertySet::_update_cache() { + + + if (!OS::get_singleton()->get_main_loop()) + return; + if (!OS::get_singleton()->get_main_loop()->cast_to<SceneTree>()) + return; + + if (!OS::get_singleton()->get_main_loop()->cast_to<SceneTree>()->is_editor_hint()) //only update cache if editor exists, it's pointless otherwise + return; + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + //not super efficient.. + + Variant v; + if (basic_type==Variant::INPUT_EVENT) { + InputEvent ev; + ev.type=event_type; + v=ev; + } else { + Variant::CallError ce; + v = Variant::construct(basic_type,NULL,0,ce); + } + + List<PropertyInfo> pinfo; + v.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + if (E->get().name==property) { + + type_cache=E->get(); + } + } + + } else { + + + StringName type; + Ref<Script> script; + Node *node=NULL; + + if (call_mode==CALL_MODE_NODE_PATH) { + + node=_get_base_node(); + if (node) { + type=node->get_class(); + base_type=type; //cache, too + script = node->get_script(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + type=get_visual_script()->get_instance_base_type(); + base_type=type; //cache, too + script=get_visual_script(); + } + } else if (call_mode==CALL_MODE_INSTANCE) { + + type=base_type; + if (base_script!=String()) { + + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + script = Ref<Resource>( ResourceCache::get(base_script) ); + } else { + return; + } + } + } + + List<PropertyInfo> pinfo; + + + if (node) { + + node->get_property_list(&pinfo); + } else { + ClassDB::get_property_list(type,&pinfo); + } + + if (script.is_valid()) { + + script->get_script_property_list(&pinfo); + } + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + if (E->get().name==property) { + type_cache=E->get(); + return; + } + } + + } +} + +void VisualScriptPropertySet::set_property(const StringName& p_type){ + + if (property==p_type) + return; + + property=p_type; + _update_cache(); + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptPropertySet::get_property() const { + + + return property; +} + +void VisualScriptPropertySet::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + _update_base_type(); + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptPropertySet::get_base_path() const { + + return base_path; +} + + +void VisualScriptPropertySet::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + _update_base_type(); + _change_notify(); + ports_changed_notify(); + +} +VisualScriptPropertySet::CallMode VisualScriptPropertySet::get_call_mode() const { + + return call_mode; +} + + + + +void VisualScriptPropertySet::_set_type_cache(const Dictionary &p_type) { + type_cache=PropertyInfo::from_dict(p_type); +} + +Dictionary VisualScriptPropertySet::_get_type_cache() const { + + return type_cache; +} + +void VisualScriptPropertySet::_validate_property(PropertyInfo& property) const { + + if (property.name=="property/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + if (property.name=="property/base_script") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=0; + } + } + + if (property.name=="property/basic_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + + if (property.name=="property/event_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE || basic_type!=Variant::INPUT_EVENT) { + property.usage=0; + } + } + + if (property.name=="property/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="property/property") { + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE; + property.hint_string=Variant::get_type_name(basic_type); + + } else if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) { + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(get_visual_script()->get_instance_ID()); + } else if (call_mode==CALL_MODE_INSTANCE) { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=base_type; + + if (base_script!=String()) { + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + Ref<Script> script = Ref<Resource>( ResourceCache::get(base_script) ); + if (script.is_valid()) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(script->get_instance_ID()); + } + } + } + + } else if (call_mode==CALL_MODE_NODE_PATH) { + Node *node = _get_base_node(); + if (node) { + property.hint=PROPERTY_HINT_PROPERTY_OF_INSTANCE; + property.hint_string=itos(node->get_instance_ID()); + } else { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=get_base_type(); + } + + } + + } + +} + +void VisualScriptPropertySet::_bind_methods() { + + ClassDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptPropertySet::set_base_type); + ClassDB::bind_method(_MD("get_base_type"),&VisualScriptPropertySet::get_base_type); + + ClassDB::bind_method(_MD("set_base_script","base_script"),&VisualScriptPropertySet::set_base_script); + ClassDB::bind_method(_MD("get_base_script"),&VisualScriptPropertySet::get_base_script); + + ClassDB::bind_method(_MD("set_basic_type","basic_type"),&VisualScriptPropertySet::set_basic_type); + ClassDB::bind_method(_MD("get_basic_type"),&VisualScriptPropertySet::get_basic_type); + + ClassDB::bind_method(_MD("_set_type_cache","type_cache"),&VisualScriptPropertySet::_set_type_cache); + ClassDB::bind_method(_MD("_get_type_cache"),&VisualScriptPropertySet::_get_type_cache); + + ClassDB::bind_method(_MD("set_event_type","event_type"),&VisualScriptPropertySet::set_event_type); + ClassDB::bind_method(_MD("get_event_type"),&VisualScriptPropertySet::get_event_type); + + ClassDB::bind_method(_MD("set_property","property"),&VisualScriptPropertySet::set_property); + ClassDB::bind_method(_MD("get_property"),&VisualScriptPropertySet::get_property); + + ClassDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptPropertySet::set_call_mode); + ClassDB::bind_method(_MD("get_call_mode"),&VisualScriptPropertySet::get_call_mode); + + ClassDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptPropertySet::set_base_path); + ClassDB::bind_method(_MD("get_base_path"),&VisualScriptPropertySet::get_base_path); + + + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + String et; + for(int i=0;i<InputEvent::TYPE_MAX;i++) { + if (i>0) + et+=","; + + et+=event_type_names[i]; + } + + List<String> script_extensions; + for(int i=0;i<ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="*."+E->get(); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/set_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance,Basic Type"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/type_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_type_cache"),_SCS("_get_type_cache")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/basic_type",PROPERTY_HINT_ENUM,bt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/event_type",PROPERTY_HINT_ENUM,et),_SCS("set_event_type"),_SCS("get_event_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"property/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/property"),_SCS("set_property"),_SCS("get_property")); + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); + +} + +class VisualScriptNodeInstancePropertySet : public VisualScriptNodeInstance { +public: + + + VisualScriptPropertySet::CallMode call_mode; + NodePath node_path; + StringName property; + + VisualScriptPropertySet *node; + VisualScriptInstance *instance; + + + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + switch(call_mode) { + + case VisualScriptPropertySet::CALL_MODE_SELF: { + + Object *object=instance->get_owner_ptr(); + + bool valid; + + object->set(property,*p_inputs[0],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set value '"+String(*p_inputs[0])+"' on property '"+String(property)+"' of type "+object->get_class(); + } + } break; + case VisualScriptPropertySet::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + bool valid; + + another->set(property,*p_inputs[0],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set value '"+String(*p_inputs[0])+"' on property '"+String(property)+"' of type "+another->get_class(); + } + + } break; + case VisualScriptPropertySet::CALL_MODE_INSTANCE: + case VisualScriptPropertySet::CALL_MODE_BASIC_TYPE: { + + Variant v = *p_inputs[0]; + + bool valid; + + v.set(property,*p_inputs[1],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set value '"+String(*p_inputs[1])+"' ("+Variant::get_type_name(p_inputs[1]->get_type())+") on property '"+String(property)+"' of type "+Variant::get_type_name(v.get_type()); + } + + *p_outputs[0]=v; + + } break; + + } + return 0; + + } + + +}; + +VisualScriptNodeInstance* VisualScriptPropertySet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstancePropertySet * instance = memnew(VisualScriptNodeInstancePropertySet ); + instance->node=this; + instance->instance=p_instance; + instance->property=property; + instance->call_mode=call_mode; + instance->node_path=base_path; + return instance; +} + + + +VisualScriptPropertySet::TypeGuess VisualScriptPropertySet::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + if (p_output==0 && call_mode==CALL_MODE_INSTANCE) { + return p_inputs[0]; + } + + return VisualScriptNode::guess_output_type(p_inputs,p_output); + +} +VisualScriptPropertySet::VisualScriptPropertySet() { + + call_mode=CALL_MODE_SELF; + base_type="Object"; + basic_type=Variant::NIL; + event_type=InputEvent::NONE; + +} + +template<VisualScriptPropertySet::CallMode cmode> +static Ref<VisualScriptNode> create_property_set_node(const String& p_name) { + + Ref<VisualScriptPropertySet> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + + +////////////////////////////////////////// +////////////////GET////////////////////// +////////////////////////////////////////// + +int VisualScriptPropertyGet::get_output_sequence_port_count() const { + + return 0;// (call_mode==CALL_MODE_SELF || call_mode==CALL_MODE_NODE_PATH)?0:1; +} + +bool VisualScriptPropertyGet::has_input_sequence_port() const{ + + return false;//(call_mode==CALL_MODE_SELF || call_mode==CALL_MODE_NODE_PATH)?false:true; +} +void VisualScriptPropertyGet::_update_base_type() { + //cache it because this information may not be available on load + if (call_mode==CALL_MODE_NODE_PATH) { + + Node* node=_get_base_node(); + if (node) { + base_type=node->get_class(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + base_type=get_visual_script()->get_instance_base_type(); + } + } + +} +Node *VisualScriptPropertyGet::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptPropertyGet::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_class(); + + } + + return base_type; +} + + +int VisualScriptPropertyGet::get_input_value_port_count() const{ + + return (call_mode==CALL_MODE_BASIC_TYPE || call_mode==CALL_MODE_INSTANCE)?1:0; + +} +int VisualScriptPropertyGet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptPropertyGet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptPropertyGet::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE || call_mode==CALL_MODE_BASIC_TYPE) { + if (p_idx==0) { + PropertyInfo pi; + pi.type=(call_mode==CALL_MODE_INSTANCE?Variant::OBJECT:basic_type); + pi.name=(call_mode==CALL_MODE_INSTANCE?String("instance"):Variant::get_type_name(basic_type).to_lower()); + return pi; + } else { + p_idx--; + } + } + return PropertyInfo(); + +} + +PropertyInfo VisualScriptPropertyGet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type_cache,"value"); +} + + +String VisualScriptPropertyGet::get_caption() const { + + static const char*cname[4]= { + "SelfGet", + "NodeGet", + "InstanceGet", + "BasicGet" + }; + + return cname[call_mode]; +} + +String VisualScriptPropertyGet::get_text() const { + + String prop; + + if (call_mode==CALL_MODE_BASIC_TYPE) + prop=Variant::get_type_name(basic_type)+"."+property; + else if (call_mode==CALL_MODE_NODE_PATH) + prop=String(base_path)+":"+property; + else if (call_mode==CALL_MODE_SELF) + prop=property; + else if (call_mode==CALL_MODE_INSTANCE) + prop=String(base_type)+":"+property; + + return prop; +} + +void VisualScriptPropertyGet::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptPropertyGet::get_base_type() const{ + + return base_type; +} + +void VisualScriptPropertyGet::set_base_script(const String& p_path) { + + if (base_script==p_path) + return; + + base_script=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptPropertyGet::get_base_script() const { + + return base_script; +} + + +void VisualScriptPropertyGet::_update_cache() { + + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + //not super efficient.. + + Variant v; + if (basic_type==Variant::INPUT_EVENT) { + InputEvent ev; + ev.type=event_type; + v=ev; + } else { + Variant::CallError ce; + v = Variant::construct(basic_type,NULL,0,ce); + } + + List<PropertyInfo> pinfo; + v.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + if (E->get().name==property) { + + type_cache=E->get().type; + return; + } + } + + } else { + + + StringName type; + Ref<Script> script; + Node *node=NULL; + + if (call_mode==CALL_MODE_NODE_PATH) { + + node=_get_base_node(); + if (node) { + type=node->get_class(); + base_type=type; //cache, too + script = node->get_script(); + } + } else if (call_mode==CALL_MODE_SELF) { + + if (get_visual_script().is_valid()) { + type=get_visual_script()->get_instance_base_type(); + base_type=type; //cache, too + script=get_visual_script(); + } + } else if (call_mode==CALL_MODE_INSTANCE) { + + type=base_type; + if (base_script!=String()) { + + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + script = Ref<Resource>( ResourceCache::get(base_script) ); + } else { + return; + } + } + } + + + bool valid=false; + + Variant::Type type_ret; + + type_ret=ClassDB::get_property_type(base_type,property,&valid); + + if (valid) { + type_cache=type_ret; + return; //all dandy + } + + if (node) { + + Variant prop = node->get(property,&valid); + if (valid) { + type_cache=prop.get_type(); + return; //all dandy again + } + } + + if (script.is_valid()) { + + type_ret=script->get_static_property_type(property,&valid); + + if (valid) { + type_cache=type_ret; + return; //all dandy + } + } + } +} + +void VisualScriptPropertyGet::set_property(const StringName& p_type){ + + if (property==p_type) + return; + + property=p_type; + + + _update_cache(); + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptPropertyGet::get_property() const { + + + return property; +} + +void VisualScriptPropertyGet::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +NodePath VisualScriptPropertyGet::get_base_path() const { + + return base_path; +} + + +void VisualScriptPropertyGet::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + _change_notify(); + _update_base_type(); + ports_changed_notify(); + +} +VisualScriptPropertyGet::CallMode VisualScriptPropertyGet::get_call_mode() const { + + return call_mode; +} + + + +void VisualScriptPropertyGet::set_basic_type(Variant::Type p_type) { + + if (basic_type==p_type) + return; + basic_type=p_type; + + + _change_notify(); + ports_changed_notify(); +} + +Variant::Type VisualScriptPropertyGet::get_basic_type() const{ + + return basic_type; +} + + +void VisualScriptPropertyGet::set_event_type(InputEvent::Type p_type) { + + if (event_type==p_type) + return; + event_type=p_type; + if(call_mode==CALL_MODE_BASIC_TYPE) { + _update_cache(); + } + _change_notify(); + _update_base_type(); + ports_changed_notify(); +} + +InputEvent::Type VisualScriptPropertyGet::get_event_type() const{ + + return event_type; +} + + +void VisualScriptPropertyGet::_set_type_cache(Variant::Type p_type) { + type_cache=p_type; +} + +Variant::Type VisualScriptPropertyGet::_get_type_cache() const { + + return type_cache; +} + + +void VisualScriptPropertyGet::_validate_property(PropertyInfo& property) const { + + if (property.name=="property/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + if (property.name=="property/base_script") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=0; + } + } + + if (property.name=="property/basic_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE) { + property.usage=0; + } + } + if (property.name=="property/event_type") { + if (call_mode!=CALL_MODE_BASIC_TYPE || basic_type!=Variant::INPUT_EVENT) { + property.usage=0; + } + } + + if (property.name=="property/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="property/property") { + + if (call_mode==CALL_MODE_BASIC_TYPE) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE; + property.hint_string=Variant::get_type_name(basic_type); + + } else if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) { + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(get_visual_script()->get_instance_ID()); + } else if (call_mode==CALL_MODE_INSTANCE) { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=base_type; + + if (base_script!=String()) { + if (!ResourceCache::has(base_script) && ScriptServer::edit_request_func) { + + ScriptServer::edit_request_func(base_script); //make sure it's loaded + } + + if (ResourceCache::has(base_script)) { + + Ref<Script> script = Ref<Resource>( ResourceCache::get(base_script) ); + if (script.is_valid()) { + + property.hint=PROPERTY_HINT_PROPERTY_OF_SCRIPT; + property.hint_string=itos(script->get_instance_ID()); + } + } + } + } else if (call_mode==CALL_MODE_NODE_PATH) { + Node *node = _get_base_node(); + if (node) { + property.hint=PROPERTY_HINT_PROPERTY_OF_INSTANCE; + property.hint_string=itos(node->get_instance_ID()); + } else { + property.hint=PROPERTY_HINT_PROPERTY_OF_BASE_TYPE; + property.hint_string=get_base_type(); + } + + } + + } + +} + +void VisualScriptPropertyGet::_bind_methods() { + + ClassDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptPropertyGet::set_base_type); + ClassDB::bind_method(_MD("get_base_type"),&VisualScriptPropertyGet::get_base_type); + + ClassDB::bind_method(_MD("set_base_script","base_script"),&VisualScriptPropertyGet::set_base_script); + ClassDB::bind_method(_MD("get_base_script"),&VisualScriptPropertyGet::get_base_script); + + ClassDB::bind_method(_MD("set_basic_type","basic_type"),&VisualScriptPropertyGet::set_basic_type); + ClassDB::bind_method(_MD("get_basic_type"),&VisualScriptPropertyGet::get_basic_type); + + ClassDB::bind_method(_MD("_set_type_cache","type_cache"),&VisualScriptPropertyGet::_set_type_cache); + ClassDB::bind_method(_MD("_get_type_cache"),&VisualScriptPropertyGet::_get_type_cache); + + ClassDB::bind_method(_MD("set_event_type","event_type"),&VisualScriptPropertyGet::set_event_type); + ClassDB::bind_method(_MD("get_event_type"),&VisualScriptPropertyGet::get_event_type); + + + ClassDB::bind_method(_MD("set_property","property"),&VisualScriptPropertyGet::set_property); + ClassDB::bind_method(_MD("get_property"),&VisualScriptPropertyGet::get_property); + + ClassDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptPropertyGet::set_call_mode); + ClassDB::bind_method(_MD("get_call_mode"),&VisualScriptPropertyGet::get_call_mode); + + ClassDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptPropertyGet::set_base_path); + ClassDB::bind_method(_MD("get_base_path"),&VisualScriptPropertyGet::get_base_path); + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + String et; + for(int i=0;i<InputEvent::TYPE_MAX;i++) { + if (i>0) + et+=","; + + et+=event_type_names[i]; + } + + List<String> script_extensions; + for(int i=0;i<ScriptServer::get_language_count();i++) { + ScriptServer::get_language(i)->get_recognized_extensions(&script_extensions); + } + + String script_ext_hint; + for (List<String>::Element *E=script_extensions.front();E;E=E->next()) { + if (script_ext_hint!=String()) + script_ext_hint+=","; + script_ext_hint+="."+E->get(); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/set_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance,Basic Type"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/base_script",PROPERTY_HINT_FILE,script_ext_hint),_SCS("set_base_script"),_SCS("get_base_script")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/type_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_type_cache"),_SCS("_get_type_cache")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/basic_type",PROPERTY_HINT_ENUM,bt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"property/event_type",PROPERTY_HINT_ENUM,et),_SCS("set_event_type"),_SCS("get_event_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"property/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"property/property"),_SCS("set_property"),_SCS("get_property")); + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); +} + +class VisualScriptNodeInstancePropertyGet : public VisualScriptNodeInstance { +public: + + + VisualScriptPropertyGet::CallMode call_mode; + NodePath node_path; + StringName property; + + VisualScriptPropertyGet *node; + VisualScriptInstance *instance; + + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + + switch(call_mode) { + + case VisualScriptPropertyGet::CALL_MODE_SELF: { + + Object *object=instance->get_owner_ptr(); + + bool valid; + + *p_outputs[0] = object->get(property,&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Invalid index property name."); + return 0; + } + } break; + case VisualScriptPropertyGet::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Base object is not a Node!"); + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Path does not lead Node!"); + return 0; + } + + bool valid; + + + *p_outputs[0] = another->get(property,&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=vformat(RTR("Invalid index property name '%s' in node %s."),String(property),another->get_name()); + return 0; + } + + } break; + default: { + + bool valid; + Variant v = *p_inputs[0]; + + *p_outputs[0] = v.get(property,&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("Invalid index property name."); + + } + }; + } + + return 0; + } + + + + +}; + +VisualScriptNodeInstance* VisualScriptPropertyGet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstancePropertyGet * instance = memnew(VisualScriptNodeInstancePropertyGet ); + instance->node=this; + instance->instance=p_instance; + instance->property=property; + instance->call_mode=call_mode; + instance->node_path=base_path; + + return instance; +} + +VisualScriptPropertyGet::VisualScriptPropertyGet() { + + call_mode=CALL_MODE_SELF; + base_type="Object"; + basic_type=Variant::NIL; + event_type=InputEvent::NONE; + type_cache=Variant::NIL; + +} + +template<VisualScriptPropertyGet::CallMode cmode> +static Ref<VisualScriptNode> create_property_get_node(const String& p_name) { + + Ref<VisualScriptPropertyGet> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + + +////////////////////////////////////////// +////////////////EMIT////////////////////// +////////////////////////////////////////// + +int VisualScriptEmitSignal::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptEmitSignal::has_input_sequence_port() const{ + + return true; +} + + +int VisualScriptEmitSignal::get_input_value_port_count() const{ + + Ref<VisualScript> vs = get_visual_script(); + if (vs.is_valid()) { + + if (!vs->has_custom_signal(name)) + return 0; + + return vs->custom_signal_get_argument_count(name); + } + + return 0; + +} +int VisualScriptEmitSignal::get_output_value_port_count() const{ + return 0; +} + +String VisualScriptEmitSignal::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptEmitSignal::get_input_value_port_info(int p_idx) const{ + + Ref<VisualScript> vs = get_visual_script(); + if (vs.is_valid()) { + + if (!vs->has_custom_signal(name)) + return PropertyInfo(); + + return PropertyInfo(vs->custom_signal_get_argument_type(name,p_idx),vs->custom_signal_get_argument_name(name,p_idx)); + } + + return PropertyInfo(); + +} + +PropertyInfo VisualScriptEmitSignal::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptEmitSignal::get_caption() const { + + return "EmitSignal"; +} + +String VisualScriptEmitSignal::get_text() const { + + return "emit "+String(name); +} + + + +void VisualScriptEmitSignal::set_signal(const StringName& p_type){ + + if (name==p_type) + return; + + name=p_type; + + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptEmitSignal::get_signal() const { + + + return name; +} + + +void VisualScriptEmitSignal::_validate_property(PropertyInfo& property) const { + + + + if (property.name=="signal/signal") { + property.hint=PROPERTY_HINT_ENUM; + + + List<StringName> sigs; + + Ref<VisualScript> vs = get_visual_script(); + if (vs.is_valid()) { + + vs->get_custom_signal_list(&sigs); + + } + + String ml; + for (List<StringName>::Element *E=sigs.front();E;E=E->next()) { + + if (ml!=String()) + ml+=","; + ml+=E->get(); + } + + property.hint_string=ml; + } + +} + + +void VisualScriptEmitSignal::_bind_methods() { + + ClassDB::bind_method(_MD("set_signal","name"),&VisualScriptEmitSignal::set_signal); + ClassDB::bind_method(_MD("get_signal"),&VisualScriptEmitSignal::get_signal); + + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"signal/signal"),_SCS("set_signal"),_SCS("get_signal")); + + +} + +class VisualScriptNodeInstanceEmitSignal : public VisualScriptNodeInstance { +public: + + VisualScriptEmitSignal *node; + VisualScriptInstance *instance; + int argcount; + StringName name; + + //virtual int get_working_memory_size() const { return 0; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + Object *obj = instance->get_owner_ptr(); + + obj->emit_signal(name,p_inputs,argcount); + + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptEmitSignal::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceEmitSignal * instance = memnew(VisualScriptNodeInstanceEmitSignal ); + instance->node=this; + instance->instance=p_instance; + instance->name=name; + instance->argcount=get_input_value_port_count(); + return instance; +} + +VisualScriptEmitSignal::VisualScriptEmitSignal() { +} + + + +static Ref<VisualScriptNode> create_basic_type_call_node(const String& p_name) { + + Vector<String> path = p_name.split("/"); + ERR_FAIL_COND_V(path.size()<4,Ref<VisualScriptNode>()); + String base_type = path[2]; + String method = path[3]; + + Ref<VisualScriptFunctionCall> node; + node.instance(); + + Variant::Type type=Variant::VARIANT_MAX; + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + + if (Variant::get_type_name(Variant::Type(i))==base_type) { + type=Variant::Type(i); + break; + } + } + + ERR_FAIL_COND_V(type==Variant::VARIANT_MAX,Ref<VisualScriptNode>()); + + + node->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + node->set_basic_type(type); + node->set_function(method); + + return node; +} + + +void register_visual_script_func_nodes() { + + VisualScriptLanguage::singleton->add_register_func("functions/call",create_node_generic<VisualScriptFunctionCall>); + VisualScriptLanguage::singleton->add_register_func("functions/set",create_node_generic<VisualScriptPropertySet>); + VisualScriptLanguage::singleton->add_register_func("functions/get",create_node_generic<VisualScriptPropertyGet>); + + //VisualScriptLanguage::singleton->add_register_func("functions/call_script/call_self",create_script_call_node<VisualScriptScriptCall::CALL_MODE_SELF>); + //VisualScriptLanguage::singleton->add_register_func("functions/call_script/call_node",create_script_call_node<VisualScriptScriptCall::CALL_MODE_NODE_PATH>); + VisualScriptLanguage::singleton->add_register_func("functions/emit_signal",create_node_generic<VisualScriptEmitSignal>); + + + for(int i=0;i<Variant::VARIANT_MAX;i++) { + + Variant::Type t = Variant::Type(i); + String type_name = Variant::get_type_name(t); + Variant::CallError ce; + Variant vt = Variant::construct(t,NULL,0,ce); + List<MethodInfo> ml; + vt.get_method_list(&ml); + + for (List<MethodInfo>::Element *E=ml.front();E;E=E->next()) { + VisualScriptLanguage::singleton->add_register_func("functions/by_type/"+type_name+"/"+E->get().name,create_basic_type_call_node); + } + } +} diff --git a/modules/visual_script/visual_script_func_nodes.h b/modules/visual_script/visual_script_func_nodes.h new file mode 100644 index 000000000..7d33549e2 --- /dev/null +++ b/modules/visual_script/visual_script_func_nodes.h @@ -0,0 +1,349 @@ +#ifndef VISUAL_SCRIPT_FUNC_NODES_H +#define VISUAL_SCRIPT_FUNC_NODES_H + +#include "visual_script.h" + + +class VisualScriptFunctionCall : public VisualScriptNode { + + GDCLASS(VisualScriptFunctionCall,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + CALL_MODE_BASIC_TYPE, + CALL_MODE_SINGLETON, + }; + + enum RPCCallMode { + RPC_DISABLED, + RPC_RELIABLE, + RPC_UNRELIABLE, + RPC_RELIABLE_TO_ID, + RPC_UNRELIABLE_TO_ID + }; + +private: + + CallMode call_mode; + StringName base_type; + String base_script; + Variant::Type basic_type; + NodePath base_path; + StringName function; + int use_default_args; + RPCCallMode rpc_call_mode; + StringName singleton; + bool validate; + + + Node *_get_base_node() const; + StringName _get_base_type() const; + + MethodInfo method_cache; + void _update_method_cache(); + + void _set_argument_cache(const Dictionary& p_args); + Dictionary _get_argument_cache() const; + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_basic_type(Variant::Type p_type); + Variant::Type get_basic_type() const; + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + void set_singleton(const StringName& p_type); + StringName get_singleton() const; + + void set_function(const StringName& p_type); + StringName get_function() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + void set_use_default_args(int p_amount); + int get_use_default_args() const; + + void set_validate(bool p_amount); + bool get_validate() const; + + void set_rpc_call_mode(RPCCallMode p_mode); + RPCCallMode get_rpc_call_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + + VisualScriptFunctionCall(); +}; + +VARIANT_ENUM_CAST(VisualScriptFunctionCall::CallMode ); +VARIANT_ENUM_CAST(VisualScriptFunctionCall::RPCCallMode ); + + +class VisualScriptPropertySet : public VisualScriptNode { + + GDCLASS(VisualScriptPropertySet,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + CALL_MODE_BASIC_TYPE, + + + }; +private: + + PropertyInfo type_cache; + + CallMode call_mode; + Variant::Type basic_type; + StringName base_type; + String base_script; + NodePath base_path; + StringName property; + InputEvent::Type event_type; + + Node *_get_base_node() const; + StringName _get_base_type() const; + + void _update_base_type(); + + void _update_cache(); + + void _set_type_cache(const Dictionary& p_type); + Dictionary _get_type_cache() const; + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + void set_basic_type(Variant::Type p_type); + Variant::Type get_basic_type() const; + + void set_event_type(InputEvent::Type p_type); + InputEvent::Type get_event_type() const; + + void set_property(const StringName& p_type); + StringName get_property() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptPropertySet(); +}; + +VARIANT_ENUM_CAST(VisualScriptPropertySet::CallMode ); + + +class VisualScriptPropertyGet : public VisualScriptNode { + + GDCLASS(VisualScriptPropertyGet,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + CALL_MODE_BASIC_TYPE, + + }; +private: + + Variant::Type type_cache; + + CallMode call_mode; + Variant::Type basic_type; + StringName base_type; + String base_script; + NodePath base_path; + StringName property; + InputEvent::Type event_type; + + void _update_base_type(); + Node *_get_base_node() const; + StringName _get_base_type() const; + + void _update_cache(); + + void _set_type_cache(Variant::Type p_type); + Variant::Type _get_type_cache() const; + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_base_script(const String& p_path); + String get_base_script() const; + + void set_basic_type(Variant::Type p_type); + Variant::Type get_basic_type() const; + + void set_event_type(InputEvent::Type p_type); + InputEvent::Type get_event_type() const; + + void set_property(const StringName& p_type); + StringName get_property() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptPropertyGet(); +}; + + + + + +VARIANT_ENUM_CAST(VisualScriptPropertyGet::CallMode ); + + + +class VisualScriptEmitSignal : public VisualScriptNode { + + GDCLASS(VisualScriptEmitSignal,VisualScriptNode) + +private: + + StringName name; + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_signal(const StringName& p_type); + StringName get_signal() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + + + + VisualScriptEmitSignal(); +}; + + + +void register_visual_script_func_nodes(); + +#endif // VISUAL_SCRIPT_FUNC_NODES_H diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp new file mode 100644 index 000000000..c9e24fc9d --- /dev/null +++ b/modules/visual_script/visual_script_nodes.cpp @@ -0,0 +1,3963 @@ +#include "visual_script_nodes.h" +#include "global_constants.h" +#include "globals.h" +#include "scene/main/scene_main_loop.h" +#include "os/os.h" +#include "scene/main/node.h" +#include "os/input.h" + +////////////////////////////////////////// +////////////////FUNCTION////////////////// +////////////////////////////////////////// + + +bool VisualScriptFunction::_set(const StringName& p_name, const Variant& p_value) { + + + if (p_name=="argument_count") { + + int new_argc=p_value; + int argc = arguments.size(); + if (argc==new_argc) + return true; + + arguments.resize(new_argc); + + for(int i=argc;i<new_argc;i++) { + arguments[i].name="arg"+itos(i+1); + arguments[i].type=Variant::NIL; + } + ports_changed_notify(); + _change_notify(); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,arguments.size(),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + + Variant::Type new_type = Variant::Type(int(p_value)); + arguments[idx].type=new_type; + ports_changed_notify(); + + return true; + } + + if (what=="name") { + + arguments[idx].name=p_value; + ports_changed_notify(); + return true; + } + + + } + + if (p_name=="stack/stackless") { + set_stack_less(p_value); + return true; + } + + if (p_name=="stack/size") { + stack_size=p_value; + return true; + } + + if (p_name=="rpc/mode") { + rpc_mode=ScriptInstance::RPCMode(int(p_value)); + return true; + } + + return false; +} + +bool VisualScriptFunction::_get(const StringName& p_name,Variant &r_ret) const { + + + if (p_name=="argument_count") { + r_ret = arguments.size(); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/",1).to_int()-1; + ERR_FAIL_INDEX_V(idx,arguments.size(),false); + String what = String(p_name).get_slice("/",2); + if (what=="type") { + r_ret = arguments[idx].type; + return true; + } + if (what=="name") { + r_ret = arguments[idx].name; + return true; + } + + + + } + + if (p_name=="stack/stackless") { + r_ret=stack_less; + return true; + } + + if (p_name=="stack/size") { + r_ret=stack_size; + return true; + } + + if (p_name=="rpc/mode") { + r_ret=rpc_mode; + return true; + } + + return false; +} +void VisualScriptFunction::_get_property_list( List<PropertyInfo> *p_list) const { + + + p_list->push_back(PropertyInfo(Variant::INT,"argument_count",PROPERTY_HINT_RANGE,"0,256")); + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + for(int i=0;i<arguments.size();i++) { + p_list->push_back(PropertyInfo(Variant::INT,"argument/"+itos(i+1)+"/type",PROPERTY_HINT_ENUM,argt)); + p_list->push_back(PropertyInfo(Variant::STRING,"argument/"+itos(i+1)+"/name")); + } + if (!stack_less) { + p_list->push_back(PropertyInfo(Variant::INT,"stack/size",PROPERTY_HINT_RANGE,"1,100000")); + } + p_list->push_back(PropertyInfo(Variant::BOOL,"stack/stackless")); + p_list->push_back(PropertyInfo(Variant::INT,"rpc/mode",PROPERTY_HINT_ENUM,"Disabled,Remote,Sync,Master,Slave")); + +} + + +int VisualScriptFunction::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptFunction::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptFunction::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptFunction::get_output_value_port_count() const{ + + return arguments.size(); +} + +String VisualScriptFunction::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptFunction::get_input_value_port_info(int p_idx) const{ + + ERR_FAIL_V(PropertyInfo()); + return PropertyInfo(); +} +PropertyInfo VisualScriptFunction::get_output_value_port_info(int p_idx) const{ + + ERR_FAIL_INDEX_V(p_idx,arguments.size(),PropertyInfo()); + PropertyInfo out; + out.type=arguments[p_idx].type; + out.name=arguments[p_idx].name; + return out; +} + +String VisualScriptFunction::get_caption() const { + + return "Function"; +} + +String VisualScriptFunction::get_text() const { + + return get_name(); //use name as function name I guess +} + +void VisualScriptFunction::add_argument(Variant::Type p_type,const String& p_name,int p_index){ + + Argument arg; + arg.name=p_name; + arg.type=p_type; + if (p_index>=0) + arguments.insert(p_index,arg); + else + arguments.push_back(arg); + + ports_changed_notify(); + +} +void VisualScriptFunction::set_argument_type(int p_argidx,Variant::Type p_type){ + + ERR_FAIL_INDEX(p_argidx,arguments.size()); + + arguments[p_argidx].type=p_type; + ports_changed_notify(); +} +Variant::Type VisualScriptFunction::get_argument_type(int p_argidx) const { + + ERR_FAIL_INDEX_V(p_argidx,arguments.size(),Variant::NIL); + return arguments[p_argidx].type; + +} +void VisualScriptFunction::set_argument_name(int p_argidx,const String& p_name) { + + ERR_FAIL_INDEX(p_argidx,arguments.size()); + + arguments[p_argidx].name=p_name; + ports_changed_notify(); + +} +String VisualScriptFunction::get_argument_name(int p_argidx) const { + + ERR_FAIL_INDEX_V(p_argidx,arguments.size(),String()); + return arguments[p_argidx].name; + +} +void VisualScriptFunction::remove_argument(int p_argidx) { + + ERR_FAIL_INDEX(p_argidx,arguments.size()); + + arguments.remove(p_argidx); + ports_changed_notify(); + +} + +int VisualScriptFunction::get_argument_count() const { + + return arguments.size(); +} + + +void VisualScriptFunction::set_rpc_mode(ScriptInstance::RPCMode p_mode) { + rpc_mode=p_mode; +} + +ScriptInstance::RPCMode VisualScriptFunction::get_rpc_mode() const { + return rpc_mode; +} + + +class VisualScriptNodeInstanceFunction : public VisualScriptNodeInstance { +public: + + VisualScriptFunction *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + int ac = node->get_argument_count(); + + for(int i=0;i<ac;i++) { +#ifdef DEBUG_ENABLED + Variant::Type expected = node->get_argument_type(i); + if (expected!=Variant::NIL) { + if (!Variant::can_convert_strict(p_inputs[i]->get_type(),expected)) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.expected=expected; + r_error.argument=i; + return 0; + } + } +#endif + + *p_outputs[i]=*p_inputs[i]; + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptFunction::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceFunction * instance = memnew(VisualScriptNodeInstanceFunction ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptFunction::VisualScriptFunction() { + + stack_size=256; + stack_less=false; + rpc_mode=ScriptInstance::RPC_MODE_DISABLED; +} + + +void VisualScriptFunction::set_stack_less(bool p_enable) { + stack_less=p_enable; + _change_notify(); +} + +bool VisualScriptFunction::is_stack_less() const { + return stack_less; +} + +void VisualScriptFunction::set_stack_size(int p_size) { + + ERR_FAIL_COND(p_size <1 || p_size>100000); + stack_size=p_size; +} + +int VisualScriptFunction::get_stack_size() const { + + return stack_size; +} + + +////////////////////////////////////////// +////////////////OPERATOR////////////////// +////////////////////////////////////////// + +int VisualScriptOperator::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptOperator::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptOperator::get_input_value_port_count() const{ + + return (op==Variant::OP_BIT_NEGATE || op==Variant::OP_NOT || op==Variant::OP_NEGATE || op==Variant::OP_POSITIVE) ? 1 : 2; +} +int VisualScriptOperator::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptOperator::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptOperator::get_input_value_port_info(int p_idx) const{ + + static const Variant::Type port_types[Variant::OP_MAX][2]={ + {Variant::NIL,Variant::NIL}, //OP_EQUAL, + {Variant::NIL,Variant::NIL}, //OP_NOT_EQUAL, + {Variant::NIL,Variant::NIL}, //OP_LESS, + {Variant::NIL,Variant::NIL}, //OP_LESS_EQUAL, + {Variant::NIL,Variant::NIL}, //OP_GREATER, + {Variant::NIL,Variant::NIL}, //OP_GREATER_EQUAL, + //mathematic + {Variant::NIL,Variant::NIL}, //OP_ADD, + {Variant::NIL,Variant::NIL}, //OP_SUBSTRACT, + {Variant::NIL,Variant::NIL}, //OP_MULTIPLY, + {Variant::NIL,Variant::NIL}, //OP_DIVIDE, + {Variant::NIL,Variant::NIL}, //OP_NEGATE, + {Variant::NIL,Variant::NIL}, //OP_POSITIVE, + {Variant::INT,Variant::INT}, //OP_MODULE, + {Variant::STRING,Variant::STRING}, //OP_STRING_CONCAT, + //bitwise + {Variant::INT,Variant::INT}, //OP_SHIFT_LEFT, + {Variant::INT,Variant::INT}, //OP_SHIFT_RIGHT, + {Variant::INT,Variant::INT}, //OP_BIT_AND, + {Variant::INT,Variant::INT}, //OP_BIT_OR, + {Variant::INT,Variant::INT}, //OP_BIT_XOR, + {Variant::INT,Variant::INT}, //OP_BIT_NEGATE, + //logic + {Variant::BOOL,Variant::BOOL}, //OP_AND, + {Variant::BOOL,Variant::BOOL}, //OP_OR, + {Variant::BOOL,Variant::BOOL}, //OP_XOR, + {Variant::BOOL,Variant::BOOL}, //OP_NOT, + //containment + {Variant::NIL,Variant::NIL} //OP_IN, + }; + + ERR_FAIL_INDEX_V(p_idx,Variant::OP_MAX,PropertyInfo()); + + PropertyInfo pinfo; + pinfo.name=p_idx==0?"A":"B"; + pinfo.type=port_types[op][p_idx]; + if (pinfo.type==Variant::NIL) + pinfo.type=typed; + return pinfo; +} +PropertyInfo VisualScriptOperator::get_output_value_port_info(int p_idx) const{ + static const Variant::Type port_types[Variant::OP_MAX]={ + //comparation + Variant::BOOL, //OP_EQUAL, + Variant::BOOL, //OP_NOT_EQUAL, + Variant::BOOL, //OP_LESS, + Variant::BOOL, //OP_LESS_EQUAL, + Variant::BOOL, //OP_GREATER, + Variant::BOOL, //OP_GREATER_EQUAL, + //mathematic + Variant::NIL, //OP_ADD, + Variant::NIL, //OP_SUBSTRACT, + Variant::NIL, //OP_MULTIPLY, + Variant::NIL, //OP_DIVIDE, + Variant::NIL, //OP_NEGATE, + Variant::NIL, //OP_POSITIVE, + Variant::INT, //OP_MODULE, + Variant::STRING, //OP_STRING_CONCAT, + //bitwise + Variant::INT, //OP_SHIFT_LEFT, + Variant::INT, //OP_SHIFT_RIGHT, + Variant::INT, //OP_BIT_AND, + Variant::INT, //OP_BIT_OR, + Variant::INT, //OP_BIT_XOR, + Variant::INT, //OP_BIT_NEGATE, + //logic + Variant::BOOL, //OP_AND, + Variant::BOOL, //OP_OR, + Variant::BOOL, //OP_XOR, + Variant::BOOL, //OP_NOT, + //containment + Variant::BOOL //OP_IN, + }; + + PropertyInfo pinfo; + pinfo.name=""; + pinfo.type=port_types[op]; + if (pinfo.type==Variant::NIL) + pinfo.type=typed; + return pinfo; + +} + +static const char* op_names[]={ + //comparation + "Equal", //OP_EQUAL, + "NotEqual", //OP_NOT_EQUAL, + "Less", //OP_LESS, + "LessEqual", //OP_LESS_EQUAL, + "Greater", //OP_GREATER, + "GreaterEq", //OP_GREATER_EQUAL, + //mathematic + "Add", //OP_ADD, + "Subtract", //OP_SUBSTRACT, + "Multiply", //OP_MULTIPLY, + "Divide", //OP_DIVIDE, + "Negate", //OP_NEGATE, + "Positive", //OP_POSITIVE, + "Remainder", //OP_MODULE, + "Concat", //OP_STRING_CONCAT, + //bitwise + "ShiftLeft", //OP_SHIFT_LEFT, + "ShiftRight", //OP_SHIFT_RIGHT, + "BitAnd", //OP_BIT_AND, + "BitOr", //OP_BIT_OR, + "BitXor", //OP_BIT_XOR, + "BitNeg", //OP_BIT_NEGATE, + //logic + "And", //OP_AND, + "Or", //OP_OR, + "Xor", //OP_XOR, + "Not", //OP_NOT, + //containment + "In", //OP_IN, +}; + +String VisualScriptOperator::get_caption() const { + + + + return op_names[op]; +} + +String VisualScriptOperator::get_text() const { + + static const wchar_t* op_names[]={ + //comparation + L"A = B", //OP_EQUAL, + L"A \u2260 B", //OP_NOT_EQUAL, + L"A < B", //OP_LESS, + L"A \u2264 B", //OP_LESS_EQUAL, + L"A > B", //OP_GREATER, + L"A \u2265 B", //OP_GREATER_EQUAL, + //mathematic + L"A + B", //OP_ADD, + L"A - B", //OP_SUBSTRACT, + L"A x B", //OP_MULTIPLY, + L"A \u00F7 B", //OP_DIVIDE, + L"\u00AC A", //OP_NEGATE, + L"+ A", //OP_POSITIVE, + L"A mod B", //OP_MODULE, + L"A .. B", //OP_STRING_CONCAT, + //bitwise + L"A << B", //OP_SHIFT_LEFT, + L"A >> B", //OP_SHIFT_RIGHT, + L"A & B", //OP_BIT_AND, + L"A | B", //OP_BIT_OR, + L"A ^ B", //OP_BIT_XOR, + L"~A", //OP_BIT_NEGATE, + //logic + L"A and B", //OP_AND, + L"A or B", //OP_OR, + L"A xor B", //OP_XOR, + L"not A", //OP_NOT, + + }; + return op_names[op]; +} + +void VisualScriptOperator::set_operator(Variant::Operator p_op) { + + if (op==p_op) + return; + op=p_op; + ports_changed_notify(); + +} + +Variant::Operator VisualScriptOperator::get_operator() const{ + + return op; +} + +void VisualScriptOperator::set_typed(Variant::Type p_op) { + + if (typed==p_op) + return; + + typed=p_op; + ports_changed_notify(); +} + +Variant::Type VisualScriptOperator::get_typed() const { + + return typed; +} + + +void VisualScriptOperator::_bind_methods() { + + ClassDB::bind_method(_MD("set_operator","op"),&VisualScriptOperator::set_operator); + ClassDB::bind_method(_MD("get_operator"),&VisualScriptOperator::get_operator); + + ClassDB::bind_method(_MD("set_typed","type"),&VisualScriptOperator::set_typed); + ClassDB::bind_method(_MD("get_typed"),&VisualScriptOperator::get_typed); + + String types; + for(int i=0;i<Variant::OP_MAX;i++) { + if (i>0) + types+=","; + types+=op_names[i]; + } + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"operator_value/type",PROPERTY_HINT_ENUM,types),_SCS("set_operator"),_SCS("get_operator")); + ADD_PROPERTY(PropertyInfo(Variant::INT,"typed_value/typed",PROPERTY_HINT_ENUM,argt),_SCS("set_typed"),_SCS("get_typed")); + +} + +class VisualScriptNodeInstanceOperator : public VisualScriptNodeInstance { +public: + + bool unary; + Variant::Operator op; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool valid; + if (unary) { + + Variant::evaluate(op,*p_inputs[0],Variant(),*p_outputs[0],valid); + } else { + Variant::evaluate(op,*p_inputs[0],*p_inputs[1],*p_outputs[0],valid); + } + + if (!valid) { + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + if (p_outputs[0]->get_type()==Variant::STRING) { + r_error_str=*p_outputs[0]; + } else { + if (unary) + r_error_str=String(op_names[op])+RTR(": Invalid argument of type: ")+Variant::get_type_name(p_inputs[0]->get_type()); + else + r_error_str=String(op_names[op])+RTR(": Invalid arguments: ")+"A: "+Variant::get_type_name(p_inputs[0]->get_type())+" B: "+Variant::get_type_name(p_inputs[1]->get_type()); + } + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptOperator::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceOperator * instance = memnew(VisualScriptNodeInstanceOperator ); + instance->unary=get_input_value_port_count()==1; + instance->op=op; + return instance; +} + +VisualScriptOperator::VisualScriptOperator() { + + op=Variant::OP_ADD; + typed=Variant::NIL; +} + + + +template<Variant::Operator OP> +static Ref<VisualScriptNode> create_op_node(const String& p_name) { + + Ref<VisualScriptOperator> node; + node.instance(); + node->set_operator(OP); + return node; +} + +////////////////////////////////////////// +////////////////VARIABLE GET////////////////// +////////////////////////////////////////// + +int VisualScriptVariableGet::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptVariableGet::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptVariableGet::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptVariableGet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptVariableGet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptVariableGet::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptVariableGet::get_output_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="value"; + if (get_visual_script().is_valid() && get_visual_script()->has_variable(variable)) { + PropertyInfo vinfo = get_visual_script()->get_variable_info(variable); + pinfo.type=vinfo.type; + pinfo.hint=vinfo.hint; + pinfo.hint_string=vinfo.hint_string; + } + return pinfo; +} + + +String VisualScriptVariableGet::get_caption() const { + + return "Variable"; +} + +String VisualScriptVariableGet::get_text() const { + + return variable; +} + +void VisualScriptVariableGet::set_variable(StringName p_variable) { + + if (variable==p_variable) + return; + variable=p_variable; + ports_changed_notify(); + +} + +StringName VisualScriptVariableGet::get_variable() const{ + + return variable; +} + +void VisualScriptVariableGet::_validate_property(PropertyInfo& property) const { + + if (property.name=="variable/name" && get_visual_script().is_valid()) { + Ref<VisualScript> vs = get_visual_script(); + List<StringName> vars; + vs->get_variable_list(&vars); + + String vhint; + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + if (vhint!=String()) + vhint+=","; + + vhint+=E->get().operator String(); + } + + property.hint=PROPERTY_HINT_ENUM; + property.hint_string=vhint; + } +} + +void VisualScriptVariableGet::_bind_methods() { + + ClassDB::bind_method(_MD("set_variable","name"),&VisualScriptVariableGet::set_variable); + ClassDB::bind_method(_MD("get_variable"),&VisualScriptVariableGet::get_variable); + + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_variable"),_SCS("get_variable")); + +} + +class VisualScriptNodeInstanceVariableGet : public VisualScriptNodeInstance { +public: + + VisualScriptVariableGet *node; + VisualScriptInstance *instance; + StringName variable; + + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (instance->get_variable(variable,p_outputs[0])==false) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("VariableGet not found in script: ")+"'"+String(variable)+"'"; + return false; + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptVariableGet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceVariableGet * instance = memnew(VisualScriptNodeInstanceVariableGet ); + instance->node=this; + instance->instance=p_instance; + instance->variable=variable; + return instance; +} +VisualScriptVariableGet::VisualScriptVariableGet() { + + +} + + +////////////////////////////////////////// +////////////////VARIABLE SET////////////////// +////////////////////////////////////////// + +int VisualScriptVariableSet::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptVariableSet::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptVariableSet::get_input_value_port_count() const{ + + return 1; +} +int VisualScriptVariableSet::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptVariableSet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptVariableSet::get_input_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="set"; + if (get_visual_script().is_valid() && get_visual_script()->has_variable(variable)) { + PropertyInfo vinfo = get_visual_script()->get_variable_info(variable); + pinfo.type=vinfo.type; + pinfo.hint=vinfo.hint; + pinfo.hint_string=vinfo.hint_string; + } + return pinfo; +} + +PropertyInfo VisualScriptVariableSet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptVariableSet::get_caption() const { + + return "VariableSet"; +} + +String VisualScriptVariableSet::get_text() const { + + return variable; +} + +void VisualScriptVariableSet::set_variable(StringName p_variable) { + + if (variable==p_variable) + return; + variable=p_variable; + ports_changed_notify(); + +} + +StringName VisualScriptVariableSet::get_variable() const{ + + return variable; +} + +void VisualScriptVariableSet::_validate_property(PropertyInfo& property) const { + + if (property.name=="variable/name" && get_visual_script().is_valid()) { + Ref<VisualScript> vs = get_visual_script(); + List<StringName> vars; + vs->get_variable_list(&vars); + + String vhint; + for (List<StringName>::Element *E=vars.front();E;E=E->next()) { + if (vhint!=String()) + vhint+=","; + + vhint+=E->get().operator String(); + } + + property.hint=PROPERTY_HINT_ENUM; + property.hint_string=vhint; + } +} + +void VisualScriptVariableSet::_bind_methods() { + + ClassDB::bind_method(_MD("set_variable","name"),&VisualScriptVariableSet::set_variable); + ClassDB::bind_method(_MD("get_variable"),&VisualScriptVariableSet::get_variable); + + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_variable"),_SCS("get_variable")); + +} + +class VisualScriptNodeInstanceVariableSet : public VisualScriptNodeInstance { +public: + + VisualScriptVariableSet *node; + VisualScriptInstance *instance; + StringName variable; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (instance->set_variable(variable,*p_inputs[0])==false) { + + + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str=RTR("VariableSet not found in script: ")+"'"+String(variable)+"'"; + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptVariableSet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceVariableSet * instance = memnew(VisualScriptNodeInstanceVariableSet ); + instance->node=this; + instance->instance=p_instance; + instance->variable=variable; + return instance; +} +VisualScriptVariableSet::VisualScriptVariableSet() { + + +} + + + +////////////////////////////////////////// +////////////////CONSTANT////////////////// +////////////////////////////////////////// + +int VisualScriptConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptConstant::get_output_value_port_info(int p_idx) const{ + + PropertyInfo pinfo; + pinfo.name="get"; + pinfo.type=type; + return pinfo; +} + + +String VisualScriptConstant::get_caption() const { + + return "Constant"; +} + +String VisualScriptConstant::get_text() const { + + return String(value); +} + +void VisualScriptConstant::set_constant_type(Variant::Type p_type) { + + if (type==p_type) + return; + + type=p_type; + ports_changed_notify(); + Variant::CallError ce; + value=Variant::construct(type,NULL,0,ce); + _change_notify(); + +} + +Variant::Type VisualScriptConstant::get_constant_type() const{ + + return type; +} + +void VisualScriptConstant::set_constant_value(Variant p_value){ + + if (value==p_value) + return; + + value=p_value; + ports_changed_notify(); +} +Variant VisualScriptConstant::get_constant_value() const{ + + return value; +} + +void VisualScriptConstant::_validate_property(PropertyInfo& property) const { + + + if (property.name=="constant/value") { + property.type=type; + if (type==Variant::NIL) + property.usage=0; //do not save if nil + } +} + +void VisualScriptConstant::_bind_methods() { + + ClassDB::bind_method(_MD("set_constant_type","type"),&VisualScriptConstant::set_constant_type); + ClassDB::bind_method(_MD("get_constant_type"),&VisualScriptConstant::get_constant_type); + + ClassDB::bind_method(_MD("set_constant_value","value"),&VisualScriptConstant::set_constant_value); + ClassDB::bind_method(_MD("get_constant_value"),&VisualScriptConstant::get_constant_value); + + String argt="Null"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + + ADD_PROPERTY(PropertyInfo(Variant::INT,"constant/type",PROPERTY_HINT_ENUM,argt),_SCS("set_constant_type"),_SCS("get_constant_type")); + ADD_PROPERTY(PropertyInfo(Variant::NIL,"constant/value"),_SCS("set_constant_value"),_SCS("get_constant_value")); + +} + +class VisualScriptNodeInstanceConstant : public VisualScriptNodeInstance { +public: + + Variant constant; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=constant; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceConstant * instance = memnew(VisualScriptNodeInstanceConstant ); + instance->constant=value; + return instance; +} + +VisualScriptConstant::VisualScriptConstant() { + + type=Variant::NIL; + +} + +////////////////////////////////////////// +////////////////PRELOAD////////////////// +////////////////////////////////////////// + +int VisualScriptPreload::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptPreload::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptPreload::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptPreload::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptPreload::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptPreload::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptPreload::get_output_value_port_info(int p_idx) const{ + + PropertyInfo pinfo=PropertyInfo(Variant::OBJECT,"res"); + if (preload.is_valid()) { + pinfo.hint=PROPERTY_HINT_RESOURCE_TYPE; + pinfo.hint_string=preload->get_class(); + } + + return pinfo; +} + + +String VisualScriptPreload::get_caption() const { + + return "Preload"; +} + +String VisualScriptPreload::get_text() const { + + if (preload.is_valid()) { + if (preload->get_path().is_resource_file()) { + return preload->get_path(); + } else if (preload->get_name()!=String()) { + return preload->get_name(); + } else { + return preload->get_class(); + } + } else { + return "<empty>"; + } +} + + +void VisualScriptPreload::set_preload(const Ref<Resource>& p_preload){ + + if (preload==p_preload) + return; + + preload=p_preload; + ports_changed_notify(); +} +Ref<Resource> VisualScriptPreload::get_preload() const{ + + return preload; +} + + +void VisualScriptPreload::_bind_methods() { + + + ClassDB::bind_method(_MD("set_preload","resource"),&VisualScriptPreload::set_preload); + ClassDB::bind_method(_MD("get_preload"),&VisualScriptPreload::get_preload); + + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"resource",PROPERTY_HINT_RESOURCE_TYPE,"Resource"),_SCS("set_preload"),_SCS("get_preload")); + +} + +class VisualScriptNodeInstancePreload : public VisualScriptNodeInstance { +public: + + Ref<Resource> preload; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=preload; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptPreload::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstancePreload * instance = memnew(VisualScriptNodeInstancePreload ); + instance->preload=preload; + return instance; +} + +VisualScriptPreload::VisualScriptPreload() { + +} + + + + +////////////////////////////////////////// +////////////////INDEX//////////////////// +////////////////////////////////////////// + +int VisualScriptIndexGet::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptIndexGet::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptIndexGet::get_input_value_port_count() const{ + + return 2; +} +int VisualScriptIndexGet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptIndexGet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptIndexGet::get_input_value_port_info(int p_idx) const{ + + if (p_idx==0) { + return PropertyInfo(Variant::NIL,"base"); + } else { + return PropertyInfo(Variant::NIL,"index"); + + } +} + +PropertyInfo VisualScriptIndexGet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptIndexGet::get_caption() const { + + return "IndexGet"; +} + +String VisualScriptIndexGet::get_text() const { + + return String("get"); +} + + +class VisualScriptNodeInstanceIndexGet : public VisualScriptNodeInstance { +public: + + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool valid; + *p_outputs[0] = p_inputs[0]->get(*p_inputs[1],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid get: "+p_inputs[0]->get_construct_string(); + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptIndexGet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceIndexGet * instance = memnew(VisualScriptNodeInstanceIndexGet ); + return instance; +} +VisualScriptIndexGet::VisualScriptIndexGet() { + + + +} + +////////////////////////////////////////// +////////////////INDEXSET////////////////// +////////////////////////////////////////// + +int VisualScriptIndexSet::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptIndexSet::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptIndexSet::get_input_value_port_count() const{ + + return 3; +} +int VisualScriptIndexSet::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptIndexSet::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptIndexSet::get_input_value_port_info(int p_idx) const{ + + if (p_idx==0) { + return PropertyInfo(Variant::NIL,"base"); + } else if (p_idx==1){ + return PropertyInfo(Variant::NIL,"index"); + + } else { + return PropertyInfo(Variant::NIL,"value"); + + } +} + +PropertyInfo VisualScriptIndexSet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptIndexSet::get_caption() const { + + return "IndexSet"; +} + +String VisualScriptIndexSet::get_text() const { + + return String("set"); +} + + +class VisualScriptNodeInstanceIndexSet : public VisualScriptNodeInstance { +public: + + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + bool valid; + *p_outputs[0]=*p_inputs[0]; + p_outputs[0]->set(*p_inputs[1],*p_inputs[2],&valid); + + if (!valid) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Invalid set: "+p_inputs[1]->get_construct_string(); + } + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptIndexSet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceIndexSet * instance = memnew(VisualScriptNodeInstanceIndexSet ); + return instance; +} +VisualScriptIndexSet::VisualScriptIndexSet() { + + + +} + + +////////////////////////////////////////// +////////////////GLOBALCONSTANT/////////// +////////////////////////////////////////// + +int VisualScriptGlobalConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptGlobalConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptGlobalConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptGlobalConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptGlobalConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptGlobalConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptGlobalConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::REAL,"value"); +} + + +String VisualScriptGlobalConstant::get_caption() const { + + return "GlobalConst"; +} + +String VisualScriptGlobalConstant::get_text() const { + + return GlobalConstants::get_global_constant_name(index); +} + +void VisualScriptGlobalConstant::set_global_constant(int p_which) { + + index=p_which; + _change_notify(); + ports_changed_notify(); +} + +int VisualScriptGlobalConstant::get_global_constant() { + return index; +} + + +class VisualScriptNodeInstanceGlobalConstant : public VisualScriptNodeInstance { +public: + + int index; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + *p_outputs[0] = GlobalConstants::get_global_constant_value(index); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptGlobalConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceGlobalConstant * instance = memnew(VisualScriptNodeInstanceGlobalConstant ); + instance->index=index; + return instance; +} + +void VisualScriptGlobalConstant::_bind_methods() { + + ClassDB::bind_method(_MD("set_global_constant","index"),&VisualScriptGlobalConstant::set_global_constant); + ClassDB::bind_method(_MD("get_global_constant"),&VisualScriptGlobalConstant::get_global_constant); + + String cc; + + for(int i=0;i<GlobalConstants::get_global_constant_count();i++) { + + if (i>0) + cc+=","; + cc+=GlobalConstants::get_global_constant_name(i); + } + ADD_PROPERTY(PropertyInfo(Variant::INT,"constant",PROPERTY_HINT_ENUM,cc),_SCS("set_global_constant"),_SCS("get_global_constant")); +} + +VisualScriptGlobalConstant::VisualScriptGlobalConstant() { + + index=0; +} + +////////////////////////////////////////// +////////////////CLASSCONSTANT/////////// +////////////////////////////////////////// + +int VisualScriptClassConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptClassConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptClassConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptClassConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptClassConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptClassConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptClassConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INT,"value"); +} + + +String VisualScriptClassConstant::get_caption() const { + + return "ClassConst"; +} + +String VisualScriptClassConstant::get_text() const { + + return String(base_type)+"."+String(name); +} + +void VisualScriptClassConstant::set_class_constant(const StringName& p_which) { + + name=p_which; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptClassConstant::get_class_constant() { + return name; +} + + +void VisualScriptClassConstant::set_base_type(const StringName& p_which) { + + base_type=p_which; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptClassConstant::get_base_type() { + return base_type; +} + +class VisualScriptNodeInstanceClassConstant : public VisualScriptNodeInstance { +public: + + int value; + bool valid; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!valid) { + r_error_str="Invalid constant name, pick a valid class constant."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } + + *p_outputs[0] = value; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptClassConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceClassConstant * instance = memnew(VisualScriptNodeInstanceClassConstant ); + instance->value=ClassDB::get_integer_constant(base_type,name,&instance->valid); + return instance; +} + +void VisualScriptClassConstant::_validate_property(PropertyInfo& property) const { + + if (property.name=="constant") { + + List<String> constants; + ClassDB::get_integer_constant_list(base_type,&constants,true); + + property.hint_string=""; + for(List<String>::Element *E=constants.front();E;E=E->next()) { + if (property.hint_string!=String()) { + property.hint_string+=","; + } + property.hint_string+=E->get(); + } + } +} + +void VisualScriptClassConstant::_bind_methods() { + + ClassDB::bind_method(_MD("set_class_constant","name"),&VisualScriptClassConstant::set_class_constant); + ClassDB::bind_method(_MD("get_class_constant"),&VisualScriptClassConstant::get_class_constant); + + ClassDB::bind_method(_MD("set_base_type","name"),&VisualScriptClassConstant::set_base_type); + ClassDB::bind_method(_MD("get_base_type"),&VisualScriptClassConstant::get_base_type); + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"constant",PROPERTY_HINT_ENUM,""),_SCS("set_class_constant"),_SCS("get_class_constant")); +} + +VisualScriptClassConstant::VisualScriptClassConstant() { + + base_type="Object"; +} + + +////////////////////////////////////////// +////////////////BASICTYPECONSTANT/////////// +////////////////////////////////////////// + +int VisualScriptBasicTypeConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptBasicTypeConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptBasicTypeConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptBasicTypeConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptBasicTypeConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptBasicTypeConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptBasicTypeConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::INT,"value"); +} + + +String VisualScriptBasicTypeConstant::get_caption() const { + + return "BasicConst"; +} + +String VisualScriptBasicTypeConstant::get_text() const { + + return Variant::get_type_name(type)+"."+String(name); +} + +void VisualScriptBasicTypeConstant::set_basic_type_constant(const StringName& p_which) { + + name=p_which; + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptBasicTypeConstant::get_basic_type_constant() const { + return name; +} + + +void VisualScriptBasicTypeConstant::set_basic_type(Variant::Type p_which) { + + type=p_which; + _change_notify(); + ports_changed_notify(); +} + +Variant::Type VisualScriptBasicTypeConstant::get_basic_type() const { + return type; +} + +class VisualScriptNodeInstanceBasicTypeConstant : public VisualScriptNodeInstance { +public: + + int value; + bool valid; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!valid) { + r_error_str="Invalid constant name, pick a valid basic type constant."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + } + + *p_outputs[0] = value; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptBasicTypeConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceBasicTypeConstant * instance = memnew(VisualScriptNodeInstanceBasicTypeConstant ); + instance->value=Variant::get_numeric_constant_value(type,name,&instance->valid); + return instance; +} + +void VisualScriptBasicTypeConstant::_validate_property(PropertyInfo& property) const { + + if (property.name=="constant") { + + List<StringName> constants; + Variant::get_numeric_constants_for_type(type,&constants); + + if (constants.size()==0) { + property.usage=0; + return; + } + property.hint_string=""; + for(List<StringName>::Element *E=constants.front();E;E=E->next()) { + if (property.hint_string!=String()) { + property.hint_string+=","; + } + property.hint_string+=String(E->get()); + } + + } +} + +void VisualScriptBasicTypeConstant::_bind_methods() { + + ClassDB::bind_method(_MD("set_basic_type","name"),&VisualScriptBasicTypeConstant::set_basic_type); + ClassDB::bind_method(_MD("get_basic_type"),&VisualScriptBasicTypeConstant::get_basic_type); + + ClassDB::bind_method(_MD("set_basic_type_constant","name"),&VisualScriptBasicTypeConstant::set_basic_type_constant); + ClassDB::bind_method(_MD("get_basic_type_constant"),&VisualScriptBasicTypeConstant::get_basic_type_constant); + + + String argt="Null"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"basic_type",PROPERTY_HINT_ENUM,argt),_SCS("set_basic_type"),_SCS("get_basic_type")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"constant",PROPERTY_HINT_ENUM,""),_SCS("set_basic_type_constant"),_SCS("get_basic_type_constant")); +} + +VisualScriptBasicTypeConstant::VisualScriptBasicTypeConstant() { + + type=Variant::NIL; +} + + + +////////////////////////////////////////// +////////////////MATHCONSTANT/////////// +////////////////////////////////////////// + + +const char* VisualScriptMathConstant::const_name[MATH_CONSTANT_MAX]={ + "One", + "PI", + "PIx2", + "PI/2", + "E", + "Sqrt2", +}; + +double VisualScriptMathConstant::const_value[MATH_CONSTANT_MAX]={ + 1.0, + Math_PI, + Math_PI*2, + Math_PI*0.5, + 2.71828182845904523536, + Math::sqrt(2.0) +}; + + +int VisualScriptMathConstant::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptMathConstant::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptMathConstant::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptMathConstant::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptMathConstant::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptMathConstant::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptMathConstant::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::REAL,"value"); +} + + +String VisualScriptMathConstant::get_caption() const { + + return "MathConst"; +} + +String VisualScriptMathConstant::get_text() const { + + return const_name[constant]; +} + +void VisualScriptMathConstant::set_math_constant(MathConstant p_which) { + + constant=p_which; + _change_notify(); + ports_changed_notify(); +} + +VisualScriptMathConstant::MathConstant VisualScriptMathConstant::get_math_constant() { + return constant; +} + +class VisualScriptNodeInstanceMathConstant : public VisualScriptNodeInstance { +public: + + float value; + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=value; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptMathConstant::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceMathConstant * instance = memnew(VisualScriptNodeInstanceMathConstant ); + instance->value=const_value[constant]; + return instance; +} + + +void VisualScriptMathConstant::_bind_methods() { + + ClassDB::bind_method(_MD("set_math_constant","which"),&VisualScriptMathConstant::set_math_constant); + ClassDB::bind_method(_MD("get_math_constant"),&VisualScriptMathConstant::get_math_constant); + + String cc; + + for(int i=0;i<MATH_CONSTANT_MAX;i++) { + + if (i>0) + cc+=","; + cc+=const_name[i]; + } + ADD_PROPERTY(PropertyInfo(Variant::INT,"constant",PROPERTY_HINT_ENUM,cc),_SCS("set_math_constant"),_SCS("get_math_constant")); +} + +VisualScriptMathConstant::VisualScriptMathConstant() { + + constant=MATH_CONSTANT_ONE; +} + + + +////////////////////////////////////////// +////////////////GLOBALSINGLETON/////////// +////////////////////////////////////////// + +int VisualScriptEngineSingleton::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptEngineSingleton::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptEngineSingleton::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptEngineSingleton::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptEngineSingleton::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptEngineSingleton::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptEngineSingleton::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + + +String VisualScriptEngineSingleton::get_caption() const { + + return "EngineSingleton"; +} + +String VisualScriptEngineSingleton::get_text() const { + + return singleton; +} + +void VisualScriptEngineSingleton::set_singleton(const String& p_string) { + + singleton=p_string; + + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptEngineSingleton::get_singleton() { + return singleton; +} + + + +class VisualScriptNodeInstanceEngineSingleton : public VisualScriptNodeInstance { +public: + + Object* singleton; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=singleton; + return 0; + } + +}; + +VisualScriptNodeInstance* VisualScriptEngineSingleton::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceEngineSingleton * instance = memnew(VisualScriptNodeInstanceEngineSingleton ); + instance->singleton=GlobalConfig::get_singleton()->get_singleton_object(singleton); + return instance; +} + +VisualScriptEngineSingleton::TypeGuess VisualScriptEngineSingleton::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + Object *obj=GlobalConfig::get_singleton()->get_singleton_object(singleton); + TypeGuess tg; + tg.type=Variant::OBJECT; + if (obj) { + tg.GDCLASS=obj->get_class(); + tg.script=obj->get_script(); + } + + return tg; +} + + +void VisualScriptEngineSingleton::_bind_methods() { + + ClassDB::bind_method(_MD("set_singleton","name"),&VisualScriptEngineSingleton::set_singleton); + ClassDB::bind_method(_MD("get_singleton"),&VisualScriptEngineSingleton::get_singleton); + + String cc; + + List<GlobalConfig::Singleton> singletons; + + GlobalConfig::get_singleton()->get_singletons(&singletons); + + for (List<GlobalConfig::Singleton>::Element *E=singletons.front();E;E=E->next()) { + if (E->get().name=="VS" || E->get().name=="PS" || E->get().name=="PS2D" || E->get().name=="AS" || E->get().name=="TS" || E->get().name=="SS" || E->get().name=="SS2D") + continue; //skip these, too simple named + + if (cc!=String()) + cc+=","; + cc+=E->get().name; + } + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"constant",PROPERTY_HINT_ENUM,cc),_SCS("set_singleton"),_SCS("get_singleton")); +} + +VisualScriptEngineSingleton::VisualScriptEngineSingleton() { + + singleton=String(); +} + + + +////////////////////////////////////////// +////////////////GETNODE/////////// +////////////////////////////////////////// + +int VisualScriptSceneNode::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptSceneNode::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptSceneNode::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSceneNode::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSceneNode::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSceneNode::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSceneNode::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"node"); +} + + +String VisualScriptSceneNode::get_caption() const { + + return "SceneNode"; +} + +String VisualScriptSceneNode::get_text() const { + + return path.simplified(); +} + +void VisualScriptSceneNode::set_node_path(const NodePath& p_path) { + + path=p_path; + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptSceneNode::get_node_path() { + return path; +} + + +class VisualScriptNodeInstanceSceneNode : public VisualScriptNodeInstance { +public: + + VisualScriptSceneNode *node; + VisualScriptInstance *instance; + NodePath path; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + + + Node* another = node->get_node(path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + *p_outputs[0]=another; + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSceneNode::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSceneNode * instance = memnew(VisualScriptNodeInstanceSceneNode ); + instance->node=this; + instance->instance=p_instance; + instance->path=path; + return instance; +} + + + + +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif + +VisualScriptSceneNode::TypeGuess VisualScriptSceneNode::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + + VisualScriptSceneNode::TypeGuess tg; + tg.type=Variant::OBJECT; + tg.GDCLASS="Node"; + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return tg; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return tg; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return tg; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return tg; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return tg; + + Node* another = script_node->get_node(path); + + if (another) { + tg.GDCLASS=another->get_class(); + tg.script=another->get_script(); + } +#endif + return tg; + +} + + +void VisualScriptSceneNode::_validate_property(PropertyInfo& property) const { + +#ifdef TOOLS_ENABLED + if (property.name=="node_path") { + + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return; + + property.hint_string=script_node->get_path(); + } +#endif +} + +void VisualScriptSceneNode::_bind_methods() { + + ClassDB::bind_method(_MD("set_node_path","path"),&VisualScriptSceneNode::set_node_path); + ClassDB::bind_method(_MD("get_node_path"),&VisualScriptSceneNode::get_node_path); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_node_path"),_SCS("get_node_path")); +} + +VisualScriptSceneNode::VisualScriptSceneNode() { + + path=String("."); +} + + +////////////////////////////////////////// +////////////////SceneTree/////////// +////////////////////////////////////////// + +int VisualScriptSceneTree::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptSceneTree::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptSceneTree::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSceneTree::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSceneTree::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSceneTree::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSceneTree::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + + +String VisualScriptSceneTree::get_caption() const { + + return "SceneTree"; +} + +String VisualScriptSceneTree::get_text() const { + + return ""; +} + + +class VisualScriptNodeInstanceSceneTree : public VisualScriptNodeInstance { +public: + + VisualScriptSceneTree *node; + VisualScriptInstance *instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + SceneTree* tree = node->get_tree(); + if (!tree) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Attempt to get SceneTree while node is not in the active tree."; + return 0; + } + + *p_outputs[0]=tree; + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSceneTree::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSceneTree * instance = memnew(VisualScriptNodeInstanceSceneTree ); + instance->node=this; + instance->instance=p_instance; + return instance; +} + +VisualScriptSceneTree::TypeGuess VisualScriptSceneTree::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + TypeGuess tg; + tg.type=Variant::OBJECT; + tg.GDCLASS="SceneTree"; + return tg; +} + +void VisualScriptSceneTree::_validate_property(PropertyInfo& property) const { + +} + +void VisualScriptSceneTree::_bind_methods() { + +} + +VisualScriptSceneTree::VisualScriptSceneTree() { + +} + + +////////////////////////////////////////// +////////////////RESPATH/////////// +////////////////////////////////////////// + +int VisualScriptResourcePath::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptResourcePath::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptResourcePath::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptResourcePath::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptResourcePath::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptResourcePath::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptResourcePath::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::STRING,"path"); +} + + +String VisualScriptResourcePath::get_caption() const { + + return "ResourcePath"; +} + +String VisualScriptResourcePath::get_text() const { + + return path; +} + +void VisualScriptResourcePath::set_resource_path(const String& p_path) { + + path=p_path; + _change_notify(); + ports_changed_notify(); +} + +String VisualScriptResourcePath::get_resource_path() { + return path; +} + + +class VisualScriptNodeInstanceResourcePath : public VisualScriptNodeInstance { +public: + + String path; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0] = path; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptResourcePath::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceResourcePath * instance = memnew(VisualScriptNodeInstanceResourcePath ); + instance->path=path; + return instance; +} + + + +void VisualScriptResourcePath::_bind_methods() { + + ClassDB::bind_method(_MD("set_resource_path","path"),&VisualScriptResourcePath::set_resource_path); + ClassDB::bind_method(_MD("get_resource_path"),&VisualScriptResourcePath::get_resource_path); + + ADD_PROPERTY(PropertyInfo(Variant::STRING,"path",PROPERTY_HINT_FILE),_SCS("set_resource_path"),_SCS("get_resource_path")); +} + +VisualScriptResourcePath::VisualScriptResourcePath() { + + path=""; +} + + + +////////////////////////////////////////// +////////////////SELF/////////// +////////////////////////////////////////// + +int VisualScriptSelf::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptSelf::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptSelf::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptSelf::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSelf::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSelf::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSelf::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::OBJECT,"instance"); +} + + +String VisualScriptSelf::get_caption() const { + + return "Self"; +} + +String VisualScriptSelf::get_text() const { + + if (get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else + return ""; +} + + +class VisualScriptNodeInstanceSelf : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0] = instance->get_owner_ptr(); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSelf::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSelf * instance = memnew(VisualScriptNodeInstanceSelf ); + instance->instance=p_instance; + return instance; +} + +VisualScriptSelf::TypeGuess VisualScriptSelf::guess_output_type(TypeGuess* p_inputs, int p_output) const { + + VisualScriptSceneNode::TypeGuess tg; + tg.type=Variant::OBJECT; + tg.GDCLASS="Object"; + + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return tg; + + tg.GDCLASS=script->get_instance_base_type(); + tg.script=script; + + return tg; + + +} + + +void VisualScriptSelf::_bind_methods() { + +} + +VisualScriptSelf::VisualScriptSelf() { + + +} + +////////////////////////////////////////// +////////////////CUSTOM (SCRIPTED)/////////// +////////////////////////////////////////// + +int VisualScriptCustomNode::get_output_sequence_port_count() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_output_sequence_port_count")) { + return get_script_instance()->call("_get_output_sequence_port_count"); + } + return 0; +} + +bool VisualScriptCustomNode::has_input_sequence_port() const{ + + if (get_script_instance() && get_script_instance()->has_method("_has_input_sequence_port")) { + return get_script_instance()->call("_has_input_sequence_port"); + } + return false; +} + +int VisualScriptCustomNode::get_input_value_port_count() const{ + + if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_count")) { + return get_script_instance()->call("_get_input_value_port_count"); + } + return 0; +} +int VisualScriptCustomNode::get_output_value_port_count() const{ + + if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_count")) { + return get_script_instance()->call("_get_output_value_port_count"); + } + return 0; +} + +String VisualScriptCustomNode::get_output_sequence_port_text(int p_port) const { + + if (get_script_instance() && get_script_instance()->has_method("_get_output_sequence_port_text")) { + return get_script_instance()->call("_get_output_sequence_port_text",p_port); + } + + return String(); +} + +PropertyInfo VisualScriptCustomNode::get_input_value_port_info(int p_idx) const{ + + PropertyInfo info; + if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_type")) { + info.type=Variant::Type(int(get_script_instance()->call("_get_input_value_port_type",p_idx))); + } + if (get_script_instance() && get_script_instance()->has_method("_get_input_value_port_name")) { + info.name=get_script_instance()->call("_get_input_value_port_name",p_idx); + } + return info; +} + +PropertyInfo VisualScriptCustomNode::get_output_value_port_info(int p_idx) const{ + + PropertyInfo info; + if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_type")) { + info.type=Variant::Type(int(get_script_instance()->call("_get_output_value_port_type",p_idx))); + } + if (get_script_instance() && get_script_instance()->has_method("_get_output_value_port_name")) { + info.name=get_script_instance()->call("_get_output_value_port_name",p_idx); + } + return info; +} + + +String VisualScriptCustomNode::get_caption() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_caption")) { + return get_script_instance()->call("_get_caption"); + } + return "CustomNode"; +} + +String VisualScriptCustomNode::get_text() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_text")) { + return get_script_instance()->call("_get_text"); + } + return ""; +} + +String VisualScriptCustomNode::get_category() const { + + if (get_script_instance() && get_script_instance()->has_method("_get_category")) { + return get_script_instance()->call("_get_category"); + } + return "custom"; +} + +class VisualScriptNodeInstanceCustomNode : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + VisualScriptCustomNode *node; + int in_count; + int out_count; + int work_mem_size; + + virtual int get_working_memory_size() const { return work_mem_size; } + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (node->get_script_instance()) { +#ifdef DEBUG_ENABLED + if (!node->get_script_instance()->has_method(VisualScriptLanguage::singleton->_step)) { + r_error_str=RTR("Custom node has no _step() method, can't process graph."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } +#endif + Array in_values; + Array out_values; + Array work_mem; + + in_values.resize(in_count); + + for(int i=0;i<in_count;i++) { + in_values[i]=p_inputs[i]; + } + + out_values.resize(in_count); + + work_mem.resize(work_mem_size); + + for(int i=0;i<work_mem_size;i++) { + work_mem[i]=p_working_mem[i]; + } + + int ret_out; + + Variant ret = node->get_script_instance()->call(VisualScriptLanguage::singleton->_step,in_values,out_values,p_start_mode,work_mem); + if (ret.get_type()==Variant::STRING) { + r_error_str=ret; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } else if (ret.is_num()) { + ret_out=ret; + } else { + r_error_str=RTR("Invalid return value from _step(), must be integer (seq out), or string (error)."); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + for(int i=0;i<out_count;i++) { + if (i<out_values.size()) { + *p_outputs[i]=out_values[i]; + } + } + + for(int i=0;i<work_mem_size;i++) { + if (i<work_mem.size()) { + p_working_mem[i]=work_mem[i]; + } + } + + return ret_out; + + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptCustomNode::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceCustomNode * instance = memnew(VisualScriptNodeInstanceCustomNode ); + instance->instance=p_instance; + instance->in_count=get_input_value_port_count(); + instance->out_count=get_output_value_port_count(); + + + if (get_script_instance() && get_script_instance()->has_method("_get_working_memory_size")) { + instance->work_mem_size = get_script_instance()->call("_get_working_memory_size"); + } else { + instance->work_mem_size=0; + } + + return instance; +} + + + +void VisualScriptCustomNode::_bind_methods() { + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_output_sequence_port_count") ); + BIND_VMETHOD( MethodInfo(Variant::BOOL,"_has_input_sequence_port") ); + + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_output_sequence_port_text",PropertyInfo(Variant::INT,"idx")) ); + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_input_value_port_count") ); + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_output_value_port_count") ); + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_input_value_port_type",PropertyInfo(Variant::INT,"idx")) ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_input_value_port_name",PropertyInfo(Variant::INT,"idx")) ); + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_output_value_port_type",PropertyInfo(Variant::INT,"idx")) ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_output_value_port_name",PropertyInfo(Variant::INT,"idx")) ); + + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_caption") ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_text") ); + BIND_VMETHOD( MethodInfo(Variant::STRING,"_get_category") ); + + BIND_VMETHOD( MethodInfo(Variant::INT,"_get_working_memory_size") ); + BIND_VMETHOD( MethodInfo(Variant::NIL,"_step:Variant",PropertyInfo(Variant::ARRAY,"inputs"),PropertyInfo(Variant::ARRAY,"outputs"),PropertyInfo(Variant::INT,"start_mode"),PropertyInfo(Variant::ARRAY,"working_mem")) ); + + BIND_CONSTANT( START_MODE_BEGIN_SEQUENCE ); + BIND_CONSTANT( START_MODE_CONTINUE_SEQUENCE ); + BIND_CONSTANT( START_MODE_RESUME_YIELD ); + + BIND_CONSTANT( STEP_PUSH_STACK_BIT ); + BIND_CONSTANT( STEP_GO_BACK_BIT ); + BIND_CONSTANT( STEP_NO_ADVANCE_BIT ); + BIND_CONSTANT( STEP_EXIT_FUNCTION_BIT ); + BIND_CONSTANT( STEP_YIELD_BIT ); + +} + +VisualScriptCustomNode::VisualScriptCustomNode() { + + +} + +////////////////////////////////////////// +////////////////SUBCALL/////////// +////////////////////////////////////////// + +int VisualScriptSubCall::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptSubCall::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptSubCall::get_input_value_port_count() const{ + + Ref<Script> script = get_script(); + + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + + MethodInfo mi = script->get_method_info(VisualScriptLanguage::singleton->_subcall); + return mi.arguments.size(); + } + + return 0; +} +int VisualScriptSubCall::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptSubCall::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptSubCall::get_input_value_port_info(int p_idx) const{ + + Ref<Script> script = get_script(); + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + + MethodInfo mi = script->get_method_info(VisualScriptLanguage::singleton->_subcall); + return mi.arguments[p_idx]; + } + + return PropertyInfo(); +} + +PropertyInfo VisualScriptSubCall::get_output_value_port_info(int p_idx) const{ + + Ref<Script> script = get_script(); + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + MethodInfo mi = script->get_method_info(VisualScriptLanguage::singleton->_subcall); + return mi.return_val; + } + return PropertyInfo(); +} + + +String VisualScriptSubCall::get_caption() const { + + return "SubCall"; +} + + +String VisualScriptSubCall::get_text() const { + + Ref<Script> script = get_script(); + if (script.is_valid()) { + if (script->get_name()!=String()) + return script->get_name(); + if (script->get_path().is_resource_file()) + return script->get_path().get_file(); + return script->get_class(); + } + return ""; +} + +String VisualScriptSubCall::get_category() const { + + return "custom"; +} + +class VisualScriptNodeInstanceSubCall : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + VisualScriptSubCall *subcall; + int input_args; + bool valid; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (!valid) { + r_error_str="Node requires a script with a _subcall(<args>) method to work."; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + *p_outputs[0]=subcall->call(VisualScriptLanguage::singleton->_subcall,p_inputs,input_args,r_error_str); + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptSubCall::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceSubCall * instance = memnew(VisualScriptNodeInstanceSubCall ); + instance->instance=p_instance; + Ref<Script> script = get_script(); + if (script.is_valid() && script->has_method(VisualScriptLanguage::singleton->_subcall)) { + instance->valid=true; + instance->input_args=get_input_value_port_count(); + } else { + instance->valid=false; + } + return instance; +} + + + +void VisualScriptSubCall::_bind_methods() { + + BIND_VMETHOD( MethodInfo(Variant::NIL,"_subcall",PropertyInfo(Variant::NIL,"arguments:Variant")) ); + +} + +VisualScriptSubCall::VisualScriptSubCall() { + + +} + +////////////////////////////////////////// +////////////////Comment/////////// +////////////////////////////////////////// + +int VisualScriptComment::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptComment::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptComment::get_input_value_port_count() const{ + return 0; +} +int VisualScriptComment::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptComment::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptComment::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptComment::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptComment::get_caption() const { + + return title; +} + + +String VisualScriptComment::get_text() const { + + return description; +} + +void VisualScriptComment::set_title(const String& p_title) { + + + if (title==p_title) + return; + title=p_title; + ports_changed_notify(); +} + +String VisualScriptComment::get_title() const{ + + return title; +} + +void VisualScriptComment::set_description(const String& p_description){ + + if (description==p_description) + return; + description=p_description; + ports_changed_notify(); + +} +String VisualScriptComment::get_description() const{ + + return description; +} + +void VisualScriptComment::set_size(const Size2& p_size){ + + if (size==p_size) + return; + size=p_size; + ports_changed_notify(); + +} +Size2 VisualScriptComment::get_size() const{ + + return size; +} + + +String VisualScriptComment::get_category() const { + + return "data"; +} + +class VisualScriptNodeInstanceComment : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptComment::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceComment * instance = memnew(VisualScriptNodeInstanceComment ); + instance->instance=p_instance; + return instance; +} + + + +void VisualScriptComment::_bind_methods() { + + + ClassDB::bind_method(_MD("set_title","title"),&VisualScriptComment::set_title); + ClassDB::bind_method(_MD("get_title"),&VisualScriptComment::get_title); + + ClassDB::bind_method(_MD("set_description","description"),&VisualScriptComment::set_description); + ClassDB::bind_method(_MD("get_description"),&VisualScriptComment::get_description); + + ClassDB::bind_method(_MD("set_size","size"),&VisualScriptComment::set_size); + ClassDB::bind_method(_MD("get_size"),&VisualScriptComment::get_size); + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"title"),_SCS("set_title"),_SCS("get_title")); + ADD_PROPERTY( PropertyInfo(Variant::STRING,"description",PROPERTY_HINT_MULTILINE_TEXT),_SCS("set_description"),_SCS("get_description")); + ADD_PROPERTY( PropertyInfo(Variant::VECTOR2,"size"),_SCS("set_size"),_SCS("get_size")); + +} + +VisualScriptComment::VisualScriptComment() { + + title="Comment"; + size=Size2(150,150); +} + + +////////////////////////////////////////// +////////////////Constructor/////////// +////////////////////////////////////////// + +int VisualScriptConstructor::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptConstructor::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptConstructor::get_input_value_port_count() const{ + return constructor.arguments.size(); +} +int VisualScriptConstructor::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptConstructor::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptConstructor::get_input_value_port_info(int p_idx) const{ + + return constructor.arguments[p_idx]; +} + +PropertyInfo VisualScriptConstructor::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"value"); +} + + +String VisualScriptConstructor::get_caption() const { + + return "Construct"; +} + + +String VisualScriptConstructor::get_text() const { + + return "new "+Variant::get_type_name(type)+"()"; +} + + +String VisualScriptConstructor::get_category() const { + + return "functions"; +} + +void VisualScriptConstructor::set_constructor_type(Variant::Type p_type) { + + if (type==p_type) + return; + + type=p_type; + ports_changed_notify(); +} + +Variant::Type VisualScriptConstructor::get_constructor_type() const { + + return type; +} + +void VisualScriptConstructor::set_constructor(const Dictionary& p_info) { + + constructor=MethodInfo::from_dict(p_info); + ports_changed_notify(); +} + +Dictionary VisualScriptConstructor::get_constructor() const { + + return constructor; +} + + +class VisualScriptNodeInstanceConstructor : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + Variant::Type type; + int argcount; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Variant::CallError ce; + *p_outputs[0]=Variant::construct(type,p_inputs,argcount,ce); + if (ce.error!=Variant::CallError::CALL_OK) { + r_error_str="Invalid arguments for constructor"; + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptConstructor::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceConstructor * instance = memnew(VisualScriptNodeInstanceConstructor ); + instance->instance=p_instance; + instance->type=type; + instance->argcount=constructor.arguments.size(); + return instance; +} + + + +void VisualScriptConstructor::_bind_methods() { + + ClassDB::bind_method(_MD("set_constructor_type","type"),&VisualScriptConstructor::set_constructor_type); + ClassDB::bind_method(_MD("get_constructor_type"),&VisualScriptConstructor::get_constructor_type); + + ClassDB::bind_method(_MD("set_constructor","constructor"),&VisualScriptConstructor::set_constructor); + ClassDB::bind_method(_MD("get_constructor"),&VisualScriptConstructor::get_constructor); + + ADD_PROPERTY( PropertyInfo(Variant::INT,"type",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("set_constructor_type"),_SCS("get_constructor_type")); + ADD_PROPERTY( PropertyInfo(Variant::DICTIONARY,"constructor",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("set_constructor"),_SCS("get_constructor")); + +} + +VisualScriptConstructor::VisualScriptConstructor() { + + type=Variant::NIL; + +} + +static Map<String,Pair<Variant::Type,MethodInfo> > constructor_map; + +static Ref<VisualScriptNode> create_constructor_node(const String& p_name) { + + ERR_FAIL_COND_V(!constructor_map.has(p_name),Ref<VisualScriptNode>()); + + Ref<VisualScriptConstructor> vsc; + vsc.instance(); + vsc->set_constructor_type(constructor_map[p_name].first); + vsc->set_constructor(constructor_map[p_name].second); + + return vsc; +} + +////////////////////////////////////////// +////////////////LocalVar/////////// +////////////////////////////////////////// + +int VisualScriptLocalVar::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptLocalVar::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptLocalVar::get_input_value_port_count() const{ + return 0; +} +int VisualScriptLocalVar::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptLocalVar::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptLocalVar::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} +PropertyInfo VisualScriptLocalVar::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"get"); +} + + +String VisualScriptLocalVar::get_caption() const { + + return "LocalVarGet"; +} + + +String VisualScriptLocalVar::get_text() const { + + return name; +} + + +String VisualScriptLocalVar::get_category() const { + + return "data"; +} + + +void VisualScriptLocalVar::set_var_name(const StringName& p_name) { + + if (name==p_name) + return; + + name=p_name; + ports_changed_notify(); + +} + +StringName VisualScriptLocalVar::get_var_name() const { + + return name; +} + +void VisualScriptLocalVar::set_var_type(Variant::Type p_type) { + + type=p_type; + ports_changed_notify(); +} + +Variant::Type VisualScriptLocalVar::get_var_type() const { + + return type; +} + + +class VisualScriptNodeInstanceLocalVar : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName name; + + + virtual int get_working_memory_size() const { return 1; } + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_outputs[0]=*p_working_mem; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptLocalVar::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceLocalVar * instance = memnew(VisualScriptNodeInstanceLocalVar ); + instance->instance=p_instance; + instance->name=name; + + return instance; +} + + + +void VisualScriptLocalVar::_bind_methods() { + + ClassDB::bind_method(_MD("set_var_name","name"),&VisualScriptLocalVar::set_var_name); + ClassDB::bind_method(_MD("get_var_name"),&VisualScriptLocalVar::get_var_name); + + ClassDB::bind_method(_MD("set_var_type","type"),&VisualScriptLocalVar::set_var_type); + ClassDB::bind_method(_MD("get_var_type"),&VisualScriptLocalVar::get_var_type); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_var_name"),_SCS("get_var_name")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"variable/type",PROPERTY_HINT_ENUM,argt),_SCS("set_var_type"),_SCS("get_var_type")); + + +} + +VisualScriptLocalVar::VisualScriptLocalVar() { + + name="new_local"; + type=Variant::NIL; + +} + +////////////////////////////////////////// +////////////////LocalVar/////////// +////////////////////////////////////////// + +int VisualScriptLocalVarSet::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptLocalVarSet::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptLocalVarSet::get_input_value_port_count() const{ + return 1; +} +int VisualScriptLocalVarSet::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptLocalVarSet::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptLocalVarSet::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"set"); +} +PropertyInfo VisualScriptLocalVarSet::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"get"); +} + + +String VisualScriptLocalVarSet::get_caption() const { + + return "LocalVarSet"; +} + + +String VisualScriptLocalVarSet::get_text() const { + + return name; +} + + +String VisualScriptLocalVarSet::get_category() const { + + return "data"; +} + + +void VisualScriptLocalVarSet::set_var_name(const StringName& p_name) { + + if (name==p_name) + return; + + name=p_name; + ports_changed_notify(); + +} + +StringName VisualScriptLocalVarSet::get_var_name() const { + + return name; +} + +void VisualScriptLocalVarSet::set_var_type(Variant::Type p_type) { + + type=p_type; + ports_changed_notify(); +} + +Variant::Type VisualScriptLocalVarSet::get_var_type() const { + + return type; +} + + +class VisualScriptNodeInstanceLocalVarSet : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName name; + + + virtual int get_working_memory_size() const { return 1; } + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + *p_working_mem=*p_inputs[0]; + *p_outputs[0]=*p_working_mem; + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptLocalVarSet::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceLocalVarSet * instance = memnew(VisualScriptNodeInstanceLocalVarSet ); + instance->instance=p_instance; + instance->name=name; + + return instance; +} + + + +void VisualScriptLocalVarSet::_bind_methods() { + + ClassDB::bind_method(_MD("set_var_name","name"),&VisualScriptLocalVarSet::set_var_name); + ClassDB::bind_method(_MD("get_var_name"),&VisualScriptLocalVarSet::get_var_name); + + ClassDB::bind_method(_MD("set_var_type","type"),&VisualScriptLocalVarSet::set_var_type); + ClassDB::bind_method(_MD("get_var_type"),&VisualScriptLocalVarSet::get_var_type); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"variable/name"),_SCS("set_var_name"),_SCS("get_var_name")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"variable/type",PROPERTY_HINT_ENUM,argt),_SCS("set_var_type"),_SCS("get_var_type")); + + +} + +VisualScriptLocalVarSet::VisualScriptLocalVarSet() { + + name="new_local"; + type=Variant::NIL; + +} + + +////////////////////////////////////////// +////////////////LocalVar/////////// +////////////////////////////////////////// + +int VisualScriptInputAction::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptInputAction::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptInputAction::get_input_value_port_count() const{ + return 0; +} +int VisualScriptInputAction::get_output_value_port_count() const{ + + return 1; +} + +String VisualScriptInputAction::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptInputAction::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} +PropertyInfo VisualScriptInputAction::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(Variant::BOOL,"pressed"); +} + + +String VisualScriptInputAction::get_caption() const { + + + return "Action"; +} + + +String VisualScriptInputAction::get_text() const { + + switch(mode) { + case MODE_PRESSED: { + return name; + } break; + case MODE_RELEASED: { + return "not "+name; + } break; + case MODE_JUST_PRESSED: { + return String(name)+" "+TTR("just pressed"); + } break; + case MODE_JUST_RELEASED: { + return String(name)+" "+TTR("just released"); + } break; + } + + return String(); +} + + +String VisualScriptInputAction::get_category() const { + + return "data"; +} + + +void VisualScriptInputAction::set_action_name(const StringName& p_name) { + + if (name==p_name) + return; + + name=p_name; + ports_changed_notify(); + +} + +StringName VisualScriptInputAction::get_action_name() const { + + return name; +} + +void VisualScriptInputAction::set_action_mode(Mode p_mode) { + + if (mode==p_mode) + return; + + mode=p_mode; + ports_changed_notify(); + +} +VisualScriptInputAction::Mode VisualScriptInputAction::get_action_mode() const { + + return mode; +} + + +class VisualScriptNodeInstanceInputAction : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + StringName action; + VisualScriptInputAction::Mode mode; + + + virtual int get_working_memory_size() const { return 1; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + switch(mode) { + case VisualScriptInputAction::MODE_PRESSED: { + *p_outputs[0]=Input::get_singleton()->is_action_pressed(action); + } break; + case VisualScriptInputAction::MODE_RELEASED: { + *p_outputs[0]=!Input::get_singleton()->is_action_pressed(action); + } break; + case VisualScriptInputAction::MODE_JUST_PRESSED: { + *p_outputs[0]=Input::get_singleton()->is_action_just_pressed(action); + } break; + case VisualScriptInputAction:: MODE_JUST_RELEASED: { + *p_outputs[0]=Input::get_singleton()->is_action_just_released(action); + } break; + + } + + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptInputAction::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceInputAction * instance = memnew(VisualScriptNodeInstanceInputAction ); + instance->instance=p_instance; + instance->action=name; + instance->mode=mode; + + return instance; +} + +void VisualScriptInputAction::_validate_property(PropertyInfo& property) const { + + + if (property.name=="action") { + + property.hint=PROPERTY_HINT_ENUM; + String actions; + + List<PropertyInfo> pinfo; + GlobalConfig::get_singleton()->get_property_list(&pinfo); + Vector<String> al; + + for(List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + const PropertyInfo &pi=E->get(); + + if (!pi.name.begins_with("input/")) + continue; + + String name = pi.name.substr(pi.name.find("/")+1,pi.name.length()); + + + al.push_back(name); + } + + al.sort();; + + for(int i=0;i<al.size();i++) { + if (actions!=String()) + actions+=","; + actions+=al[i]; + } + + property.hint_string=actions; + } +} + + +void VisualScriptInputAction::_bind_methods() { + + ClassDB::bind_method(_MD("set_action_name","name"),&VisualScriptInputAction::set_action_name); + ClassDB::bind_method(_MD("get_action_name"),&VisualScriptInputAction::get_action_name); + + ClassDB::bind_method(_MD("set_action_mode","mode"),&VisualScriptInputAction::set_action_mode); + ClassDB::bind_method(_MD("get_action_mode"),&VisualScriptInputAction::get_action_mode); + + ADD_PROPERTY( PropertyInfo(Variant::STRING,"action"),_SCS("set_action_name"),_SCS("get_action_name")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"mode",PROPERTY_HINT_ENUM,"Pressed,Released,JustPressed,JustReleased"),_SCS("set_action_mode"),_SCS("get_action_mode")); + +} + +VisualScriptInputAction::VisualScriptInputAction() { + + name=""; + mode=MODE_PRESSED; + +} + +////////////////////////////////////////// +////////////////Constructor/////////// +////////////////////////////////////////// + +int VisualScriptDeconstruct::get_output_sequence_port_count() const { + + return 0; +} + +bool VisualScriptDeconstruct::has_input_sequence_port() const{ + + return false; +} + +int VisualScriptDeconstruct::get_input_value_port_count() const{ + return 1; +} +int VisualScriptDeconstruct::get_output_value_port_count() const{ + + return elements.size(); +} + +String VisualScriptDeconstruct::get_output_sequence_port_text(int p_port) const { + + return ""; +} + +PropertyInfo VisualScriptDeconstruct::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(type,"value"); +} + +PropertyInfo VisualScriptDeconstruct::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(elements[p_idx].type,elements[p_idx].name); +} + + +String VisualScriptDeconstruct::get_caption() const { + + return "Deconstruct"; +} + + +String VisualScriptDeconstruct::get_text() const { + + return "from "+Variant::get_type_name(type)+":"; +} + + +String VisualScriptDeconstruct::get_category() const { + + return "functions"; +} + +void VisualScriptDeconstruct::_update_elements() { + + elements.clear();; + Variant v; + if (type==Variant::INPUT_EVENT) { + InputEvent ie; + ie.type=input_type; + v=ie; + } else { + Variant::CallError ce; + v = Variant::construct(type,NULL,0,ce); + } + + List<PropertyInfo> pinfo; + v.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E=pinfo.front();E;E=E->next()) { + + Element e; + e.name=E->get().name; + e.type=E->get().type; + elements.push_back(e); + } +} + +void VisualScriptDeconstruct::set_deconstruct_type(Variant::Type p_type) { + + if (type==p_type) + return; + + type=p_type; + _update_elements(); + ports_changed_notify(); + _change_notify(); //to make input appear/disappear +} + +Variant::Type VisualScriptDeconstruct::get_deconstruct_type() const { + + return type; +} + +void VisualScriptDeconstruct::set_deconstruct_input_type(InputEvent::Type p_input_type) { + + if (input_type==p_input_type) + return; + + input_type=p_input_type; + _update_elements(); + ports_changed_notify(); +} + +InputEvent::Type VisualScriptDeconstruct::get_deconstruct_input_type() const { + + return input_type; +} + +void VisualScriptDeconstruct::_set_elem_cache(const Array& p_elements) { + + ERR_FAIL_COND(p_elements.size()%2==1); + elements.resize(p_elements.size()/2); + for(int i=0;i<elements.size();i++) { + elements[i].name=p_elements[i*2+0]; + elements[i].type=Variant::Type(int(p_elements[i*2+1])); + } +} + +Array VisualScriptDeconstruct::_get_elem_cache() const { + + Array ret; + for(int i=0;i<elements.size();i++) { + ret.push_back(elements[i].name); + ret.push_back(elements[i].type); + } + return ret; +} + +class VisualScriptNodeInstanceDeconstruct : public VisualScriptNodeInstance { +public: + + VisualScriptInstance* instance; + Vector<StringName> outputs; + + //virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + Variant in=*p_inputs[0]; + + for(int i=0;i<outputs.size();i++) { + bool valid; + *p_outputs[i]=in.get(outputs[i],&valid); + if (!valid) { + r_error_str="Can't obtain element '"+String(outputs[i])+"' from "+Variant::get_type_name(in.get_type()); + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + } + + return 0; + } + + +}; + +VisualScriptNodeInstance* VisualScriptDeconstruct::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceDeconstruct * instance = memnew(VisualScriptNodeInstanceDeconstruct ); + instance->instance=p_instance; + instance->outputs.resize(elements.size()); + for(int i=0;i<elements.size();i++) { + instance->outputs[i]=elements[i].name; + } + + return instance; +} + + + +void VisualScriptDeconstruct::_validate_property(PropertyInfo& property) const { + + if (property.name=="input_type") { + if (type!=Variant::INPUT_EVENT) { + property.usage=0; + } + } +} + + +void VisualScriptDeconstruct::_bind_methods() { + + ClassDB::bind_method(_MD("set_deconstruct_type","type"),&VisualScriptDeconstruct::set_deconstruct_type); + ClassDB::bind_method(_MD("get_deconstruct_type"),&VisualScriptDeconstruct::get_deconstruct_type); + + ClassDB::bind_method(_MD("set_deconstruct_input_type","input_type"),&VisualScriptDeconstruct::set_deconstruct_input_type); + ClassDB::bind_method(_MD("get_deconstruct_input_type"),&VisualScriptDeconstruct::get_deconstruct_input_type); + + ClassDB::bind_method(_MD("_set_elem_cache","_cache"),&VisualScriptDeconstruct::_set_elem_cache); + ClassDB::bind_method(_MD("_get_elem_cache"),&VisualScriptDeconstruct::_get_elem_cache); + + String argt="Any"; + for(int i=1;i<Variant::VARIANT_MAX;i++) { + argt+=","+Variant::get_type_name(Variant::Type(i)); + } + + String iet="None,Key,MouseMotion,MouseButton,JoypadMotion,JoypadButton,ScreenTouch,ScreenDrag,Action"; + + ADD_PROPERTY( PropertyInfo(Variant::INT,"type",PROPERTY_HINT_ENUM,argt),_SCS("set_deconstruct_type"),_SCS("get_deconstruct_type")); + ADD_PROPERTY( PropertyInfo(Variant::INT,"input_type",PROPERTY_HINT_ENUM,iet),_SCS("set_deconstruct_input_type"),_SCS("get_deconstruct_input_type")); + ADD_PROPERTY( PropertyInfo(Variant::ARRAY,"elem_cache",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_elem_cache"),_SCS("_get_elem_cache")); + +} + +VisualScriptDeconstruct::VisualScriptDeconstruct() { + + type=Variant::NIL; + input_type=InputEvent::NONE; + +} + + +void register_visual_script_nodes() { + + VisualScriptLanguage::singleton->add_register_func("data/set_variable",create_node_generic<VisualScriptVariableSet>); + VisualScriptLanguage::singleton->add_register_func("data/get_variable",create_node_generic<VisualScriptVariableGet>); + VisualScriptLanguage::singleton->add_register_func("data/engine_singleton",create_node_generic<VisualScriptEngineSingleton>); + VisualScriptLanguage::singleton->add_register_func("data/scene_node",create_node_generic<VisualScriptSceneNode>); + VisualScriptLanguage::singleton->add_register_func("data/scene_tree",create_node_generic<VisualScriptSceneTree>); + VisualScriptLanguage::singleton->add_register_func("data/resource_path",create_node_generic<VisualScriptResourcePath>); + VisualScriptLanguage::singleton->add_register_func("data/self",create_node_generic<VisualScriptSelf>); + VisualScriptLanguage::singleton->add_register_func("data/comment",create_node_generic<VisualScriptComment>); + VisualScriptLanguage::singleton->add_register_func("data/get_local_variable",create_node_generic<VisualScriptLocalVar>); + VisualScriptLanguage::singleton->add_register_func("data/set_local_variable",create_node_generic<VisualScriptLocalVarSet>); + VisualScriptLanguage::singleton->add_register_func("data/preload",create_node_generic<VisualScriptPreload>); + VisualScriptLanguage::singleton->add_register_func("data/action",create_node_generic<VisualScriptInputAction>); + + VisualScriptLanguage::singleton->add_register_func("constants/constant",create_node_generic<VisualScriptConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/math_constant",create_node_generic<VisualScriptMathConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/class_constant",create_node_generic<VisualScriptClassConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/global_constant",create_node_generic<VisualScriptGlobalConstant>); + VisualScriptLanguage::singleton->add_register_func("constants/basic_type_constant",create_node_generic<VisualScriptBasicTypeConstant>); + + VisualScriptLanguage::singleton->add_register_func("custom/custom_node",create_node_generic<VisualScriptCustomNode>); + VisualScriptLanguage::singleton->add_register_func("custom/sub_call",create_node_generic<VisualScriptSubCall>); + + VisualScriptLanguage::singleton->add_register_func("index/get_index",create_node_generic<VisualScriptIndexGet>); + VisualScriptLanguage::singleton->add_register_func("index/set_index",create_node_generic<VisualScriptIndexSet>); + + + VisualScriptLanguage::singleton->add_register_func("operators/compare/equal",create_op_node<Variant::OP_EQUAL>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/not_equal",create_op_node<Variant::OP_NOT_EQUAL>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/less",create_op_node<Variant::OP_LESS>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/less_equal",create_op_node<Variant::OP_LESS_EQUAL>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/greater",create_op_node<Variant::OP_GREATER>); + VisualScriptLanguage::singleton->add_register_func("operators/compare/greater_equal",create_op_node<Variant::OP_GREATER_EQUAL>); + //mathematic + VisualScriptLanguage::singleton->add_register_func("operators/math/add",create_op_node<Variant::OP_ADD>); + VisualScriptLanguage::singleton->add_register_func("operators/math/subtract",create_op_node<Variant::OP_SUBSTRACT>); + VisualScriptLanguage::singleton->add_register_func("operators/math/multiply",create_op_node<Variant::OP_MULTIPLY>); + VisualScriptLanguage::singleton->add_register_func("operators/math/divide",create_op_node<Variant::OP_DIVIDE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/negate",create_op_node<Variant::OP_NEGATE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/positive",create_op_node<Variant::OP_POSITIVE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/remainder",create_op_node<Variant::OP_MODULE>); + VisualScriptLanguage::singleton->add_register_func("operators/math/string_concat",create_op_node<Variant::OP_STRING_CONCAT>); + //bitwise + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/shift_left",create_op_node<Variant::OP_SHIFT_LEFT>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/shift_right",create_op_node<Variant::OP_SHIFT_RIGHT>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_and",create_op_node<Variant::OP_BIT_AND>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_or",create_op_node<Variant::OP_BIT_OR>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_xor",create_op_node<Variant::OP_BIT_XOR>); + VisualScriptLanguage::singleton->add_register_func("operators/bitwise/bit_negate",create_op_node<Variant::OP_BIT_NEGATE>); + //logic + VisualScriptLanguage::singleton->add_register_func("operators/logic/and",create_op_node<Variant::OP_AND>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/or",create_op_node<Variant::OP_OR>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/xor",create_op_node<Variant::OP_XOR>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/not",create_op_node<Variant::OP_NOT>); + VisualScriptLanguage::singleton->add_register_func("operators/logic/in",create_op_node<Variant::OP_IN>); + + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct",create_node_generic<VisualScriptDeconstruct>); + + for(int i=1;i<Variant::VARIANT_MAX;i++) { + + List<MethodInfo> constructors; + Variant::get_constructor_list(Variant::Type(i),&constructors); + + for(List<MethodInfo>::Element *E=constructors.front();E;E=E->next()) { + + if (E->get().arguments.size()>0) { + + + String name = "functions/constructors/"+Variant::get_type_name(Variant::Type(i))+" ( "; + for(int j=0;j<E->get().arguments.size();j++) { + if (j>0) + name+=", "; + if (E->get().arguments.size()==1) + name+=Variant::get_type_name(E->get().arguments[j].type); + else + name+=E->get().arguments[j].name; + } + name+=") "; + + VisualScriptLanguage::singleton->add_register_func(name,create_constructor_node); + Pair<Variant::Type,MethodInfo> pair; + pair.first=Variant::Type(i); + pair.second=E->get(); + constructor_map[name]=pair; + } + } + } +} + + + +void unregister_visual_script_nodes() { + + constructor_map.clear(); +} + diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h new file mode 100644 index 000000000..7a06fbf5e --- /dev/null +++ b/modules/visual_script/visual_script_nodes.h @@ -0,0 +1,1066 @@ +#ifndef VISUAL_SCRIPT_NODES_H +#define VISUAL_SCRIPT_NODES_H + +#include "visual_script.h" + +class VisualScriptFunction : public VisualScriptNode { + + GDCLASS(VisualScriptFunction,VisualScriptNode) + + + struct Argument { + String name; + Variant::Type type; + }; + + Vector<Argument> arguments; + + bool stack_less; + int stack_size; + ScriptInstance::RPCMode rpc_mode; + + +protected: + + bool _set(const StringName& p_name, const Variant& p_value); + bool _get(const StringName& p_name,Variant &r_ret) const; + void _get_property_list( List<PropertyInfo> *p_list) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "flow_control"; } + + void add_argument(Variant::Type p_type,const String& p_name,int p_index=-1); + void set_argument_type(int p_argidx,Variant::Type p_type); + Variant::Type get_argument_type(int p_argidx) const; + void set_argument_name(int p_argidx,const String& p_name); + String get_argument_name(int p_argidx) const; + void remove_argument(int p_argidx); + int get_argument_count() const; + + + void set_stack_less(bool p_enable); + bool is_stack_less() const; + + void set_stack_size(int p_size); + int get_stack_size() const; + + void set_rpc_mode(ScriptInstance::RPCMode p_mode); + ScriptInstance::RPCMode get_rpc_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptFunction(); +}; + + +class VisualScriptOperator : public VisualScriptNode { + + GDCLASS(VisualScriptOperator,VisualScriptNode) + + + Variant::Type typed; + Variant::Operator op; +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + void set_operator(Variant::Operator p_op); + Variant::Operator get_operator() const; + + void set_typed(Variant::Type p_op); + Variant::Type get_typed() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptOperator(); +}; + + +class VisualScriptVariableGet : public VisualScriptNode { + + GDCLASS(VisualScriptVariableGet,VisualScriptNode) + + + StringName variable; +protected: + + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_variable(StringName p_var); + StringName get_variable() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptVariableGet(); +}; + + +class VisualScriptVariableSet : public VisualScriptNode { + + GDCLASS(VisualScriptVariableSet,VisualScriptNode) + + + StringName variable; +protected: + + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_variable(StringName p_var); + StringName get_variable() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptVariableSet(); +}; + + +class VisualScriptConstant : public VisualScriptNode { + + GDCLASS(VisualScriptConstant,VisualScriptNode) + + + Variant::Type type; + Variant value; +protected: + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_constant_type(Variant::Type p_type); + Variant::Type get_constant_type() const; + + void set_constant_value(Variant p_value); + Variant get_constant_value() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptConstant(); +}; + + + +class VisualScriptPreload : public VisualScriptNode { + + GDCLASS(VisualScriptPreload,VisualScriptNode) + + + Ref<Resource> preload; +protected: + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_preload(const Ref<Resource>& p_value); + Ref<Resource> get_preload() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptPreload(); +}; + +class VisualScriptIndexGet : public VisualScriptNode { + + GDCLASS(VisualScriptIndexGet,VisualScriptNode) + + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptIndexGet(); +}; + + +class VisualScriptIndexSet : public VisualScriptNode { + + GDCLASS(VisualScriptIndexSet,VisualScriptNode) + + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "operators"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptIndexSet(); +}; + + + +class VisualScriptGlobalConstant : public VisualScriptNode { + + GDCLASS(VisualScriptGlobalConstant,VisualScriptNode) + + int index; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_global_constant(int p_which); + int get_global_constant(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptGlobalConstant(); +}; + + +class VisualScriptClassConstant : public VisualScriptNode { + + GDCLASS(VisualScriptClassConstant,VisualScriptNode) + + StringName base_type; + StringName name; +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo& property) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_class_constant(const StringName& p_which); + StringName get_class_constant(); + + void set_base_type(const StringName& p_which); + StringName get_base_type(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptClassConstant(); +}; + +class VisualScriptBasicTypeConstant : public VisualScriptNode { + + GDCLASS(VisualScriptBasicTypeConstant,VisualScriptNode) + + Variant::Type type; + StringName name; +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo& property) const; + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_basic_type_constant(const StringName& p_which); + StringName get_basic_type_constant() const; + + void set_basic_type(Variant::Type p_which); + Variant::Type get_basic_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptBasicTypeConstant(); +}; + + + +class VisualScriptMathConstant : public VisualScriptNode { + + GDCLASS(VisualScriptMathConstant,VisualScriptNode) +public: + + enum MathConstant { + MATH_CONSTANT_ONE, + MATH_CONSTANT_PI, + MATH_CONSTANT_2PI, + MATH_CONSTANT_HALF_PI, + MATH_CONSTANT_E, + MATH_CONSTANT_SQRT2, + MATH_CONSTANT_MAX, + }; + +private: + static const char* const_name[MATH_CONSTANT_MAX]; + static double const_value[MATH_CONSTANT_MAX]; + MathConstant constant; +protected: + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "constants"; } + + void set_math_constant(MathConstant p_which); + MathConstant get_math_constant(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptMathConstant(); +}; + +VARIANT_ENUM_CAST( VisualScriptMathConstant::MathConstant ) + +class VisualScriptEngineSingleton : public VisualScriptNode { + + GDCLASS(VisualScriptEngineSingleton,VisualScriptNode) + + String singleton; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_singleton(const String &p_string); + String get_singleton(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + + VisualScriptEngineSingleton(); +}; + + + + +class VisualScriptSceneNode : public VisualScriptNode { + + GDCLASS(VisualScriptSceneNode,VisualScriptNode) + + NodePath path; +protected: + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_node_path(const NodePath &p_path); + NodePath get_node_path(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptSceneNode(); +}; + + + + +class VisualScriptSceneTree : public VisualScriptNode { + + GDCLASS(VisualScriptSceneTree,VisualScriptNode) + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptSceneTree(); +}; + + + +class VisualScriptResourcePath : public VisualScriptNode { + + GDCLASS(VisualScriptResourcePath,VisualScriptNode) + + String path; +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + void set_resource_path(const String &p_path); + String get_resource_path(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptResourcePath(); +}; + + +class VisualScriptSelf : public VisualScriptNode { + + GDCLASS(VisualScriptSelf,VisualScriptNode) + + +protected: + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "data"; } + + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + virtual TypeGuess guess_output_type(TypeGuess* p_inputs, int p_output) const; + + VisualScriptSelf(); +}; + + +class VisualScriptCustomNode: public VisualScriptNode { + + GDCLASS(VisualScriptCustomNode,VisualScriptNode) + + +protected: + + static void _bind_methods(); +public: + + enum StartMode { //replicated for step + START_MODE_BEGIN_SEQUENCE, + START_MODE_CONTINUE_SEQUENCE, + START_MODE_RESUME_YIELD + }; + + enum { //replicated for step + STEP_SHIFT=1<<24, + STEP_MASK=STEP_SHIFT-1, + STEP_PUSH_STACK_BIT=STEP_SHIFT, //push bit to stack + STEP_GO_BACK_BIT=STEP_SHIFT<<1, //go back to previous node + STEP_NO_ADVANCE_BIT=STEP_SHIFT<<2, //do not advance past this node + STEP_EXIT_FUNCTION_BIT=STEP_SHIFT<<3, //return from function + STEP_YIELD_BIT=STEP_SHIFT<<4, //yield (will find VisualScriptFunctionState state in first working memory) + }; + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptCustomNode(); +}; + +class VisualScriptSubCall: public VisualScriptNode { + + GDCLASS(VisualScriptSubCall,VisualScriptNode) + + +protected: + + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptSubCall(); +}; + +class VisualScriptComment: public VisualScriptNode { + + GDCLASS(VisualScriptComment,VisualScriptNode) + + + String title; + String description; + Size2 size; +protected: + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_title(const String& p_title); + String get_title() const; + + void set_description(const String& p_description); + String get_description() const; + + void set_size(const Size2& p_size); + Size2 get_size() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptComment(); +}; + +class VisualScriptConstructor: public VisualScriptNode { + + GDCLASS(VisualScriptConstructor,VisualScriptNode) + + + Variant::Type type; + MethodInfo constructor; + +protected: + + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_constructor_type(Variant::Type p_type); + Variant::Type get_constructor_type() const; + + void set_constructor(const Dictionary& p_info); + Dictionary get_constructor() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptConstructor(); +}; + + + + +class VisualScriptLocalVar: public VisualScriptNode { + + GDCLASS(VisualScriptLocalVar,VisualScriptNode) + + StringName name; + Variant::Type type; + +protected: + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_var_name(const StringName& p_name); + StringName get_var_name() const; + + void set_var_type(Variant::Type p_type); + Variant::Type get_var_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptLocalVar(); +}; + +class VisualScriptLocalVarSet: public VisualScriptNode { + + GDCLASS(VisualScriptLocalVarSet,VisualScriptNode) + + StringName name; + Variant::Type type; + +protected: + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_var_name(const StringName& p_name); + StringName get_var_name() const; + + void set_var_type(Variant::Type p_type); + Variant::Type get_var_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptLocalVarSet(); +}; + + + +class VisualScriptInputAction: public VisualScriptNode { + + GDCLASS(VisualScriptInputAction,VisualScriptNode) +public: + enum Mode { + MODE_PRESSED, + MODE_RELEASED, + MODE_JUST_PRESSED, + MODE_JUST_RELEASED, + }; + + StringName name; + Mode mode; + +protected: + + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_action_name(const StringName& p_name); + StringName get_action_name() const; + + void set_action_mode(Mode p_mode); + Mode get_action_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptInputAction(); +}; + +VARIANT_ENUM_CAST( VisualScriptInputAction::Mode ) + +class VisualScriptDeconstruct: public VisualScriptNode { + + GDCLASS(VisualScriptDeconstruct,VisualScriptNode) + + + struct Element { + StringName name; + Variant::Type type; + }; + + + Vector<Element> elements; + + void _update_elements(); + Variant::Type type; + InputEvent::Type input_type; + + void _set_elem_cache(const Array& p_elements); + Array _get_elem_cache() const; + + virtual void _validate_property(PropertyInfo& property) const; + +protected: + + + static void _bind_methods(); +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const; + + void set_deconstruct_type(Variant::Type p_type); + Variant::Type get_deconstruct_type() const; + + void set_deconstruct_input_type(InputEvent::Type p_input_type); + InputEvent::Type get_deconstruct_input_type() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptDeconstruct(); +}; + + +void register_visual_script_nodes(); +void unregister_visual_script_nodes(); + +#endif // VISUAL_SCRIPT_NODES_H diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp new file mode 100644 index 000000000..8b251e667 --- /dev/null +++ b/modules/visual_script/visual_script_yield_nodes.cpp @@ -0,0 +1,625 @@ +#include "visual_script_yield_nodes.h" +#include "scene/main/scene_main_loop.h" +#include "os/os.h" +#include "scene/main/node.h" +#include "visual_script_nodes.h" + +////////////////////////////////////////// +////////////////YIELD/////////// +////////////////////////////////////////// + +int VisualScriptYield::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptYield::has_input_sequence_port() const{ + + return true; +} + +int VisualScriptYield::get_input_value_port_count() const{ + + return 0; +} +int VisualScriptYield::get_output_value_port_count() const{ + + return 0; +} + +String VisualScriptYield::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptYield::get_input_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + +PropertyInfo VisualScriptYield::get_output_value_port_info(int p_idx) const{ + + return PropertyInfo(); +} + + +String VisualScriptYield::get_caption() const { + + return yield_mode==YIELD_RETURN?"Yield":"Wait"; +} + +String VisualScriptYield::get_text() const { + + switch (yield_mode) { + case YIELD_RETURN: return ""; break; + case YIELD_FRAME: return "Next Frame"; break; + case YIELD_FIXED_FRAME: return "Next Fixed Frame"; break; + case YIELD_WAIT: return rtos(wait_time)+" sec(s)"; break; + } + + return String(); +} + + +class VisualScriptNodeInstanceYield : public VisualScriptNodeInstance { +public: + + VisualScriptYield::YieldMode mode; + float wait_time; + + virtual int get_working_memory_size() const { return 1; } //yield needs at least 1 + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return false; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_RESUME_YIELD) { + return 0; //resuming yield + } else { + //yield + + + SceneTree *tree = OS::get_singleton()->get_main_loop()->cast_to<SceneTree>(); + if (!tree) { + r_error_str="Main Loop is not SceneTree"; + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + return 0; + } + + Ref<VisualScriptFunctionState> state; + state.instance(); + + int ret = STEP_YIELD_BIT; + switch(mode) { + + case VisualScriptYield::YIELD_RETURN: ret=STEP_EXIT_FUNCTION_BIT; break; //return the yield + case VisualScriptYield::YIELD_FRAME: state->connect_to_signal(tree,"idle_frame",Array()); break; + case VisualScriptYield::YIELD_FIXED_FRAME: state->connect_to_signal(tree,"fixed_frame",Array()); break; + case VisualScriptYield::YIELD_WAIT: state->connect_to_signal(tree->create_timer(wait_time).ptr(),"timeout",Array()); break; + + } + + *p_working_mem=state; + + return ret; + } + } + +}; + +VisualScriptNodeInstance* VisualScriptYield::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceYield * instance = memnew(VisualScriptNodeInstanceYield ); + //instance->instance=p_instance; + instance->mode=yield_mode; + instance->wait_time=wait_time; + return instance; +} + +void VisualScriptYield::set_yield_mode(YieldMode p_mode) { + + if (yield_mode==p_mode) + return; + yield_mode=p_mode; + ports_changed_notify(); + _change_notify(); +} + +VisualScriptYield::YieldMode VisualScriptYield::get_yield_mode(){ + + return yield_mode; +} + +void VisualScriptYield::set_wait_time(float p_time) { + + if (wait_time==p_time) + return; + wait_time=p_time; + ports_changed_notify(); + +} + +float VisualScriptYield::get_wait_time(){ + + return wait_time; +} + + +void VisualScriptYield::_validate_property(PropertyInfo& property) const { + + + if (property.name=="wait_time") { + if (yield_mode!=YIELD_WAIT) { + property.usage=0; + } + } +} + +void VisualScriptYield::_bind_methods() { + + ClassDB::bind_method(_MD("set_yield_mode","mode"),&VisualScriptYield::set_yield_mode); + ClassDB::bind_method(_MD("get_yield_mode"),&VisualScriptYield::get_yield_mode); + + ClassDB::bind_method(_MD("set_wait_time","sec"),&VisualScriptYield::set_wait_time); + ClassDB::bind_method(_MD("get_wait_time"),&VisualScriptYield::get_wait_time); + + ADD_PROPERTY(PropertyInfo(Variant::INT,"mode",PROPERTY_HINT_ENUM,"Frame,FixedFrame,Time",PROPERTY_USAGE_NOEDITOR),_SCS("set_yield_mode"),_SCS("get_yield_mode")); + ADD_PROPERTY(PropertyInfo(Variant::REAL,"wait_time"),_SCS("set_wait_time"),_SCS("get_wait_time")); + + + BIND_CONSTANT( YIELD_FRAME ); + BIND_CONSTANT( YIELD_FIXED_FRAME ); + BIND_CONSTANT( YIELD_WAIT ); + +} + +VisualScriptYield::VisualScriptYield() { + + yield_mode=YIELD_FRAME; + wait_time=1; + +} + + +template<VisualScriptYield::YieldMode MODE> +static Ref<VisualScriptNode> create_yield_node(const String& p_name) { + + Ref<VisualScriptYield> node; + node.instance(); + node->set_yield_mode(MODE); + return node; +} + +/////////////////////////////////////////////////// +////////////////YIELD SIGNAL////////////////////// +////////////////////////////////////////////////// + +int VisualScriptYieldSignal::get_output_sequence_port_count() const { + + return 1; +} + +bool VisualScriptYieldSignal::has_input_sequence_port() const{ + + return true; +} +#ifdef TOOLS_ENABLED + +static Node* _find_script_node(Node* p_edited_scene,Node* p_current_node,const Ref<Script> &script) { + + if (p_edited_scene!=p_current_node && p_current_node->get_owner()!=p_edited_scene) + return NULL; + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr==script) + return p_current_node; + + for(int i=0;i<p_current_node->get_child_count();i++) { + Node *n = _find_script_node(p_edited_scene,p_current_node->get_child(i),script); + if (n) + return n; + } + + return NULL; +} + +#endif +Node *VisualScriptYieldSignal::_get_base_node() const { + +#ifdef TOOLS_ENABLED + Ref<Script> script = get_visual_script(); + if (!script.is_valid()) + return NULL; + + MainLoop * main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) + return NULL; + + SceneTree *scene_tree = main_loop->cast_to<SceneTree>(); + + if (!scene_tree) + return NULL; + + Node *edited_scene = scene_tree->get_edited_scene_root(); + + if (!edited_scene) + return NULL; + + Node* script_node = _find_script_node(edited_scene,edited_scene,script); + + if (!script_node) + return NULL; + + if (!script_node->has_node(base_path)) + return NULL; + + Node *path_to = script_node->get_node(base_path); + + return path_to; +#else + + return NULL; +#endif +} + +StringName VisualScriptYieldSignal::_get_base_type() const { + + if (call_mode==CALL_MODE_SELF && get_visual_script().is_valid()) + return get_visual_script()->get_instance_base_type(); + else if (call_mode==CALL_MODE_NODE_PATH && get_visual_script().is_valid()) { + Node *path = _get_base_node(); + if (path) + return path->get_class(); + + } + + return base_type; +} + +int VisualScriptYieldSignal::get_input_value_port_count() const{ + + if (call_mode==CALL_MODE_INSTANCE) + return 1; + else + return 0; + +} +int VisualScriptYieldSignal::get_output_value_port_count() const{ + + + MethodInfo sr; + + if (!ClassDB::get_signal(_get_base_type(),signal,&sr)) + return 0; + + return sr.arguments.size(); + +} + +String VisualScriptYieldSignal::get_output_sequence_port_text(int p_port) const { + + return String(); +} + +PropertyInfo VisualScriptYieldSignal::get_input_value_port_info(int p_idx) const{ + + if (call_mode==CALL_MODE_INSTANCE) + return PropertyInfo(Variant::OBJECT,"instance"); + else + return PropertyInfo(); + +} + +PropertyInfo VisualScriptYieldSignal::get_output_value_port_info(int p_idx) const{ + + MethodInfo sr; + + if (!ClassDB::get_signal(_get_base_type(),signal,&sr)) + return PropertyInfo(); //no signal + ERR_FAIL_INDEX_V(p_idx,sr.arguments.size(),PropertyInfo()); + return sr.arguments[p_idx]; + +} + + +String VisualScriptYieldSignal::get_caption() const { + + static const char*cname[3]= { + "WaitSignal", + "WaitNodeSignal", + "WaitInstanceSigna;", + }; + + return cname[call_mode]; +} + +String VisualScriptYieldSignal::get_text() const { + + if (call_mode==CALL_MODE_SELF) + return " "+String(signal)+"()"; + else + return " "+_get_base_type()+"."+String(signal)+"()"; + +} + + +void VisualScriptYieldSignal::set_base_type(const StringName& p_type) { + + if (base_type==p_type) + return; + + base_type=p_type; + + _change_notify(); + ports_changed_notify(); +} + +StringName VisualScriptYieldSignal::get_base_type() const{ + + return base_type; +} + +void VisualScriptYieldSignal::set_signal(const StringName& p_type){ + + if (signal==p_type) + return; + + signal=p_type; + + _change_notify(); + ports_changed_notify(); +} +StringName VisualScriptYieldSignal::get_signal() const { + + + return signal; +} + +void VisualScriptYieldSignal::set_base_path(const NodePath& p_type) { + + if (base_path==p_type) + return; + + base_path=p_type; + + _change_notify(); + ports_changed_notify(); +} + +NodePath VisualScriptYieldSignal::get_base_path() const { + + return base_path; +} + + +void VisualScriptYieldSignal::set_call_mode(CallMode p_mode) { + + if (call_mode==p_mode) + return; + + call_mode=p_mode; + + _change_notify(); + ports_changed_notify(); + +} + +VisualScriptYieldSignal::CallMode VisualScriptYieldSignal::get_call_mode() const { + + return call_mode; +} + + +void VisualScriptYieldSignal::_validate_property(PropertyInfo& property) const { + + if (property.name=="signal/base_type") { + if (call_mode!=CALL_MODE_INSTANCE) { + property.usage=PROPERTY_USAGE_NOEDITOR; + } + } + + + if (property.name=="signal/node_path") { + if (call_mode!=CALL_MODE_NODE_PATH) { + property.usage=0; + } else { + + Node *bnode = _get_base_node(); + if (bnode) { + property.hint_string=bnode->get_path(); //convert to loong string + } else { + + } + } + } + + if (property.name=="signal/signal") { + property.hint=PROPERTY_HINT_ENUM; + + + List<MethodInfo> methods; + + ClassDB::get_signal_list(_get_base_type(),&methods); + + List<String> mstring; + for (List<MethodInfo>::Element *E=methods.front();E;E=E->next()) { + if (E->get().name.begins_with("_")) + continue; + mstring.push_back(E->get().name.get_slice(":",0)); + } + + mstring.sort(); + + String ml; + for (List<String>::Element *E=mstring.front();E;E=E->next()) { + + if (ml!=String()) + ml+=","; + ml+=E->get(); + } + + property.hint_string=ml; + } + + +} + + +void VisualScriptYieldSignal::_bind_methods() { + + ClassDB::bind_method(_MD("set_base_type","base_type"),&VisualScriptYieldSignal::set_base_type); + ClassDB::bind_method(_MD("get_base_type"),&VisualScriptYieldSignal::get_base_type); + + ClassDB::bind_method(_MD("set_signal","signal"),&VisualScriptYieldSignal::set_signal); + ClassDB::bind_method(_MD("get_signal"),&VisualScriptYieldSignal::get_signal); + + ClassDB::bind_method(_MD("set_call_mode","mode"),&VisualScriptYieldSignal::set_call_mode); + ClassDB::bind_method(_MD("get_call_mode"),&VisualScriptYieldSignal::get_call_mode); + + ClassDB::bind_method(_MD("set_base_path","base_path"),&VisualScriptYieldSignal::set_base_path); + ClassDB::bind_method(_MD("get_base_path"),&VisualScriptYieldSignal::get_base_path); + + + + String bt; + for(int i=0;i<Variant::VARIANT_MAX;i++) { + if (i>0) + bt+=","; + + bt+=Variant::get_type_name(Variant::Type(i)); + } + + ADD_PROPERTY(PropertyInfo(Variant::INT,"signal/call_mode",PROPERTY_HINT_ENUM,"Self,Node Path,Instance"),_SCS("set_call_mode"),_SCS("get_call_mode")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"signal/base_type",PROPERTY_HINT_TYPE_STRING,"Object"),_SCS("set_base_type"),_SCS("get_base_type")); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH,"signal/node_path",PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE),_SCS("set_base_path"),_SCS("get_base_path")); + ADD_PROPERTY(PropertyInfo(Variant::STRING,"signal/signal"),_SCS("set_signal"),_SCS("get_signal")); + + + BIND_CONSTANT( CALL_MODE_SELF ); + BIND_CONSTANT( CALL_MODE_NODE_PATH); + BIND_CONSTANT( CALL_MODE_INSTANCE); + +} + +class VisualScriptNodeInstanceYieldSignal : public VisualScriptNodeInstance { +public: + + + VisualScriptYieldSignal::CallMode call_mode; + NodePath node_path; + int output_args; + StringName signal; + + VisualScriptYieldSignal *node; + VisualScriptInstance *instance; + + + + virtual int get_working_memory_size() const { return 1; } + //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } + //virtual bool get_output_port_unsequenced(int p_idx,Variant* r_value,Variant* p_working_mem,String &r_error) const { return true; } + + virtual int step(const Variant** p_inputs,Variant** p_outputs,StartMode p_start_mode,Variant* p_working_mem,Variant::CallError& r_error,String& r_error_str) { + + if (p_start_mode==START_MODE_RESUME_YIELD) { + return 0; //resuming yield + } else { + //yield + + Object * object; + + switch(call_mode) { + + case VisualScriptYieldSignal::CALL_MODE_SELF: { + + object=instance->get_owner_ptr(); + + } break; + case VisualScriptYieldSignal::CALL_MODE_NODE_PATH: { + + Node* node = instance->get_owner_ptr()->cast_to<Node>(); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Base object is not a Node!"; + return 0; + } + + Node* another = node->get_node(node_path); + if (!node) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Path does not lead Node!"; + return 0; + } + + object=another; + + } break; + case VisualScriptYieldSignal::CALL_MODE_INSTANCE: { + + object = *p_inputs[0]; + if (!object) { + r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error_str="Supplied instance input is null."; + return 0; + + } + + } break; + + } + + Ref<VisualScriptFunctionState> state; + state.instance(); + + state->connect_to_signal(object,signal,Array()); + + *p_working_mem=state; + + return STEP_YIELD_BIT; + } + + + } + + +}; + +VisualScriptNodeInstance* VisualScriptYieldSignal::instance(VisualScriptInstance* p_instance) { + + VisualScriptNodeInstanceYieldSignal * instance = memnew(VisualScriptNodeInstanceYieldSignal ); + instance->node=this; + instance->instance=p_instance; + instance->signal=signal; + instance->call_mode=call_mode; + instance->node_path=base_path; + instance->output_args = get_output_value_port_count(); + return instance; +} +VisualScriptYieldSignal::VisualScriptYieldSignal() { + + call_mode=CALL_MODE_SELF; + base_type="Object"; + +} + +template<VisualScriptYieldSignal::CallMode cmode> +static Ref<VisualScriptNode> create_yield_signal_node(const String& p_name) { + + Ref<VisualScriptYieldSignal> node; + node.instance(); + node->set_call_mode(cmode); + return node; +} + +void register_visual_script_yield_nodes() { + + VisualScriptLanguage::singleton->add_register_func("functions/wait/wait_frame",create_yield_node<VisualScriptYield::YIELD_FRAME>); + VisualScriptLanguage::singleton->add_register_func("functions/wait/wait_fixed_frame",create_yield_node<VisualScriptYield::YIELD_FIXED_FRAME>); + VisualScriptLanguage::singleton->add_register_func("functions/wait/wait_time",create_yield_node<VisualScriptYield::YIELD_WAIT>); + + + VisualScriptLanguage::singleton->add_register_func("functions/yield",create_yield_node<VisualScriptYield::YIELD_RETURN>); + VisualScriptLanguage::singleton->add_register_func("functions/yield_signal",create_node_generic<VisualScriptYieldSignal>); + +} diff --git a/modules/visual_script/visual_script_yield_nodes.h b/modules/visual_script/visual_script_yield_nodes.h new file mode 100644 index 000000000..210d6ec99 --- /dev/null +++ b/modules/visual_script/visual_script_yield_nodes.h @@ -0,0 +1,128 @@ +#ifndef VISUAL_SCRIPT_YIELD_NODES_H +#define VISUAL_SCRIPT_YIELD_NODES_H + +#include "visual_script.h" + +class VisualScriptYield : public VisualScriptNode { + + GDCLASS(VisualScriptYield,VisualScriptNode) +public: + + enum YieldMode { + YIELD_RETURN, + YIELD_FRAME, + YIELD_FIXED_FRAME, + YIELD_WAIT + + }; +private: + + YieldMode yield_mode; + float wait_time; + + +protected: + + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_yield_mode(YieldMode p_mode); + YieldMode get_yield_mode(); + + void set_wait_time(float p_time); + float get_wait_time(); + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptYield(); +}; +VARIANT_ENUM_CAST( VisualScriptYield::YieldMode ) + +class VisualScriptYieldSignal : public VisualScriptNode { + + GDCLASS(VisualScriptYieldSignal,VisualScriptNode) +public: + enum CallMode { + CALL_MODE_SELF, + CALL_MODE_NODE_PATH, + CALL_MODE_INSTANCE, + + }; +private: + + CallMode call_mode; + StringName base_type; + NodePath base_path; + StringName signal; + + Node *_get_base_node() const; + StringName _get_base_type() const; + + +protected: + virtual void _validate_property(PropertyInfo& property) const; + + static void _bind_methods(); + +public: + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + + virtual String get_output_sequence_port_text(int p_port) const; + + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + void set_base_type(const StringName& p_type); + StringName get_base_type() const; + + void set_signal(const StringName& p_type); + StringName get_signal() const; + + void set_base_path(const NodePath& p_type); + NodePath get_base_path() const; + + void set_call_mode(CallMode p_mode); + CallMode get_call_mode() const; + + virtual VisualScriptNodeInstance* instance(VisualScriptInstance* p_instance); + + VisualScriptYieldSignal(); +}; + +VARIANT_ENUM_CAST(VisualScriptYieldSignal::CallMode ); + +void register_visual_script_yield_nodes(); + +#endif // VISUAL_SCRIPT_YIELD_NODES_H diff --git a/modules/vorbis/SCsub b/modules/vorbis/SCsub new file mode 100644 index 000000000..d3e4f7e15 --- /dev/null +++ b/modules/vorbis/SCsub @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_vorbis = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_libvorbis'] != 'no'): + thirdparty_dir = "#thirdparty/libvorbis/" + thirdparty_sources = [ + #"analysis.c", + #"barkmel.c", + "bitrate.c", + "block.c", + "codebook.c", + "envelope.c", + "floor0.c", + "floor1.c", + "info.c", + "lookup.c", + "lpc.c", + "lsp.c", + "mapping0.c", + "mdct.c", + "psy.c", + #"psytune.c", + "registry.c", + "res0.c", + "sharedbook.c", + "smallft.c", + "synthesis.c", + #"tone.c", + #"vorbisenc.c", + "vorbisfile.c", + "window.c", + ] + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_vorbis.add_source_files(env.modules_sources, thirdparty_sources) + env_vorbis.Append(CPPPATH=[thirdparty_dir]) + + # also requires libogg + if (env['builtin_libogg'] != 'no'): + env_vorbis.Append(CPPPATH=["#thirdparty/libogg"]) + +# Godot source files +env_vorbis.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp new file mode 100644 index 000000000..2b05daca1 --- /dev/null +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -0,0 +1,430 @@ +/*************************************************************************/ +/* audio_stream_ogg_vorbis.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "audio_stream_ogg_vorbis.h" + + + +size_t AudioStreamPlaybackOGGVorbis::_ov_read_func(void *p_dst,size_t p_data, size_t p_count, void *_f) { + + //printf("read to %p, %i bytes, %i nmemb, %p\n",p_dst,p_data,p_count,_f); + FileAccess *fa=(FileAccess*)_f; + size_t read_total = p_data*p_count; + + if (fa->eof_reached()) + return 0; + + uint8_t *dst=(uint8_t*)p_dst; + + int read = fa->get_buffer(dst, read_total); + + return read; +} + +int AudioStreamPlaybackOGGVorbis::_ov_seek_func(void *_f,ogg_int64_t offs, int whence) { + + //printf("seek to %p, offs %i, whence %i\n",_f,(int)offs,whence); + +#ifdef SEEK_SET + //printf("seek set defined\n"); + FileAccess *fa=(FileAccess*)_f; + + if (whence==SEEK_SET) { + + fa->seek(offs); + } else if (whence==SEEK_CUR) { + + fa->seek(fa->get_pos()+offs); + } else if (whence==SEEK_END) { + + fa->seek_end(offs); + } else { + + ERR_PRINT("BUG, wtf was whence set to?\n"); + } + int ret=fa->eof_reached()?-1:0; + //printf("returning %i\n",ret); + return ret; + +#else + return -1; // no seeking +#endif + +} +int AudioStreamPlaybackOGGVorbis::_ov_close_func(void *_f) { + + //printf("close %p\n",_f); + if (!_f) + return 0; + FileAccess *fa=(FileAccess*)_f; + if (fa->is_open()) + fa->close(); + return 0; +} +long AudioStreamPlaybackOGGVorbis::_ov_tell_func(void *_f) { + + //printf("close %p\n",_f); + + FileAccess *fa=(FileAccess*)_f; + return fa->get_pos(); +} + + + +int AudioStreamPlaybackOGGVorbis::mix(int16_t* p_bufer,int p_frames) { + + if (!playing) + return 0; + + int total=p_frames; + while (true) { + + int todo = p_frames; + + if (todo==0 || todo<MIN_MIX) { + break; + } + + //printf("to mix %i - mix me %i bytes\n",to_mix,to_mix*stream_channels*sizeof(int16_t)); + + #ifdef BIG_ENDIAN_ENABLED + long ret=ov_read(&vf,(char*)p_bufer,todo*stream_channels*sizeof(int16_t), 1, 2, 1, ¤t_section); + #else + long ret=ov_read(&vf,(char*)p_bufer,todo*stream_channels*sizeof(int16_t), 0, 2, 1, ¤t_section); + #endif + + if (ret<0) { + + playing = false; + ERR_EXPLAIN("Error reading OGG Vorbis File: "+file); + ERR_BREAK(ret<0); + } else if (ret==0) { // end of song, reload? + + ov_clear(&vf); + + _close_file(); + + if (!has_loop()) { + + playing=false; + repeats=1; + break; + } + + f=FileAccess::open(file,FileAccess::READ); + + int errv = ov_open_callbacks(f,&vf,NULL,0,_ov_callbacks); + if (errv!=0) { + playing=false; + break;; // :( + } + + if (loop_restart_time) { + bool ok = ov_time_seek(&vf,loop_restart_time)==0; + if (!ok) { + playing=false; + //ERR_EXPLAIN("loop restart time rejected"); + ERR_PRINT("loop restart time rejected") + } + + frames_mixed=stream_srate*loop_restart_time; + } else { + + frames_mixed=0; + } + repeats++; + continue; + + } + + ret/=stream_channels; + ret/=sizeof(int16_t); + + frames_mixed+=ret; + + p_bufer+=ret*stream_channels; + p_frames-=ret; + + } + + return total-p_frames; + +} + + + +void AudioStreamPlaybackOGGVorbis::play(float p_from) { + + if (playing) + stop(); + + if (_load_stream()!=OK) + return; + + + frames_mixed=0; + playing=true; + if (p_from>0) { + seek_pos(p_from); + } +} + +void AudioStreamPlaybackOGGVorbis::_close_file() { + + if (f) { + + memdelete(f); + f=NULL; + } +} + +bool AudioStreamPlaybackOGGVorbis::is_playing() const { + return playing; +} +void AudioStreamPlaybackOGGVorbis::stop() { + + _clear_stream(); + playing=false; + //_clear(); +} + + + +float AudioStreamPlaybackOGGVorbis::get_pos() const { + + int32_t frames = int32_t(frames_mixed); + if (frames < 0) + frames=0; + return double(frames) / stream_srate; +} + +void AudioStreamPlaybackOGGVorbis::seek_pos(float p_time) { + + + + if (!playing) + return; + bool ok = ov_time_seek(&vf,p_time)==0; + ERR_FAIL_COND(!ok); + frames_mixed=stream_srate*p_time; +} + +String AudioStreamPlaybackOGGVorbis::get_stream_name() const { + + return ""; +} + +void AudioStreamPlaybackOGGVorbis::set_loop(bool p_enable) { + + loops=p_enable; +} + +bool AudioStreamPlaybackOGGVorbis::has_loop() const { + + return loops; +} + +int AudioStreamPlaybackOGGVorbis::get_loop_count() const { + return repeats; +} + + +Error AudioStreamPlaybackOGGVorbis::set_file(const String& p_file) { + + file=p_file; + stream_valid=false; + Error err; + f=FileAccess::open(file,FileAccess::READ,&err); + + if (err) { + ERR_FAIL_COND_V( err, err ); + } + + int errv = ov_open_callbacks(f,&vf,NULL,0,_ov_callbacks); + switch(errv) { + + case OV_EREAD: { // - A read from media returned an error. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CANT_READ ); + } break; + case OV_EVERSION: // - Vorbis version mismatch. + case OV_ENOTVORBIS: { // - Bitstream is not Vorbis data. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_UNRECOGNIZED ); + } break; + case OV_EBADHEADER: { // - Invalid Vorbis bitstream header. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CORRUPT ); + } break; + case OV_EFAULT: { // - Internal logic fault; indicates a bug or heap/stack corruption. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_BUG ); + } break; + } + const vorbis_info *vinfo=ov_info(&vf,-1); + stream_channels=vinfo->channels; + stream_srate=vinfo->rate; + length = ov_time_total(&vf,-1); + ov_clear(&vf); + memdelete(f); + f=NULL; + stream_valid=true; + + + return OK; +} + +Error AudioStreamPlaybackOGGVorbis::_load_stream() { + + ERR_FAIL_COND_V(!stream_valid,ERR_UNCONFIGURED); + + _clear_stream(); + if (file=="") + return ERR_INVALID_DATA; + + Error err; + f=FileAccess::open(file,FileAccess::READ,&err); + if (err) { + ERR_FAIL_COND_V( err, err ); + } + + int errv = ov_open_callbacks(f,&vf,NULL,0,_ov_callbacks); + switch(errv) { + + case OV_EREAD: { // - A read from media returned an error. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CANT_READ ); + } break; + case OV_EVERSION: // - Vorbis version mismatch. + case OV_ENOTVORBIS: { // - Bitstream is not Vorbis data. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_UNRECOGNIZED ); + } break; + case OV_EBADHEADER: { // - Invalid Vorbis bitstream header. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_FILE_CORRUPT ); + } break; + case OV_EFAULT: { // - Internal logic fault; indicates a bug or heap/stack corruption. + memdelete(f); f=NULL; + ERR_FAIL_V( ERR_BUG ); + } break; + } + repeats=0; + stream_loaded=true; + + + return OK; +} + + +float AudioStreamPlaybackOGGVorbis::get_length() const { + + if (!stream_loaded) { + if (const_cast<AudioStreamPlaybackOGGVorbis*>(this)->_load_stream()!=OK) + return 0; + } + return length; +} + +void AudioStreamPlaybackOGGVorbis::_clear_stream() { + + if (!stream_loaded) + return; + + ov_clear(&vf); + _close_file(); + + stream_loaded=false; + //stream_channels=1; + playing=false; +} + +void AudioStreamPlaybackOGGVorbis::set_paused(bool p_paused) { + + paused=p_paused; +} + +bool AudioStreamPlaybackOGGVorbis::is_paused(bool p_paused) const { + + return paused; +} + + +AudioStreamPlaybackOGGVorbis::AudioStreamPlaybackOGGVorbis() { + + loops=false; + playing=false; + _ov_callbacks.read_func=_ov_read_func; + _ov_callbacks.seek_func=_ov_seek_func; + _ov_callbacks.close_func=_ov_close_func; + _ov_callbacks.tell_func=_ov_tell_func; + f = NULL; + stream_loaded=false; + stream_valid=false; + repeats=0; + paused=true; + stream_channels=0; + stream_srate=0; + current_section=0; + length=0; + loop_restart_time=0; +} + + +AudioStreamPlaybackOGGVorbis::~AudioStreamPlaybackOGGVorbis() { + + _clear_stream(); + +} + + + +RES ResourceFormatLoaderAudioStreamOGGVorbis::load(const String &p_path, const String& p_original_path, Error *r_error) { + if (r_error) + *r_error=OK; + + AudioStreamOGGVorbis *ogg_stream = memnew(AudioStreamOGGVorbis); + ogg_stream->set_file(p_path); + return Ref<AudioStreamOGGVorbis>(ogg_stream); +} + +void ResourceFormatLoaderAudioStreamOGGVorbis::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("ogg"); +} +String ResourceFormatLoaderAudioStreamOGGVorbis::get_resource_type(const String &p_path) const { + + if (p_path.get_extension().to_lower()=="ogg") + return "AudioStreamOGGVorbis"; + return ""; +} + +bool ResourceFormatLoaderAudioStreamOGGVorbis::handles_type(const String& p_type) const { + return (p_type=="AudioStream" || p_type=="AudioStreamOGG" || p_type=="AudioStreamOGGVorbis"); +} + diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h new file mode 100644 index 000000000..bc2bfa2ae --- /dev/null +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -0,0 +1,142 @@ +/*************************************************************************/ +/* audio_stream_ogg_vorbis.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef AUDIO_STREAM_OGG_VORBIS_H +#define AUDIO_STREAM_OGG_VORBIS_H + +#include "io/resource_loader.h" +#include "os/file_access.h" +#include "os/thread_safe.h" +#include "scene/resources/audio_stream.h" + +#include <vorbis/vorbisfile.h> + +class AudioStreamPlaybackOGGVorbis : public AudioStreamPlayback { + + GDCLASS(AudioStreamPlaybackOGGVorbis,AudioStreamPlayback); + + enum { + MIN_MIX=1024 + }; + + FileAccess *f; + + ov_callbacks _ov_callbacks; + float length; + static size_t _ov_read_func(void *p_dst,size_t p_data, size_t p_count, void *_f); + static int _ov_seek_func(void *_f,ogg_int64_t offs, int whence); + static int _ov_close_func(void *_f); + static long _ov_tell_func(void *_f); + + String file; + int64_t frames_mixed; + + bool stream_loaded; + volatile bool playing; + OggVorbis_File vf; + int stream_channels; + int stream_srate; + int current_section; + + + bool paused; + bool loops; + int repeats; + + Error _load_stream(); + void _clear_stream(); + void _close_file(); + + bool stream_valid; + float loop_restart_time; + + +public: + + + Error set_file(const String& p_file); + + virtual void play(float p_from=0); + virtual void stop(); + virtual bool is_playing() const; + + virtual void set_loop_restart_time(float p_time) { loop_restart_time=p_time; } + + virtual void set_paused(bool p_paused); + virtual bool is_paused(bool p_paused) const; + + virtual void set_loop(bool p_enable); + virtual bool has_loop() const; + + virtual float get_length() const; + + virtual String get_stream_name() const; + + virtual int get_loop_count() const; + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + virtual int get_channels() const { return stream_channels; } + virtual int get_mix_rate() const { return stream_srate; } + + virtual int get_minimum_buffer_size() const { return 0; } + virtual int mix(int16_t* p_bufer,int p_frames); + + AudioStreamPlaybackOGGVorbis(); + ~AudioStreamPlaybackOGGVorbis(); +}; + + +class AudioStreamOGGVorbis : public AudioStream { + + GDCLASS(AudioStreamOGGVorbis,AudioStream); + + String file; +public: + + Ref<AudioStreamPlayback> instance_playback() { + Ref<AudioStreamPlaybackOGGVorbis> pb = memnew( AudioStreamPlaybackOGGVorbis ); + pb->set_file(file); + return pb; + } + + void set_file(const String& p_file) { file=p_file; } + +}; + +class ResourceFormatLoaderAudioStreamOGGVorbis : public ResourceFormatLoader { +public: + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + + +#endif // AUDIO_STREAM_OGG_H diff --git a/modules/vorbis/config.py b/modules/vorbis/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/vorbis/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp new file mode 100644 index 000000000..de929d935 --- /dev/null +++ b/modules/vorbis/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "audio_stream_ogg_vorbis.h" + +static ResourceFormatLoaderAudioStreamOGGVorbis *vorbis_stream_loader = NULL; + +void register_vorbis_types() { + + vorbis_stream_loader = memnew( ResourceFormatLoaderAudioStreamOGGVorbis ); + ResourceLoader::add_resource_format_loader(vorbis_stream_loader); + ClassDB::register_class<AudioStreamOGGVorbis>(); +} + +void unregister_vorbis_types() { + + memdelete( vorbis_stream_loader ); +} diff --git a/modules/vorbis/register_types.h b/modules/vorbis/register_types.h new file mode 100644 index 000000000..b2adb55ac --- /dev/null +++ b/modules/vorbis/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_vorbis_types(); +void unregister_vorbis_types(); diff --git a/modules/webm/SCsub b/modules/webm/SCsub new file mode 100644 index 000000000..889f5e83a --- /dev/null +++ b/modules/webm/SCsub @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_webm = env_modules.Clone() + +# Thirdparty source files +thirdparty_libsimplewebm_dir = "#thirdparty/libsimplewebm/" +thirdparty_libsimplewebm_sources = [ + "libwebm/mkvparser/mkvparser.cc", + "OpusVorbisDecoder.cpp", + "VPXDecoder.cpp", + "WebMDemuxer.cpp", +] +thirdparty_libsimplewebm_sources = [thirdparty_libsimplewebm_dir + file for file in thirdparty_libsimplewebm_sources] + +env_webm.add_source_files(env.modules_sources, thirdparty_libsimplewebm_sources) +env_webm.Append(CPPPATH=[thirdparty_libsimplewebm_dir, thirdparty_libsimplewebm_dir + "libwebm/"]) + +# also requires libogg, libvorbis and libopus +if (env['builtin_libogg'] != 'no'): + env_webm.Append(CPPPATH=["#thirdparty/libogg"]) +if (env['builtin_libvorbis'] != 'no'): + env_webm.Append(CPPPATH=["#thirdparty/libvorbis"]) +if (env['builtin_opus'] != 'no'): + env_webm.Append(CPPPATH=["#thirdparty/opus"]) + +if (env['builtin_libvpx'] != 'no'): + Export('env_webm') + SConscript("libvpx/SCsub") + +# Godot source files +env_webm.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/webm/config.py b/modules/webm/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/webm/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub new file mode 100644 index 000000000..241d6e30c --- /dev/null +++ b/modules/webm/libvpx/SCsub @@ -0,0 +1,393 @@ +#!/usr/bin/env python + +libvpx_dir = "#thirdparty/libvpx/" + +libvpx_sources = [ + "vp8/vp8_dx_iface.c", + + "vp8/common/generic/systemdependent.c", + + "vp8/common/alloccommon.c", + "vp8/common/blockd.c", + "vp8/common/copy_c.c", + "vp8/common/debugmodes.c", + "vp8/common/dequantize.c", + "vp8/common/entropy.c", + "vp8/common/entropymode.c", + "vp8/common/entropymv.c", + "vp8/common/extend.c", + "vp8/common/filter.c", + "vp8/common/findnearmv.c", + "vp8/common/idct_blk.c", + "vp8/common/idctllm.c", + "vp8/common/loopfilter_filters.c", + "vp8/common/mbpitch.c", + "vp8/common/modecont.c", + "vp8/common/quant_common.c", + "vp8/common/reconinter.c", + "vp8/common/reconintra.c", + "vp8/common/reconintra4x4.c", + "vp8/common/rtcd.c", + "vp8/common/setupintrarecon.c", + "vp8/common/swapyv12buffer.c", + "vp8/common/treecoder.c", + "vp8/common/vp8_loopfilter.c", + + "vp8/decoder/dboolhuff.c", + "vp8/decoder/decodeframe.c", + "vp8/decoder/decodemv.c", + "vp8/decoder/detokenize.c", + "vp8/decoder/onyxd_if.c", + "vp8/decoder/threading.c", + + + "vp9/vp9_dx_iface.c", + + "vp9/common/vp9_alloccommon.c", + "vp9/common/vp9_blockd.c", + "vp9/common/vp9_common_data.c", + "vp9/common/vp9_debugmodes.c", + "vp9/common/vp9_entropy.c", + "vp9/common/vp9_entropymode.c", + "vp9/common/vp9_entropymv.c", + "vp9/common/vp9_filter.c", + "vp9/common/vp9_frame_buffers.c", + "vp9/common/vp9_idct.c", + "vp9/common/vp9_loopfilter.c", + "vp9/common/vp9_mvref_common.c", + "vp9/common/vp9_pred_common.c", + "vp9/common/vp9_quant_common.c", + "vp9/common/vp9_reconinter.c", + "vp9/common/vp9_reconintra.c", + "vp9/common/vp9_rtcd.c", + "vp9/common/vp9_scale.c", + "vp9/common/vp9_scan.c", + "vp9/common/vp9_seg_common.c", + "vp9/common/vp9_thread_common.c", + "vp9/common/vp9_tile_common.c", + + "vp9/decoder/vp9_decodeframe.c", + "vp9/decoder/vp9_decodemv.c", + "vp9/decoder/vp9_decoder.c", + "vp9/decoder/vp9_detokenize.c", + "vp9/decoder/vp9_dsubexp.c", + "vp9/decoder/vp9_dthread.c", + + + "vpx/src/vpx_codec.c", + "vpx/src/vpx_decoder.c", + "vpx/src/vpx_image.c", + "vpx/src/vpx_psnr.c", + + + "vpx_dsp/bitreader.c", + "vpx_dsp/bitreader_buffer.c", + "vpx_dsp/intrapred.c", + "vpx_dsp/inv_txfm.c", + "vpx_dsp/loopfilter.c", + "vpx_dsp/prob.c", + "vpx_dsp/vpx_convolve.c", + "vpx_dsp/vpx_dsp_rtcd.c", + + + "vpx_mem/vpx_mem.c", + + + "vpx_scale/vpx_scale_rtcd.c", + + "vpx_scale/generic/yv12config.c", + "vpx_scale/generic/yv12extend.c", + + + "vpx_util/vpx_thread.c" +] + +libvpx_sources_intrin_x86 = [ + "vp8/common/x86/filter_x86.c", + "vp8/common/x86/loopfilter_x86.c", + "vp8/common/x86/vp8_asm_stubs.c", + + + "vpx_dsp/x86/vpx_asm_stubs.c" +] +libvpx_sources_intrin_x86_mmx = [ + "vp8/common/x86/idct_blk_mmx.c", +] +libvpx_sources_intrin_x86_sse2 = [ + "vp8/common/x86/idct_blk_sse2.c", + + + "vp9/common/x86/vp9_idct_intrin_sse2.c", + + + "vpx_dsp/x86/inv_txfm_sse2.c", + "vpx_dsp/x86/loopfilter_sse2.c", +] +libvpx_sources_intrin_x86_ssse3 = [ + "vpx_dsp/x86/vpx_subpixel_8t_intrin_ssse3.c" +] +libvpx_sources_intrin_x86_avx2 = [ + "vpx_dsp/x86/loopfilter_avx2.c", + "vpx_dsp/x86/vpx_subpixel_8t_intrin_avx2.c" +] +libvpx_sources_x86asm = [ + "vp8/common/x86/copy_sse2.asm", + "vp8/common/x86/copy_sse3.asm", + "vp8/common/x86/dequantize_mmx.asm", + "vp8/common/x86/idctllm_mmx.asm", + "vp8/common/x86/idctllm_sse2.asm", + "vp8/common/x86/iwalsh_mmx.asm", + "vp8/common/x86/iwalsh_sse2.asm", + "vp8/common/x86/loopfilter_sse2.asm", + "vp8/common/x86/recon_mmx.asm", + "vp8/common/x86/recon_sse2.asm", + "vp8/common/x86/subpixel_mmx.asm", + "vp8/common/x86/subpixel_sse2.asm", + "vp8/common/x86/subpixel_ssse3.asm", + "vp8/common/x86/vp8_loopfilter_mmx.asm", + + + "vpx_dsp/x86/intrapred_sse2.asm", + "vpx_dsp/x86/intrapred_ssse3.asm", + "vpx_dsp/x86/inv_wht_sse2.asm", + "vpx_dsp/x86/vpx_convolve_copy_sse2.asm", + "vpx_dsp/x86/vpx_subpixel_8t_sse2.asm", + "vpx_dsp/x86/vpx_subpixel_8t_ssse3.asm", + "vpx_dsp/x86/vpx_subpixel_bilinear_sse2.asm", + "vpx_dsp/x86/vpx_subpixel_bilinear_ssse3.asm", + + + "vpx_ports/emms.asm" +] +libvpx_sources_x86_64asm = [ + "vp8/common/x86/loopfilter_block_sse2_x86_64.asm", + + + "vpx_dsp/x86/inv_txfm_ssse3_x86_64.asm" +] + +libvpx_sources_arm = [ + "vpx_ports/arm_cpudetect.c", + + + "vp8/common/arm/loopfilter_arm.c", +] +libvpx_sources_arm_neon = [ + "vp8/common/arm/neon/bilinearpredict_neon.c", + "vp8/common/arm/neon/copymem_neon.c", + "vp8/common/arm/neon/dc_only_idct_add_neon.c", + "vp8/common/arm/neon/dequant_idct_neon.c", + "vp8/common/arm/neon/dequantizeb_neon.c", + "vp8/common/arm/neon/idct_blk_neon.c", + "vp8/common/arm/neon/idct_dequant_0_2x_neon.c", + "vp8/common/arm/neon/idct_dequant_full_2x_neon.c", + "vp8/common/arm/neon/iwalsh_neon.c", + "vp8/common/arm/neon/loopfiltersimplehorizontaledge_neon.c", + "vp8/common/arm/neon/loopfiltersimpleverticaledge_neon.c", + "vp8/common/arm/neon/mbloopfilter_neon.c", + "vp8/common/arm/neon/shortidct4x4llm_neon.c", + "vp8/common/arm/neon/sixtappredict_neon.c", + "vp8/common/arm/neon/vp8_loopfilter_neon.c", + + + "vp9/common/arm/neon/vp9_iht4x4_add_neon.c", + "vp9/common/arm/neon/vp9_iht8x8_add_neon.c", + + + "vpx_dsp/arm/idct16x16_1_add_neon.c", + "vpx_dsp/arm/idct16x16_add_neon.c", + "vpx_dsp/arm/idct16x16_neon.c", + "vpx_dsp/arm/idct32x32_1_add_neon.c", + "vpx_dsp/arm/idct32x32_add_neon.c", + "vpx_dsp/arm/idct4x4_1_add_neon.c", + "vpx_dsp/arm/idct4x4_add_neon.c", + "vpx_dsp/arm/idct8x8_1_add_neon.c", + "vpx_dsp/arm/idct8x8_add_neon.c", + "vpx_dsp/arm/intrapred_neon.c", + "vpx_dsp/arm/loopfilter_16_neon.c", + "vpx_dsp/arm/loopfilter_4_neon.c", + "vpx_dsp/arm/loopfilter_8_neon.c", + "vpx_dsp/arm/loopfilter_neon.c", + "vpx_dsp/arm/vpx_convolve8_avg_neon.c", + "vpx_dsp/arm/vpx_convolve8_neon.c", + "vpx_dsp/arm/vpx_convolve_avg_neon.c", + "vpx_dsp/arm/vpx_convolve_copy_neon.c", + "vpx_dsp/arm/vpx_convolve_neon.c" +] +libvpx_sources_arm_neon_gas = [ + "vpx_dsp/arm/gas/intrapred_neon_asm.s", + "vpx_dsp/arm/gas/loopfilter_mb_neon.s", + "vpx_dsp/arm/gas/save_reg_neon.s" +] +libvpx_sources_arm_neon_armasm_ms = [ + "vpx_dsp/arm/armasm_ms/intrapred_neon_asm.asm", + "vpx_dsp/arm/armasm_ms/loopfilter_mb_neon.asm", + "vpx_dsp/arm/armasm_ms/save_reg_neon.asm" +] +libvpx_sources_arm_neon_gas_apple = [ + "vpx_dsp/arm/gas_apple/intrapred_neon_asm.s", + "vpx_dsp/arm/gas_apple/loopfilter_mb_neon.s", + "vpx_dsp/arm/gas_apple/save_reg_neon.s" +] + +libvpx_sources = [libvpx_dir + file for file in libvpx_sources] +libvpx_sources_intrin_x86 = [libvpx_dir + file for file in libvpx_sources_intrin_x86] +libvpx_sources_intrin_x86_mmx = [libvpx_dir + file for file in libvpx_sources_intrin_x86_mmx] +libvpx_sources_intrin_x86_sse2 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_sse2] +libvpx_sources_intrin_x86_ssse3 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_ssse3] +libvpx_sources_intrin_x86_avx2 = [libvpx_dir + file for file in libvpx_sources_intrin_x86_avx2] +libvpx_sources_x86asm = [libvpx_dir + file for file in libvpx_sources_x86asm] +libvpx_sources_x86_64asm = [libvpx_dir + file for file in libvpx_sources_x86_64asm] +libvpx_sources_arm = [libvpx_dir + file for file in libvpx_sources_arm] +libvpx_sources_arm_neon = [libvpx_dir + file for file in libvpx_sources_arm_neon] +libvpx_sources_arm_neon_gas = [libvpx_dir + file for file in libvpx_sources_arm_neon_gas] +libvpx_sources_arm_neon_armasm_ms = [libvpx_dir + file for file in libvpx_sources_arm_neon_armasm_ms] +libvpx_sources_arm_neon_gas_apple = [libvpx_dir + file for file in libvpx_sources_arm_neon_gas_apple] + + +Import('env') +Import('env_webm') + +env_webm.Append(CPPPATH=[libvpx_dir]) + +env_libvpx = env.Clone() +env_libvpx.Append(CPPPATH=[libvpx_dir]) + +cpu_bits = env["bits"] +osx_fat = (env["platform"] == 'osx' and cpu_bits == 'fat') +webm_cpu_x86 = False +webm_cpu_arm = False +if env["platform"] == 'uwp': + if 'arm' in env["PROGSUFFIX"]: + webm_cpu_arm = True + else: + webm_cpu_x86 = True +else: + import platform + is_x11_or_server_arm = ((env["platform"] == 'x11' or env["platform"] == 'server') and platform.machine().startswith('arm')) + is_ios_x86 = (env["platform"] == 'iphone' and env["ios_sim"] == "yes") + is_android_x86 = (env["platform"] == 'android' and env["android_arch"] == 'x86') + if is_android_x86: + cpu_bits = '32' + if osx_fat: + webm_cpu_x86 = True + else: + webm_cpu_x86 = not is_x11_or_server_arm and (cpu_bits == '32' or cpu_bits == '64') and (env["platform"] == 'windows' or env["platform"] == 'x11' or env["platform"] == 'osx' or env["platform"] == 'haiku' or is_android_x86 or is_ios_x86) + webm_cpu_arm = is_x11_or_server_arm or (not is_ios_x86 and env["platform"] == 'iphone') or env["platform"] == 'bb10' or (not is_android_x86 and env["platform"] == 'android') + +if webm_cpu_x86: + import subprocess + import os + + yasm_paths = [ + "yasm", + "../../../yasm", + ] + + yasm_found = False + + devnull = open(os.devnull) + for yasm_path in yasm_paths: + try: + yasm_found = True + subprocess.Popen([yasm_path, "--version"], stdout=devnull, stderr=devnull).communicate() + except: + yasm_found = False + if yasm_found: + break + + if not yasm_found: + webm_cpu_x86 = False + print "YASM is necessary for WebM SIMD optimizations." + +webm_simd_optimizations = False + +if webm_cpu_x86: + if osx_fat: + #'osx' platform only: run python script which will compile using 'yasm' command and then merge 32-bit and 64-bit using 'lipo' command + env_libvpx["AS"] = 'python modules/webm/libvpx/yasm_osx_fat.py' + env_libvpx["ASFLAGS"] = '-I' + libvpx_dir[1:] + env_libvpx["ASCOM"] = '$AS $ASFLAGS $TARGET $SOURCES' + else: + if env["platform"] == 'windows' or env["platform"] == 'uwp': + env_libvpx["ASFORMAT"] = 'win' + elif env["platform"] == 'osx' or env["platform"] == "iphone": + env_libvpx["ASFORMAT"] = 'macho' + else: + env_libvpx["ASFORMAT"] = 'elf' + env_libvpx["ASFORMAT"] += cpu_bits + + env_libvpx["AS"] = 'yasm' + env_libvpx["ASFLAGS"] = '-I' + libvpx_dir[1:] + ' -f $ASFORMAT -D $ASCPU' + env_libvpx["ASCOM"] = '$AS $ASFLAGS -o $TARGET $SOURCES' + + if cpu_bits == '32': + env_libvpx["ASCPU"] = 'X86_32' + elif cpu_bits == '64': + env_libvpx["ASCPU"] = 'X86_64' + + env_libvpx.Append(CCFLAGS=['-DWEBM_X86ASM']) + + webm_simd_optimizations = True + +if webm_cpu_arm: + if env["platform"] == 'iphone': + env_libvpx["ASFLAGS"] = '-arch armv7' + elif env["platform"] == 'android' or env["platform"] == 'x11' or env["platform"] == 'server': + env_libvpx["ASFLAGS"] = '-mfpu=neon' + elif env["platform"] == 'uwp': + env_libvpx["AS"] = 'armasm' + env_libvpx["ASFLAGS"] = '' + env_libvpx["ASCOM"] = '$AS $ASFLAGS -o $TARGET $SOURCES' + + env_libvpx.Append(CCFLAGS=['-DWEBM_ARMASM']) + + webm_simd_optimizations = True + +if webm_simd_optimizations == False: + print "WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!" + + +env_libvpx.add_source_files(env.modules_sources, libvpx_sources) +if webm_cpu_x86: + is_clang_or_gcc = ('gcc' in env["CC"]) or ('clang' in env["CC"]) + + env_libvpx_mmx = env_libvpx.Clone() + if cpu_bits == '32' and is_clang_or_gcc: + env_libvpx_mmx.Append(CCFLAGS=['-mmmx']) + env_libvpx_mmx.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_mmx) + + env_libvpx_sse2 = env_libvpx.Clone() + if cpu_bits == '32' and is_clang_or_gcc: + env_libvpx_sse2.Append(CCFLAGS=['-msse2']) + env_libvpx_sse2.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_sse2) + + env_libvpx_ssse3 = env_libvpx.Clone() + if is_clang_or_gcc: + env_libvpx_ssse3.Append(CCFLAGS=['-mssse3']) + env_libvpx_ssse3.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_ssse3) + + env_libvpx_avx2 = env_libvpx.Clone() + if is_clang_or_gcc: + env_libvpx_avx2.Append(CCFLAGS=['-mavx2']) + env_libvpx_avx2.add_source_files(env.modules_sources, libvpx_sources_intrin_x86_avx2) + + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_intrin_x86) + + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_x86asm) + if cpu_bits == '64' or osx_fat: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_x86_64asm) +elif webm_cpu_arm: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm) + + env_libvpx_neon = env_libvpx.Clone() + if env["platform"] == 'android' and env["android_arch"] == 'armv6': + env_libvpx_neon.Append(CCFLAGS=['-mfpu=neon']) + env_libvpx_neon.add_source_files(env.modules_sources, libvpx_sources_arm_neon) + + if env["platform"] == 'uwp': + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_armasm_ms) + elif env["platform"] == 'iphone': + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas_apple) + else: + env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas) diff --git a/modules/webm/libvpx/yasm_osx_fat.py b/modules/webm/libvpx/yasm_osx_fat.py new file mode 100644 index 000000000..0065e2766 --- /dev/null +++ b/modules/webm/libvpx/yasm_osx_fat.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import sys +import os + +includes = sys.argv[1] +output_file = sys.argv[2] +input_file = sys.argv[3] + +can_remove = {} + +lipo_command = '' + +exit_code = 1 + +for arch in ['32', '64']: + if arch == '32' and input_file.endswith('x86_64.asm'): + can_remove[arch] = False + else: + command = 'yasm ' + includes + ' -f macho' + arch + ' -D X86_' + arch + ' -o ' + output_file + '.' + arch + ' ' + input_file + print(command) + if os.system(command) == 0: + lipo_command += output_file + '.' + arch + ' ' + can_remove[arch] = True + else: + can_remove[arch] = False + +if lipo_command != '': + lipo_command = 'lipo -create ' + lipo_command + '-output ' + output_file + print(lipo_command) + if os.system(lipo_command) == 0: + exit_code = 0 + +for arch in ['32', '64']: + if can_remove[arch]: + os.remove(output_file + '.' + arch) + +sys.exit(exit_code) diff --git a/modules/webm/register_types.cpp b/modules/webm/register_types.cpp new file mode 100644 index 000000000..e50eb337a --- /dev/null +++ b/modules/webm/register_types.cpp @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "video_stream_webm.h" + +static ResourceFormatLoaderVideoStreamWebm *webm_stream_loader = NULL; + +void register_webm_types() { + + webm_stream_loader = memnew(ResourceFormatLoaderVideoStreamWebm); + ResourceLoader::add_resource_format_loader(webm_stream_loader); + ClassDB::register_class<VideoStreamWebm>(); +} + +void unregister_webm_types() { + + memdelete(webm_stream_loader); +} diff --git a/modules/webm/register_types.h b/modules/webm/register_types.h new file mode 100644 index 000000000..3df0d7bba --- /dev/null +++ b/modules/webm/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_webm_types(); +void unregister_webm_types(); diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp new file mode 100644 index 000000000..bdd97f1df --- /dev/null +++ b/modules/webm/video_stream_webm.cpp @@ -0,0 +1,446 @@ +/*************************************************************************/ +/* av_stream_webm.cpp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "video_stream_webm.h" + +#include "VPXDecoder.hpp" +#include "OpusVorbisDecoder.hpp" + +#include "mkvparser/mkvparser.h" +#include "../theora/yuv2rgb.h" + +#include "os/file_access.h" +#include "globals.h" + +#include <string.h> + +class MkvReader : public mkvparser::IMkvReader { + +public: + MkvReader(const String &p_file) { + + file = FileAccess::open(p_file, FileAccess::READ); + ERR_FAIL_COND(!file); + } + ~MkvReader() { + + if (file) + memdelete(file); + } + + virtual int Read(long long pos, long len, unsigned char *buf) { + + if (file) { + + if (file->get_pos() != (size_t)pos) + file->seek(pos); + if (file->get_buffer(buf, len) == len) + return 0; + } + return -1; + } + + virtual int Length(long long *total, long long *available) { + + if (file) { + + const size_t len = file->get_len(); + if (total) + *total = len; + if (available) + *available = len; + return 0; + } + return -1; + } + +private: + FileAccess *file; +}; + +/**/ + +VideoStreamPlaybackWebm::VideoStreamPlaybackWebm() : + audio_track(0), + webm(NULL), + video(NULL), + audio(NULL), + video_frames(NULL), audio_frame(NULL), + video_frames_pos(0), video_frames_capacity(0), + num_decoded_samples(0), samples_offset(-1), + mix_callback(NULL), + mix_udata(NULL), + playing(false), paused(false), + delay_compensation(0.0), + time(0.0), video_frame_delay(0.0), video_pos(0.0), + texture(memnew(ImageTexture)), + pcm(NULL) +{} +VideoStreamPlaybackWebm::~VideoStreamPlaybackWebm() { + + delete_pointers(); +} + +bool VideoStreamPlaybackWebm::open_file(const String &p_file) { + + file_name = p_file; + webm = memnew(WebMDemuxer(new MkvReader(file_name), 0, audio_track)); + if (webm->isOpen()) { + + video = memnew(VPXDecoder(*webm, 8)); //TODO: Detect CPU threads + if (video->isOpen()) { + + audio = memnew(OpusVorbisDecoder(*webm)); + if (audio->isOpen()) { + + audio_frame = memnew(WebMFrame); + pcm = (int16_t *)memalloc(sizeof(int16_t) * audio->getBufferSamples() * webm->getChannels()); + } else { + + memdelete(audio); + audio = NULL; + } + + frame_data.resize((webm->getWidth() * webm->getHeight()) << 2); + texture->create(webm->getWidth(), webm->getHeight(), Image::FORMAT_RGBA8, Texture::FLAG_FILTER | Texture::FLAG_VIDEO_SURFACE); + + return true; + } + memdelete(video); + video = NULL; + } + memdelete(webm); + webm = NULL; + return false; +} + +void VideoStreamPlaybackWebm::stop() { + + if (playing) { + + delete_pointers(); + + pcm = NULL; + + audio_frame = NULL; + video_frames = NULL; + + video = NULL; + audio = NULL; + + open_file(file_name); //Should not fail here... + + video_frames_capacity = video_frames_pos = 0; + num_decoded_samples = 0; + samples_offset = -1; + video_frame_delay = video_pos = 0.0; + } + time = 0.0; + playing = false; +} +void VideoStreamPlaybackWebm::play() { + + stop(); + + delay_compensation = GlobalConfig::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation /= 1000.0; + + playing = true; +} + +bool VideoStreamPlaybackWebm::is_playing() const { + + return playing; +} + +void VideoStreamPlaybackWebm::set_paused(bool p_paused) { + + paused = p_paused; +} +bool VideoStreamPlaybackWebm::is_paused(bool p_paused) const { + + return paused; +} + +void VideoStreamPlaybackWebm::set_loop(bool p_enable) { + + //Empty +} +bool VideoStreamPlaybackWebm::has_loop() const { + + return false; +} + +float VideoStreamPlaybackWebm::get_length() const { + + if (webm) + return webm->getLength(); + return 0.0f; +} + +float VideoStreamPlaybackWebm::get_pos() const { + + return video_pos; +} +void VideoStreamPlaybackWebm::seek_pos(float p_time) { + + //Not implemented +} + +void VideoStreamPlaybackWebm::set_audio_track(int p_idx) { + + audio_track = p_idx; +} + +Ref<Texture> VideoStreamPlaybackWebm::get_texture() { + + return texture; +} +void VideoStreamPlaybackWebm::update(float p_delta) { + + if ((!playing || paused) || !video) + return; + + bool audio_buffer_full = false; + + if (samples_offset > -1) { + + //Mix remaining samples + const int to_read = num_decoded_samples - samples_offset; + const int mixed = mix_callback(mix_udata, pcm + samples_offset * webm->getChannels(), to_read); + if (mixed != to_read) { + + samples_offset += mixed; + audio_buffer_full = true; + } else { + + samples_offset = -1; + } + } + + const bool hasAudio = (audio && mix_callback); + while ((hasAudio && (!audio_buffer_full || !has_enough_video_frames())) || (!hasAudio && video_frames_pos == 0)) { + + if (hasAudio && !audio_buffer_full && audio_frame->isValid() && audio->getPCMS16(*audio_frame, pcm, num_decoded_samples) && num_decoded_samples > 0) { + + const int mixed = mix_callback(mix_udata, pcm, num_decoded_samples); + if (mixed != num_decoded_samples) { + + samples_offset = mixed; + audio_buffer_full = true; + } + } + + WebMFrame *video_frame; + if (video_frames_pos >= video_frames_capacity) { + + WebMFrame **video_frames_new = (WebMFrame **)memrealloc(video_frames, ++video_frames_capacity * sizeof(void *)); + ERR_FAIL_COND(!video_frames_new); //Out of memory + (video_frames = video_frames_new)[video_frames_capacity - 1] = memnew(WebMFrame); + } + video_frame = video_frames[video_frames_pos]; + + if (!webm->readFrame(video_frame, audio_frame)) //This will invalidate frames + break; //Can't demux, EOS? + + if (video_frame->isValid()) + ++video_frames_pos; + }; + + const double video_delay = video->getFramesDelay() * video_frame_delay; + + bool want_this_frame = false; + while (video_frames_pos > 0 && !want_this_frame) { + + WebMFrame *video_frame = video_frames[0]; + if (video_frame->time <= time + video_delay) { + + if (video->decode(*video_frame)) { + + VPXDecoder::IMAGE_ERROR err; + VPXDecoder::Image image; + + while ((err = video->getImage(image)) != VPXDecoder::NO_FRAME) { + + want_this_frame = (time - video_frame->time <= video_frame_delay); + + if (want_this_frame) { + + if (err == VPXDecoder::NO_ERROR && image.w == webm->getWidth() && image.h == webm->getHeight()) { + + PoolVector<uint8_t>::Write w = frame_data.write(); + bool converted = false; + + if (image.chromaShiftW == 1 && image.chromaShiftH == 1) { + + yuv420_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0); +// libyuv::I420ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h); + converted = true; + } else if (image.chromaShiftW == 1 && image.chromaShiftH == 0) { + + yuv422_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0); +// libyuv::I422ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h); + converted = true; + } else if (image.chromaShiftW == 0 && image.chromaShiftH == 0) { + + yuv444_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0); +// libyuv::I444ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h); + converted = true; + } else if (image.chromaShiftW == 2 && image.chromaShiftH == 0) { + +// libyuv::I411ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h); +// converted = true; + } + + if (converted) + texture->set_data(Image(image.w, image.h, 0, Image::FORMAT_RGBA8, frame_data)); //Zero copy send to visual server + } + + break; + } + } + } + + video_frame_delay = video_frame->time - video_pos; + video_pos = video_frame->time; + + memmove(video_frames, video_frames + 1, (--video_frames_pos) * sizeof(void *)); + video_frames[video_frames_pos] = video_frame; + } else { + + break; + } + } + + time += p_delta; + + if (video_frames_pos == 0 && webm->isEOS()) + stop(); +} + +void VideoStreamPlaybackWebm::set_mix_callback(VideoStreamPlayback::AudioMixCallback p_callback, void *p_userdata) { + + mix_callback = p_callback; + mix_udata = p_userdata; +} +int VideoStreamPlaybackWebm::get_channels() const { + + if (audio) + return webm->getChannels(); + return 0; +} +int VideoStreamPlaybackWebm::get_mix_rate() const { + + if (audio) + return webm->getSampleRate(); + return 0; +} + +inline bool VideoStreamPlaybackWebm::has_enough_video_frames() const +{ + if (video_frames_pos > 0) { + + const double audio_delay = AudioServer::get_singleton()->get_output_delay(); + const double video_time = video_frames[video_frames_pos - 1]->time; + return video_time >= time + audio_delay + delay_compensation; + } + return false; +} + +void VideoStreamPlaybackWebm::delete_pointers() { + + if (pcm) + memfree(pcm); + + if (audio_frame) + memdelete(audio_frame); + for (int i = 0; i < video_frames_capacity; ++i) + memdelete(video_frames[i]); + if (video_frames) + memfree(video_frames); + + if (video) + memdelete(video); + if (audio) + memdelete(audio); + + if (webm) + memdelete(webm); +} + +/**/ + +RES ResourceFormatLoaderVideoStreamWebm::load(const String &p_path, const String &p_original_path, Error *r_error) { + + Ref<VideoStreamWebm> stream = memnew(VideoStreamWebm); + stream->set_file(p_path); + if (r_error) + *r_error = OK; + return stream; +} + +void ResourceFormatLoaderVideoStreamWebm::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("webm"); +} +bool ResourceFormatLoaderVideoStreamWebm::handles_type(const String &p_type) const { + + return (p_type == "VideoStream" || p_type == "VideoStreamWebm"); +} + +String ResourceFormatLoaderVideoStreamWebm::get_resource_type(const String &p_path) const { + + const String exl = p_path.get_extension().to_lower(); + if (exl == "webm") + return "VideoStreamWebm"; + return ""; +} + +/**/ + +VideoStreamWebm::VideoStreamWebm() : + audio_track(0) +{} + +Ref<VideoStreamPlayback> VideoStreamWebm::instance_playback() { + + Ref<VideoStreamPlaybackWebm> pb = memnew(VideoStreamPlaybackWebm); + pb->set_audio_track(audio_track); + if (pb->open_file(file)) + return pb; + return NULL; +} + +void VideoStreamWebm::set_file(const String &p_file) { + + file = p_file; +} +void VideoStreamWebm::set_audio_track(int p_track) { + + audio_track = p_track; +} diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h new file mode 100644 index 000000000..cb4ef53f6 --- /dev/null +++ b/modules/webm/video_stream_webm.h @@ -0,0 +1,128 @@ +/*************************************************************************/ +/* av_stream_webm.cpp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "scene/resources/video_stream.h" +#include "io/resource_loader.h" + +class WebMFrame; +class WebMDemuxer; +class VPXDecoder; +class OpusVorbisDecoder; + +class VideoStreamPlaybackWebm : public VideoStreamPlayback { + + GDCLASS(VideoStreamPlaybackWebm, VideoStreamPlayback) + + String file_name; + int audio_track; + + WebMDemuxer *webm; + VPXDecoder *video; + OpusVorbisDecoder *audio; + + WebMFrame **video_frames, *audio_frame; + int video_frames_pos, video_frames_capacity; + + int num_decoded_samples, samples_offset; + AudioMixCallback mix_callback; + void *mix_udata; + + bool playing, paused; + double delay_compensation; + double time, video_frame_delay, video_pos; + + PoolVector<uint8_t> frame_data; + Ref<ImageTexture> texture; + + int16_t *pcm; + +public: + VideoStreamPlaybackWebm(); + ~VideoStreamPlaybackWebm(); + + bool open_file(const String &p_file); + + virtual void stop(); + virtual void play(); + + virtual bool is_playing() const; + + virtual void set_paused(bool p_paused); + virtual bool is_paused(bool p_paused) const; + + virtual void set_loop(bool p_enable); + virtual bool has_loop() const; + + virtual float get_length() const; + + virtual float get_pos() const; + virtual void seek_pos(float p_time); + + virtual void set_audio_track(int p_idx); + + virtual Ref<Texture> get_texture(); + virtual void update(float p_delta); + + virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); + virtual int get_channels() const; + virtual int get_mix_rate() const; + +private: + inline bool has_enough_video_frames() const; + + void delete_pointers(); +}; + +/**/ + +class VideoStreamWebm : public VideoStream { + + GDCLASS(VideoStreamWebm, VideoStream) + + String file; + int audio_track; + +public: + VideoStreamWebm(); + + virtual Ref<VideoStreamPlayback> instance_playback(); + + virtual void set_file(const String &p_file); + virtual void set_audio_track(int p_track); +}; + +/**/ + +class ResourceFormatLoaderVideoStreamWebm : public ResourceFormatLoader { + +public: + virtual RES load(const String &p_path, const String &p_original_path, Error *r_error); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; diff --git a/modules/webp/SCsub b/modules/webp/SCsub new file mode 100644 index 000000000..92f34c4da --- /dev/null +++ b/modules/webp/SCsub @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_webp = env_modules.Clone() + +# Thirdparty source files +if (env['builtin_libwebp'] != 'no'): + thirdparty_dir = "#thirdparty/libwebp/" + thirdparty_sources = [ + "enc/webpenc.c", + "enc/near_lossless.c", + "enc/frame.c", + "enc/alpha.c", + "enc/picture_csp.c", + "enc/vp8l.c", + "enc/picture_psnr.c", + "enc/delta_palettization.c", + "enc/syntax.c", + "enc/backward_references.c", + "enc/token.c", + "enc/analysis.c", + "enc/iterator.c", + "enc/picture_tools.c", + "enc/picture_rescale.c", + "enc/config.c", + "enc/tree.c", + "enc/cost.c", + "enc/picture.c", + "enc/quant.c", + "enc/filter.c", + "enc/histogram.c", + "utils/rescaler.c", + "utils/filters.c", + "utils/quant_levels_dec.c", + "utils/huffman.c", + "utils/thread.c", + "utils/quant_levels.c", + "utils/bit_writer.c", + "utils/bit_reader.c", + "utils/random.c", + "utils/utils.c", + "utils/huffman_encode.c", + "utils/color_cache.c", + "mux/muxinternal.c", + "mux/muxread.c", + "mux/anim_encode.c", + "mux/muxedit.c", + "dec/webp.c", + "dec/frame.c", + "dec/alpha.c", + "dec/vp8l.c", + "dec/io.c", + "dec/vp8.c", + "dec/idec.c", + "dec/tree.c", + "dec/buffer.c", + "dec/quant.c", + "demux/demux.c", + "demux/anim_decode.c", + "dsp/yuv.c", + "dsp/filters_sse2.c", + "dsp/dec_sse41.c", + "dsp/rescaler.c", + "dsp/lossless_sse2.c", + "dsp/alpha_processing_sse41.c", + "dsp/alpha_processing_sse2.c", + "dsp/filters.c", + "dsp/upsampling_mips_dsp_r2.c", + "dsp/dec_neon.c", + "dsp/enc_neon.c", + "dsp/lossless_enc_mips32.c", + "dsp/lossless_enc_sse2.c", + "dsp/upsampling.c", + "dsp/lossless_enc_neon.c", + "dsp/alpha_processing.c", + "dsp/cost_sse2.c", + "dsp/dec_mips32.c", + "dsp/enc_avx2.c", + "dsp/rescaler_mips32.c", + "dsp/enc.c", + "dsp/lossless_enc_sse41.c", + "dsp/cost_mips32.c", + "dsp/lossless_mips_dsp_r2.c", + "dsp/filters_mips_dsp_r2.c", + "dsp/upsampling_neon.c", + "dsp/alpha_processing_mips_dsp_r2.c", + "dsp/enc_mips_dsp_r2.c", + "dsp/lossless.c", + "dsp/yuv_mips_dsp_r2.c", + "dsp/cost_mips_dsp_r2.c", + "dsp/argb.c", + "dsp/dec_sse2.c", + "dsp/rescaler_sse2.c", + "dsp/enc_sse41.c", + "dsp/argb_mips_dsp_r2.c", + "dsp/lossless_enc_mips_dsp_r2.c", + "dsp/dec_clip_tables.c", + "dsp/yuv_mips32.c", + "dsp/cpu.c", + "dsp/dec.c", + "dsp/argb_sse2.c", + "dsp/lossless_neon.c", + "dsp/lossless_enc.c", + "dsp/enc_mips32.c", + "dsp/cost.c", + "dsp/rescaler_mips_dsp_r2.c", + "dsp/dec_mips_dsp_r2.c", + "dsp/rescaler_neon.c", + "dsp/yuv_sse2.c", + "dsp/enc_sse2.c", + "dsp/upsampling_sse2.c", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_webp.add_source_files(env.modules_sources, thirdparty_sources) + env_webp.Append(CPPPATH=[thirdparty_dir]) + +# Godot source files +env_webp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/webp/config.py b/modules/webp/config.py new file mode 100644 index 000000000..fb920482f --- /dev/null +++ b/modules/webp/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp new file mode 100644 index 000000000..3508c6a66 --- /dev/null +++ b/modules/webp/image_loader_webp.cpp @@ -0,0 +1,183 @@ +/*************************************************************************/ +/* image_loader_webp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "image_loader_webp.h" + +#include "io/marshalls.h" +#include "print_string.h" +#include "os/os.h" + +#include <stdlib.h> +#include <webp/decode.h> +#include <webp/encode.h> + +static PoolVector<uint8_t> _webp_lossy_pack(const Image& p_image,float p_quality) { + + ERR_FAIL_COND_V(p_image.empty(),PoolVector<uint8_t>()); + + Image img=p_image; + if (img.detect_alpha()) + img.convert(Image::FORMAT_RGBA8); + else + img.convert(Image::FORMAT_RGB8); + + Size2 s(img.get_width(),img.get_height()); + PoolVector<uint8_t> data = img.get_data(); + PoolVector<uint8_t>::Read r = data.read(); + + uint8_t *dst_buff=NULL; + size_t dst_size=0; + if (img.get_format()==Image::FORMAT_RGB8) { + + dst_size = WebPEncodeRGB(r.ptr(),s.width,s.height,3*s.width,CLAMP(p_quality*100.0,0,100.0),&dst_buff); + } else { + dst_size = WebPEncodeRGBA(r.ptr(),s.width,s.height,4*s.width,CLAMP(p_quality*100.0,0,100.0),&dst_buff); + } + + ERR_FAIL_COND_V(dst_size==0,PoolVector<uint8_t>()); + PoolVector<uint8_t> dst; + dst.resize(4+dst_size); + PoolVector<uint8_t>::Write w = dst.write(); + w[0]='W'; + w[1]='E'; + w[2]='B'; + w[3]='P'; + copymem(&w[4],dst_buff,dst_size); + free(dst_buff); + w=PoolVector<uint8_t>::Write(); + return dst; +} + +static Image _webp_lossy_unpack(const PoolVector<uint8_t>& p_buffer) { + + int size = p_buffer.size()-4; + ERR_FAIL_COND_V(size<=0,Image()); + PoolVector<uint8_t>::Read r = p_buffer.read(); + + ERR_FAIL_COND_V(r[0]!='W' || r[1]!='E' || r[2]!='B' || r[3]!='P',Image()); + WebPBitstreamFeatures features; + if (WebPGetFeatures(&r[4],size,&features)!=VP8_STATUS_OK) { + ERR_EXPLAIN("Error unpacking WEBP image:"); + ERR_FAIL_V(Image()); + } + + //print_line("width: "+itos(features.width)); + //print_line("height: "+itos(features.height)); + //print_line("alpha: "+itos(features.has_alpha)); + + PoolVector<uint8_t> dst_image; + int datasize = features.width*features.height*(features.has_alpha?4:3); + dst_image.resize(datasize); + + PoolVector<uint8_t>::Write dst_w = dst_image.write(); + + bool errdec=false; + if (features.has_alpha) { + errdec = WebPDecodeRGBAInto(&r[4],size,dst_w.ptr(),datasize,4*features.width)==NULL; + } else { + errdec = WebPDecodeRGBInto(&r[4],size,dst_w.ptr(),datasize,3*features.width)==NULL; + + } + + //ERR_EXPLAIN("Error decoding webp! - "+p_file); + ERR_FAIL_COND_V(errdec,Image()); + + dst_w = PoolVector<uint8_t>::Write(); + + return Image(features.width,features.height,0,features.has_alpha?Image::FORMAT_RGBA8:Image::FORMAT_RGB8,dst_image); + +} + + +Error ImageLoaderWEBP::load_image(Image *p_image,FileAccess *f) { + + + uint32_t size = f->get_len(); + PoolVector<uint8_t> src_image; + src_image.resize(size); + + WebPBitstreamFeatures features; + + PoolVector<uint8_t>::Write src_w = src_image.write(); + f->get_buffer(src_w.ptr(),size); + ERR_FAIL_COND_V(f->eof_reached(), ERR_FILE_EOF); + + if (WebPGetFeatures(src_w.ptr(),size,&features)!=VP8_STATUS_OK) { + f->close(); + //ERR_EXPLAIN("Error decoding WEBP image: "+p_file); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + + print_line("width: "+itos(features.width)); + print_line("height: "+itos(features.height)); + print_line("alpha: "+itos(features.has_alpha)); + + src_w = PoolVector<uint8_t>::Write(); + + PoolVector<uint8_t> dst_image; + int datasize = features.width*features.height*(features.has_alpha?4:3); + dst_image.resize(datasize); + + PoolVector<uint8_t>::Read src_r = src_image.read(); + PoolVector<uint8_t>::Write dst_w = dst_image.write(); + + + bool errdec=false; + if (features.has_alpha) { + errdec = WebPDecodeRGBAInto(src_r.ptr(),size,dst_w.ptr(),datasize,4*features.width)==NULL; + } else { + errdec = WebPDecodeRGBInto(src_r.ptr(),size,dst_w.ptr(),datasize,3*features.width)==NULL; + + } + + //ERR_EXPLAIN("Error decoding webp! - "+p_file); + ERR_FAIL_COND_V(errdec,ERR_FILE_CORRUPT); + + src_r = PoolVector<uint8_t>::Read(); + dst_w = PoolVector<uint8_t>::Write(); + + *p_image = Image(features.width,features.height,0,features.has_alpha?Image::FORMAT_RGBA8:Image::FORMAT_RGB8,dst_image); + + + return OK; + +} + +void ImageLoaderWEBP::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("webp"); +} + + +ImageLoaderWEBP::ImageLoaderWEBP() { + + Image::lossy_packer=_webp_lossy_pack; + Image::lossy_unpacker=_webp_lossy_unpack; +} + + diff --git a/modules/webp/image_loader_webp.h b/modules/webp/image_loader_webp.h new file mode 100644 index 000000000..eb1b32ac9 --- /dev/null +++ b/modules/webp/image_loader_webp.h @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* image_loader_webp.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef IMAGE_LOADER_WEBP_H +#define IMAGE_LOADER_WEBP_H + +#include "io/image_loader.h" + +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ +class ImageLoaderWEBP : public ImageFormatLoader { + + +public: + + virtual Error load_image(Image *p_image,FileAccess *f); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + ImageLoaderWEBP(); +}; + + + +#endif diff --git a/modules/webp/register_types.cpp b/modules/webp/register_types.cpp new file mode 100644 index 000000000..7a4e35dc4 --- /dev/null +++ b/modules/webp/register_types.cpp @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" + +#include "image_loader_webp.h" + +static ImageLoaderWEBP *image_loader_webp = NULL; + +void register_webp_types() { + + image_loader_webp = memnew( ImageLoaderWEBP ); + ImageLoader::add_image_format_loader(image_loader_webp); +} + +void unregister_webp_types() { + + memdelete( image_loader_webp ); +} diff --git a/modules/webp/register_types.h b/modules/webp/register_types.h new file mode 100644 index 000000000..ce1f29937 --- /dev/null +++ b/modules/webp/register_types.h @@ -0,0 +1,30 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_webp_types(); +void unregister_webp_types(); |
